这是关于 Fiber 内部工作机制的高级指南,如果你是刚开始了解,最好先去看看introduction 的内容,从那里开始了解

How does it work? 是如何工作的?

React Three Fiber 是threejs 的 React renderer(渲染器)。

Creating THREE objects 创建三维物体

在 threejs 中我们创建新的三维对象用的是传统的 JS API:

const myBox = new THREE.BoxGeometry(1, 2, 3)

这意味着每个Fiber组件实际上将创建一个新的THREE对象,该对象将添加到场景中。虽然不一定需要理解这是如何工作的才能使用Fiber,但它将更好地帮助您处理在项目中可能需要的任何内容,阅读其他人的Fiber代码,甚至帮助你对其他的代码库做出贡献。

让我们看一个简单的 React 例子:

import { Canvas } from '@react-three/fiber'

function MyApp() {
  return (
    <Canvas>
      <group>
        <mesh>
          <meshNormalMaterial />
          <boxGeometry args={[2, 2, 2]} />
        </mesh>
      </group>
    </Canvas>
  )
}

如果是 threejs 代码,那么相当于是这样:

import * as THREE from 'three'

const scene = new THREE.Scene() // <Canvas>

const group = new THREE.Group() // <group>

const mesh = new THREE.Mesh() // <mesh />
const material = new THREE.MeshNormalMaterial() // <meshNormalMaterial />
const geometry = new THREE.BoxGeometry(2, 2, 2) // <boxGeometry />

mesh.material = material
mesh.geometry = geometry

group.add(mesh)
scene.add(group)

我们的Canvas元素将创建一个新的场景,Fiber将为每个组件实例化新的对象,并在场景数据中正确地将它们组合在一起!

此外,Fiber将:

在[0,0,0]处设置一个新的透视相机,并将其设置为默认值

设置具有onPointer属性的所有网格上的射线投射指针事件

设置色调映射

自动处理窗口调整大小

让我们来更深入了解一下!

对象的创建由Fiber渲染器悄悄地处理,BoxGeometry构造函数的名称等效于驼峰式组件<boxGeometry />,而构造函数参数 - 在我们的示例中为[1, 2, 3] - 通过 args 参数传递:

<boxGeometry args={[1, 2, 3]} />

注意只有组件第一次被添加到 React Tree 上时才会自动创建 object

The attach props attach 属性

Fiber始终尝试正确地推断组件与其父组件之间的关系,例如:

<group>
  <mesh />
</group>

我们知道 group 可以能有 children,所以 Fiber 会调用 group 实例上的add 方法:

group.add(mesh)

对于mesh和其他three.js对象,规则可能不同。查看three.js文档,我们可以看到如何使用材料和几何体构建THREE.Mesh对象。

使用attach 属性,我们可以精确地告诉渲染器将每个组件附加到哪个属性:

<mesh>
  <meshNormalMaterial attach="material" />
  <boxGeometry attach="geometry" />
</mesh>

这其实相当于精确的告诉 Fiber 该这样进行渲染:

mesh.material = new THREE.MeshNormalMaterial()
mesh.geometry = new THREE.BoxGeometry()

正如您所看到的,attach属性告诉Fiber将父级的材质属性设置为对我们的<meshNormalMaterial />对象的引用。

请注意,虽然我们在此示例中使用了几何体和材料,但Fiber还从构造函数名称推断出attach属性,因此任何具有material(材质)或 geometry(几何体)的内容都将自动附加到其父级的正确属性上。

Props 属性

使用 Fiber,你可以传递任何Threejs 属性作为 React 属性,只要那个属性在 threejs 相应的类型上有,设置之后该属性和值会被设置到构造出来的相应类型的实例对象上:

<meshBasicMaterial color="red" />

react 的代码相当于这些 threejs 代码:

const material = new THREE.MeshBasicMaterial()
material.color = 'red'

Fiber将检查属性值的类型,然后执行以下操作之一:

直接分配新值

如果值是具有set方法的对象,则调用该方法

如有必要构造新对象。

在格式之间进行转换

<mesh scale={[1, 2, 3]} />

react 的代码相当于这些 threejs 代码:

const mesh = new THREE.Mesh()
mesh.scale = new THREE.Vector3(1, 2, 3)

// on update, it will instead `set()` the vector
mesh.scale.set(3, 4, 5)

Pointer Events 指针事件

指针事件由Fiber自动处理。启动时,它会为鼠标拾取创建一个射线投射器raycaster。

每个带有onPointer属性的对象都将添加到每帧由射线投射器检查的对象数组中:

<mesh onPointerDown={console.log}>...</mesh>

每次鼠标移动到<Canvas/>元素上或调整窗口大小时,都会更新射线的起点和方向。Fiber还处理相机切换,这意味着射线投射器将始终使用当前活动的相机。

使用raycast属性时,将使用自定义射线选择对象:

import { useCamera } from '@react-three/drei'

return <mesh raycast={useCamera(anotherCamera)} />

Render Loop 渲染循环

默认情况下,Fiber将设置一个渲染循环,从默认相机渲染默认场景到WebGLRenderer。

循环通过setAnimationLoop进行设置,每当有新的可渲染帧时,它将执行其回调。这是每次渲染时会发生的事情:

执行所有global before effects (全局前效果)

时钟增量被保存-意味着所有useFrame调用将共享相同的增量

按顺序执行useFrame回调

调用renderer.render(scene, camera),有效地将场景渲染到屏幕上

执行所有global after effects (全局后效果)