React Three Fiber 和 NextJS 入门模板
使用模板
里面有什么?
我从哪里开始
功能细分
变化
下一步是什么?
但你能做什么呢?
我一直对 3D 和游戏开发很感兴趣,从 Blender 和 Maya 到 Unity 和 Unreal Engine,甚至使用 OpenGL 或 DirectX 定制的 C++ 游戏引擎。你研究得越深入,就越倾向于使用 Python 或 C 之类的语言。但是,如果能用 JavaScript 在 Web 上实现 Unity 等软件中的许多功能,那会怎样呢?
这就是WebGL、ThreeJS和React Three Fiber发挥作用的地方。WebGL是OpenGL规范的 Web 实现(即将 2D 和 3D 渲染引入 Web)。ThreeJS是一个 JavaScript 库,它使用 WebGL 创建低级 2D 和 3D 图形 API(例如绘制画布、创建“相机”、顶点和像素/片段着色器、从 3D 网格渲染顶点缓冲区等)。最后,react-three-fiber是一个 React 库,它通过易于使用的函数和组件将 ThreeJS 更高效地集成到 React 生态系统中。
React Three Fiber 一直是快速构建 3D 应用和游戏原型的绝佳方式,甚至高效到足以将它们投入生产。问题是什么?它可能需要大量的设置。仅从最后一段就能看出 R3F 使用的一系列依赖项,而当你熟悉当前的 ThreeJS 生态系统(例如:three-stdlib)后,情况会变得更加复杂。
我尝试寻找一些开箱即用的模板,找到了一些,但我想要更多。所以我做了不可避免的事情——自己拼凑了一个模板:r3f-next-starter。
此模板为您提供了开箱即用的坚实基础:具有 R3F + DOM 设置的 NextJS 站点、可访问性(用于 3D 的 aria 元素)、使用 Leva 的快速“切换”以及具有语法突出显示的出色着色器设置。
使用模板
- 克隆仓库:
git clone https://github.com/whoisryosuke/r3f-next-starter.git
- 安装依赖项:
yarn
- 运行开发服务器:
yarn dev
- 在http://localhost:3000/查看一些 3D 👀
里面有什么?
- ♻ 轻松混合 DOM 和 3D Canvas
- 🎨 片段和顶点着色器(带有语法高亮和自动完成功能)
- 🎛 Leva 调试面板
- ♿ R3F A11y
- 🏪 站立 商店
- 📁 相对路径(
@/components/
) - 📴离线模式
- 🍱 捆绑分析器
- ☑ Typescript
我从哪里开始
我在 NextJS 仓库里找到了一个示例,它涵盖了 ThreeJS 的基本设置(也就是使用 React Three Fiber)。这很棒,但还不够。
我深入挖掘了一下,发现了create-r3f-app。这个 CLI 在我的 Windows 系统上无法完全正常工作(在某个步骤中崩溃了)。不过它确实提供了部分输出,让我可以修改模板使其正常工作。它还附带了 Tailwind 安装程序,所以我删除了所有对它的引用。
create-r3f-app 模板是一个很好的起点,它基本上构成了我模板的很大一部分。它提供了用于管理 R3F Canvas 与 DOM 元素的出色设置。此外,它还提供了用于导入着色器文件(.frag
和.vert
)的 Webpack 设置。我将在下文中详细介绍所有功能。
我仔细研究了一下,开始把它完全转换成 Typescript,并添加了我需要的所有功能(Leva、a11y 等等),基本上就搞定了。我会在下面进一步讲解这些内容。
功能细分
这个模板中发生了很多好事(主要来自 create-r3f-app),我认为详细地介绍一下其中的一些事情会很酷。
DOM 与 R3F
创建新页面时,无需在画布中包装任何内容。这会自动在 _app.jsx
文件中进行。 partition
中的脚本 _app.jsx
会检查<Dom>
和<R3f>
,并将您的 R3F 组件包装到画布中。
import type { AppProps } from 'next/app'
import { useRouter } from 'next/router'
import useStore from '@/helpers/store'
import { useEffect } from 'react'
import Header from '@/config'
import Dom from '@/components/layout/dom'
import partition from '@/helpers/partition'
import dynamic from 'next/dynamic'
import '@/styles/index.css'
// We dynamically import the canvas (required of any R3F component in NextJS-land)
const LCanvas = dynamic(() => import('@/components/layout/canvas'), {
ssr: false,
})
// This component takes it's children and finds the R3F and DOM
// and splits them up into their own space
const Balance = ({ child }) => {
const [r3f, dom] = partition(child, (c) => c.props.r3f === true)
return (
<>
<Dom>{dom}</Dom>
<LCanvas>{r3f}</LCanvas>
</>
)
}
// The wrapper around the entire app
// Standard NextJS process/API
function App({ Component, pageProps = { title: 'index' } }: AppProps) {
// We grab the children whatever page we're rendering
// and make sure we run any initialProps from NextJS to hydrate the component
const child = Component(pageProps).props.children
return (
<>
<Header title={pageProps.title} />
<Balance child={child} />
</>
)
}
export default App
每个页面都应该导出 JSX 片段中的<DOM />
组件 <R3f r3f />
(这样就可以有 2 个子页面而没有父页面):
import dynamic from 'next/dynamic'
const Shader = dynamic(() => import('@/components/canvas/Shader/Shader'), {
ssr: false,
})
// dom components goes here
const DOM = () => {
return (
<>
<p>420 blaze it</p>
</>
)
}
// canvas components goes here
const R3F = ({ r3f = true }) => {
return (
<>
<Shader />
</>
)
}
// The page component that gets "split up" by the `_app.js` file above
const Page = () => {
return (
<>
<DOM />
<R3F r3f />
</>
)
}
export default Page
export async function getStaticProps() {
return {
props: {
title: 'Index',
},
}
}
它使页面编写变得更简单,混合 DOM 和 R3F 变得容易,并且每次都无需费力地包装画布。
着色器支持
如果你已经使用过 Webpack,这实际上很容易实现。你只需要raw-loader
加载“原始”着色器文件并glsify-loader
解析GLSL 着色器即可。
在 NextJS 中,你可以扩展 Webpack 规则来添加插件:
// next.config.js
config.module.rules.push({
"test": /\.(glsl|vs|fs|vert|frag)$/,
"exclude": /node_modules/,
"use": ["raw-loader", "glslify-loader"]
})
如果您喜欢其他文件格式,可以在此处添加,以便系统自动选择。例如,有些项目更喜欢使用.pixel
或pshader
来处理片段着色器。
当您在 VSCode 中浏览着色器(.frag
和 .vert
)(或者只是打开项目)时,您应该会看到一个弹出窗口来安装处理语法突出显示和自动完成的插件。
打开项目时的建议是使用 VSCode 工作区功能并使用推荐的扩展配置(.vscode\extensions.json
)。
变化
打字稿
第一个重大变化是将所有内容转换为 Typescript。create-r3f-app 模板支持 Typescript,但大多数文件仍是 Typescript.js
格式。这暴露了着色器文件的一些问题(.frag
),每当将它们导入到文件中时,我都会看到一个错误,提示找不到它们。我需要创建一个新的 Typescript 定义文件shader.d.ts
(参见这个 StackOverflow):
// shader.d.ts
declare module '*.vtx' {
const content: string
export default content
}
declare module '*.frg' {
const content: string
export default content
}
CSS 重置
由于我移除了 Tailwind,所以我也移除了它们的“base/reset” CSS 文件,该文件用于“规范化”跨浏览器的样式输出。我选择集成经典的 Normalize.css,然后就此打住。
a11y 支持
该模板缺少的一个主要功能是新的 react-three-a11y 库。它允许你将 3D 组件包装到一个<A11y>
可执行以下操作的组件中:
- 在 DOM 中为你的画布元素创建一个符合 aria 标准的 HTML 元素
onFocus
允许您在 3D 图层中添加焦点事件逻辑(例如
这个插件集成起来非常简单,我直接安装了它,添加了一个组件来向屏幕阅读器“播报”信息,并将现有组件用无障碍功能包装器包装起来。搞定!没有理由不让你的应用或游戏更易于访问。
使用 Leva 快速切换
在开发和原型设计过程中,最强大的工具之一就是使用 GUI 修改属性。例如,使用滑块更改对象的位置或着色器属性(例如颜色)会带来令人愉悦的体验。
这又是一个快速简便的添加功能。只需使用其钩子将一些组件属性安装并连接到面板即可useControl
。
CodeSandbox 优化
当你使用 R3F 和 CodeSandbox 等在线代码编辑器时,你首先会注意到的一件事是——它崩溃得太频繁了。这通常是由两个原因造成的:文件在容易出现 bug 的地方(比如useFrame
hook)不断进行热加载;以及热加载会干扰 ThreeJS 相对于 R3F/React 端的画布状态(画布经常掉线/崩溃)。
避免这种情况的方法是添加一个配置文件,让 CodeSandbox 知道不要在每次击键时重新加载,并尽可能避免无限重新渲染:
// sandbox.config.json
{
"infiniteLoopProtection": true,
"hardReloadOnChange": false,
"view": "browser"
}
下一步是什么?
我想在模板中添加更多示例,都是一些我经常重复的内容——比如 R3F React 组件 props 的正确输入方法。是吗Vector3
?还是number[3]
?这类答案应该直接复制粘贴就好,不用花时间在 Google 上搜索或在 R3F Discord 上翻来覆去地找。
任何过于强烈或可能使模板膨胀的东西最终都会出现在我的新 r3f-experiments 仓库中。
但你能做什么呢?
很多很酷的东西——从屡获殊荣的应用程序体验,到浏览器中完整的 2D/2.5D/3D 游戏!
我复制了这个模板,然后创建了一个受《小行星》启发的迷你游戏。我只需要添加一个玩家“模型”(也就是一个锥体),在场景中添加一些小行星(也就是立方体),并为玩家设置键盘输入(WASD 用于移动)。哦,还有,弄清楚如何让摄像机随着玩家模型一起移动有点棘手。但从提交的代码中可以看出,创建整个体验并不需要太多代码。
希望这些内容能启发你(并让你更轻松地!)创建自己的 3D 原型和实验。和往常一样,如果你想聊聊什么或问个简单的问题,可以在 Twitter 上联系我。