高效实用React Three Fiber的方式

Objects 对象 properties 属性 和 constructor arguments 构造参数

Declaring object 声明对象

你可以使用Threejs中所有的对象目录和属性。当有任何疑问的时候,查询threejs文档总没错的。

❌你可以这样来创建一个对象,但是不推荐:

<mesh
  visible
  userData={{ hello: 'world' }}
  position={new THREE.Vector3(1, 2, 3)}
  rotation={new THREE.Euler(Math.PI / 2, 0, 0)}
  geometry={new THREE.SphereGeometry(1, 16, 16)}
  material={new THREE.MeshBasicMaterial({ color: new THREE.Color('hotpink'), transparent: true })}
/>

✅用上面这个方式会导致重复创建的问题,更好的方式是一种声明式的方式来创建元素:

<mesh visible userData={{ hello: 'world' }} position={[1, 2, 3]} rotation={[Math.PI / 2, 0, 0]}>
  <sphereGeometry args={[1, 16, 16]} />
  <meshStandardMaterial color="hotpink" transparent />
</mesh>

Constructor 构造参数

Threejs中的对象一般都是需要进行实例化的类型。这些类型通常都是在实例化时传入参数(new THREE.SphereGeometry(1, 32)),或者通过实例的属性来进行设置(someObject.visible = true)。

在React Three Fiber中,这些实例化时传入的参数一般都通过 args属性来穿日。如果args属性的后来数据发生了变化,那么相应的对象会被强制重新构建。

<sphereGeometry args={[1, 32]} />

Shortcuts 简化写法

Set

所有具有 .set() 方法的类型实例的属性在通过JSX语法进行创建时,都可以直接接收与 set 方法相同的参数。例如,THREE.Color.set 可以接收颜色字符串,因此可以只写 color="hotpink",而不是 color={new THREE.Color('hotpink')}。一些 set 方法接受多个参数,例如 THREE.Vector3,在这种情况下,您可以给它一个数组,如 position={[100, 0, 0]}。

<mesh position={[1, 2, 3]} />
  <meshStandardMaterial color="hotpink" />

如果将现有对象链接到属性,例如将 THREE.Vector3() 链接到position属性,则请注意,这通常会复制对象,因为它会在目标上调用 .copy()。这仅适用于同时拥有 .set() 和 .copy()(向量、欧拉角、矩阵等)的对象。另一方面,如果将现有材质或几何体链接起来,则会进行覆盖,因为这些对象没有 .set() 方法。

SetScalar

某个属性如果有setScalar方法(比如说Vector3),那么可以这样写:

// Translates to <mesh scale={[1, 1, 1]} />
<mesh scale={1} />

Piercing into nested properties 穿透设置

如果你想要设置被嵌套的属性(比如:mesh.rotation.x),可以使用短横线的写法:

<mesh rotation-x={1} material-uniforms-resolution-value={[512, 512]} />

Dealing with non-scene objects 处理非场景对象

你也可以将非Object3D元素的一些原始数据(几何体、材料等)放入渲染树中。它们使用方式与一般的属性和构造函数参数是一样的。

你可能会想知道为什么在一个普通的Three.js应用程序中,要将不属于场景的东西放入“场景”中。这和声明任何对象的原因相同:它变得易于管理,响应和自动释放。这些对象在技术上不属于场景,但它们“附加Attach”到一个属于场景的父对象上。

Attach

使用attach属性可以把数据绑定到他们的父级上。如果你卸载了某个绑定的数据,那么它会自动从父元素上解除掉绑定。

下面的这代码就是把一个材质数据斌定到了个mesh元素的material属性上,把一个geometry数据绑定到了geometry属性上。

<mesh>
  <meshBasicMaterial attach="material">
  <boxGeometry attach="geometry">

任何扩展自THREE.Material类型的对象自动会被设置attach=“material”属性,所有扩展自THREE.BufferGeometry 类型的对象都会自动设置attach="geometry"。你不需要手动进行设置。

<mesh>
  <meshBasicMaterial />
  <boxGeometry />

你可以使用穿透的方式来给内部的属性赋值。下面这个代码就是使用了buffer-attribute来给geometry.attributes.position设置数据,然后把buffer geometry设置给到了mesh.geometry上。

<mesh>
  <bufferGeometry>
    <bufferAttribute attach="attributes-position" count={v.length / 3} array={v} itemSize={3} />

更多的示例

// Attach bar to foo.a
<foo>
  <bar attach="a" />

// Attach bar to foo.a.b and foo.a.b.c (nested object attach)
<foo>
  <bar attach="a-b" />
  <bar attach="a-b-c" />

// Attach bar to foo.a[0] and foo.a[1] (array attach is just object attach)
<foo>
  <bar attach="a-0" />
  <bar attach="a-1" />

// Attach bar to foo via explicit add/remove functions
<foo>
  <bar attach={(parent, self) => {
    parent.add(self)
    return () => parent.remove(self)
  }} />

// The same as a one liner
<foo>
  <bar attach={(parent, self) => (parent.add(self), () => parent.remove(self))} />

真实使用示例

- <directionalLight
-   castShadow
-   position={[2.5, 8, 5]}
-   shadow-mapSize={[1024, 1024]}
-   shadow-camera-far={50}
-   shadow-camera-left={-10}
-   shadow-camera-right={10}
-   shadow-camera-top={10}
-   shadow-camera-bottom={-10}
- />
+ <directionalLight castShadow position={[2.5, 8, 5]} shadow-mapSize={[1024, 1024]}>
+   <orthographicCamera attach="shadow-camera" args={[-10, 10, 10, -10]} />
+ </directionalLight>

赋值给一个嵌套的属性,比如一个shadow-camera

数组会有一个明确的顺序,比如多个材质:

<mesh>
  {colors.map((color, index) => <meshBasicMaterial key={index} attach={`material-${index}`} color={color} />}
</mesh>

Putting already existing objects into the scenegraph 把已存在的对象放到场景数据中

你可以使用primitive来进行处理。你还可以配合使用各种属性或者attach来处理。不要重复多次添加统一个元素,在Threejs中这是不允许的。Primitives 不会在被卸载的时候自动销毁相关的元素,需要你自己去手动处理。

const mesh = new THREE.Mesh(geometry, material)

function Component() {
  return <primitive object={mesh} position={[10, 0, 0]} />

Using 3rd-party objects declaratively 用声明式写法使用第三方元素

extend 函数扩展了 React Three Fiber JSX 元素的目录。通过这种方式添加的组件可以使用驼峰式命名法在场景图中进行使用,就像其他primitives一样。

import { extend } from '@react-three/fiber'
import { OrbitControls, TransformControls } from 'three-stdlib'
extend({ OrbitControls, TransformControls })

// ...
return (
  <>
    <orbitControls />
    <transformControls />

如果你使用TypeScript,你同时也需要去extend JSX的命名空间。

Disposal 处理(销毁)

const globalGeometry = new THREE.BoxGeometry()
const globalMaterial = new THREE.MeshBasicMaterial()

function Mesh() {
  return (
    <group dispose={null}>
      <mesh geometry={globalGeometry} material={globalMaterial} />

在 three.js 中释放资源是一个手动的繁琐过程,但 React 知道对象的生命周期,因此 React Three Fiber 将尝试通过调用所有未安装对象上的 object.dispose()(如果存在)来为释放资源。

如果你是自己手动管理全局或缓存中的资产,则可能不是你想要的。您可以通过在网格、材质等上放置 dispose={null} 来关闭它,甚至可以在像组这样的父容器上放置它,这对整个节点树都是有效的。