在使用react-three-fiber之前你必须知道的那些事

469 阅读3分钟

通过使用three和react-three-fiber的demo实现一样的功能来探究react-three-fiber究竟为我们做了什么事情

让我们先来实现一个最简单的正方体

three版本

import {
    BoxGeometry,
    Mesh,
    MeshStandardMaterial,
    PerspectiveCamera,
    Scene,
    WebGLRenderer,
  } from "three";
  
  // Create "Canvas"
  const scene = new Scene();
  const camera = new PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );

  camera.position.z = 5;

  const cube = new Mesh();
  const geometry = new BoxGeometry();
  const material = new MeshStandardMaterial();
  cube.geometry = geometry;
  cube.material = material;

  scene.add(cube)

  // Render
  const renderer = new WebGLRenderer({ alpha: 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();
  

react-three-fiber:

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

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

以上两个版本代码都能实现一样的功能:一个黑色正方体

在threejs中,我们创建了一个scene,并且在我们的animation中使用renderer进行渲染。这些操作,在react-three-fiber中,其实都交由来控制,也就是说,创建了scene,也创建了camera,animate&request animation的逻辑也一起由Canvas控制。

这些处理我理解都是为了将这些逻辑对我们隐藏起来,我们只需要直接使用Canvas,将我们的mesh放置在这里Canvas中,就能轻松实现我们想要的功能。

那如果我们需要自己控制camera的位置呢?其实也是可以的

使用

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

也可以从@react-three/drei 引入这个 组件来实现

说到@react-three/drei 和 @react-three/fiber

@react-three/fiber包含threejs相关的核心功能,@react-three/drei 包含更加高级的功能

我们可以将@react-three/fiber中的Canvas认为是 three中的scene,Canvas元素里面的元素都可以看成是scene.add(XXX)

也就是说我们在three中做的

  const cube = new Mesh();
  const geometry = new BoxGeometry();
  const material = new MeshStandardMaterial();
  cube.geometry = geometry;
  cube.material = material;

  scene.add(cube)

相当于

    <Canvas>  
      <mesh>
        XXX
      </mesh>
    </Canvas>

如果有多个mesh,在three中

const group = new Group();
group.add(XXX)

在react-three-fiber中可以使用,

<group>
  <mesh/>
  <mesh/>
</group>

和在react-three-fiber中作为Canvas的子元素,映射到three中,并不是以cube.add(xxx)这样子的方式,而是作为cube的属性geometry和material存在,那么这个在react-three-fiber中是如何实现的呢?

在react-three-fiber中,有一个叫attaching的关键概念,每个子元素如果没有attach的属性,那么都会以add执行,如果元素设置了attach属性,那么这个元素将会作为其父元素的属性

在react-three-fiber,为了方便,已经帮我们把Geometry和Material标签都加上了attach属性

总而言之,react-three-fiber做的事情,就是将three实现的东西,支持我们用react 标签的方式直接去使用

import {
  ACESFilmicToneMapping,
  AmbientLight,
  BoxGeometry,
  Mesh,
  MeshStandardMaterial,
  PerspectiveCamera,
  PointLight,
  Scene,
  sRGBEncoding,
  WebGLRenderer,
} from "three";

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;
  }

  dispose() {
    this.geometry.dispose();
  }
}

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

camera.position.z = 5;

// Add elements
const ambientLight = new AmbientLight();
scene.add(ambientLight);

const pointLight = new PointLight();

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

const cube = new Cube();
scene.add(cube);

// 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);

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

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

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

  cube.update();
}

animate();
import { PerspectiveCamera } from "@react-three/drei";
import { Canvas, useFrame } from "@react-three/fiber";
import { useRef } from "react";
import { Mesh } from "three";

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>
  );
}

export default function R3fDemo() {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10, 10, 10]} />
      <Cube />
    </Canvas>
  );
}