前端3d—比较 Three.js 和 React Three Fiber

1,787 阅读3分钟

整理自油管博主📺:I wish I knew this before using React Three Fiber - YouTube

对比demo源码:rabbit-hole-syndrome/r3f-under-the-hood: Source code for "I wish I knew this before using React Three Fiber" 🎥 (github.com)

因为正在学习Three.js,但是发现油管上的大神都在用React Three Fiber(R3F)做3D,有些好奇这两个库的关系,于是上网找了将这两者作类比的资料,希望对你有帮助~

先说结论👉 React Three Fiber的核心库仍然采用了Three.js的概念和技术,旨在通过使用React语法来实现Three.js的功能,从而创建和管理3D场景。而且相较于Three.js,它具有易于使用的API和更好的性能。

场景、相机、渲染

Three.js

创建场景scene

照相机camera(fov,aspect,near,far)

渲染器renderer

逐帧渲染动画函数

// Create "Canvas"
const scene = new Scene();
const camera = new PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

camera.position.z = 5;

// Render
const renderer = new WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);

const container = document.getElementById("__next");

if (container) {
  container.appendChild(renderer.domElement);
}

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

animate();

R3F

只需从@react-three/fiber引入Canvas画布组件,可以视为scene,所有嵌套在canvas内的子组件都相当于添加到了scene中( scene.add() )

相机、逐帧渲染等React会默认帮我们处理

import { Canvas, useFrame } from "@react-three/fiber";

export default function R3fDemo() {
  return (
    <Canvas>
    </Canvas>
  );
}

如果想要自己管理相机,可以在Canvas组件上添加camera属性,属性值为配置对象

    <Canvas camera={{position: [0, 0, 5]}}></Canvas>

或者使用@react-three/drei中的组件PerspectiveCamera,通过属性设置参数

import { PerspectiveCamera } from "@react-three/drei";
<Canvas>
	<PerspectiveCamera fov={75} position={[0, 0, 5]}>
</Canvas>

对象

Three.js

const geometry = new BoxGeometry(2, 2, 2);
const material = new MeshStandardMaterial({color: "blue"});
const mesh = new Mesh(geometry, material)
scene.add(mesh);

R3F

元素的嵌套对应于Three.js的add()功能,除非他们具有attach属性

React中所有原始元素如div, h1都采用首字母小写。而Three.js中的原有的元素,如Mesh, BoxGeometry都被视为原始元素,采用首字母小写

使用args给原始元素传参

export default function R3fDemo() {
  return (
    <Canvas>
	<mesh>
            <boxGeometry attach="geometry" args={[2, 2, 2]}/>
	    <meshStandardMaterial attach="material" color="blue" />
	<mesh>
    </Canvas>
  );
}

动画

Three.js

class Cube extends Mesh {
  constructor() {
    super();

    const geometry = new BoxGeometry();
    const material = new MeshStandardMaterial();
    material.color.set("blue");

    this.geometry = geometry;
    this.material = material;
  }

  update() {
    this.rotation.x += 0.01;
    this.rotation.y += 0.01;
  }

	// 当模型不再使用时,释放geometry对象占用的内存
  dispose() {
    this.geometry.dispose();
  }
}

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);

  cube.update();
}

animate();

R3F

使用 useRef获取mesh元素,ref指向three,js中的实例mesh,修改其旋转状态

useFrame 的回调函数中更新场景中的物体,使其在每一帧上渲染。对应于Three.js中,在动画函数中调用requestAnimationFrame ,并在其中更新物体

我们不需要手动清除,React会自动回收被移除的模型

function Cube() {
  const meshRef = useRef<Mesh>(null);

  useFrame(() => {
    if (!meshRef.current) {
      return;
    }

    meshRef.current.rotation.x += 0.01;
    meshRef.current.rotation.y += 0.01;
  });

  return (
    <mesh ref={meshRef}>
      <PerspectiveCamera />
      <boxGeometry />
      <meshStandardMaterial color="blue" />
    </mesh>
  );
}

渲染表现

Three.js

为了提高three.js的渲染表现,我们通常会更改下面的属性:

默认关闭关闭抗锯齿效果,需要手动将antialias选项为改为true

three.js默认像素比为1,需要setPixelRatio来匹配用户浏览器支持的像素比

需要手动开启ACESFilmic色调映射

输出编码设置为sRGB编码

// Render
const renderer = new WebGLRenderer({ alpha: true, antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.toneMapping = ACESFilmicToneMapping;
renderer.outputEncoding = sRGBEncoding;
renderer.setSize(window.innerWidth, window.innerHeight);

R3F

R3F默认为我们开启了上面的渲染配置

当然我们也可以通过Canvas的gl属性,传入配置对象更改这些默认设置

<Canvas gl={{toneMapping: ACES}}></Canvas>

向量

Three.js

向量是一个有方向和大小的量,在3D模型中的作用非常重要,因为它们允许我们在三维空间中计算位置、方向和距离等信息,从而实现对3D模型的控制和操作。

在Three.js中,使用Vector3类来表示一个三维向量,其中x、y和z分别表示向量在三个坐标轴上的分量。

position是Vector3的实例,使用set方法设置xyz的分量值

const pointLight = new PointLight();

pointLight.position.set(10, 10, 10);
scene.add(pointLight);

R3F

R3F使用三元数组表示向量,实质是将三个值传给了set(x, y, z)

<Canvas>
  <pointLight position={[10, 10, 10]} />
</Canvas>