用threejs+rtf手写hello world的第二天

597 阅读3分钟

先上效果图

Aug-09-2024 15-27-48.gif

在很久以前,自学c4d的时候,见过一个类似时空隧道的一个案例,样子大概是这样

image.png

然后就突发奇想,做一个隧道加文字破碎效果的场景出来

第一步:初始化场景

初始化项目和必要依赖同第一天相同

app.tsx

import { Canvas } from "@react-three/fiber";
import "./App.css";
import Render from "./Render";

function App() {
  return (
    <>
      <Canvas>
        <Render />
      </Canvas>
    </>
  );
}

export default App;

render.tsx

const Render = () => {
  return (
    <>
      <mesh>
        <boxGeometry />
        <meshBasicMaterial color={'red'} />
      </mesh>
    </>
  );
};

export default Render;

如果上述代码正常的话,我们的页面会得到如下的显示结果

image.png

第二步:导入隧道模型和碎片模型

  1. 制作模型(暂时先不介绍,敢兴趣在单独开一个建模篇)
  2. 将隧道模型导入到项目中
    • 将ware.gltf文件放到项目的public目录下
    • 将studio_small_09_4k.exr环境贴图放到public目录下
    • 将隧道模型导入到场景
      render.tsx
      
      import { useGLTF } from "@react-three/drei"
      import { BackSide } from "three"
      
      const Ware = () => {
          const { nodes } = useGLTF('/ware.gltf');
          return (
             <mesh geometry={(nodes.yz).geometry}>
               <meshBasicMaterial color={'red'} side={BackSide} />
             </mesh>
          );
      };
      
      const Render = () => {
          return (
             <>
               <Ware />
             </>
          );
      };
      
      export default Render;
      
      

这个时候我们可以看到我们的模型已经成功到导入到场景中

image.png

  1. 给隧道添加物理材质MeshPhysicalMaterial
render.tsx

...
const Ware = () => {
  const { nodes } = useGLTF('/ware.gltf');
  
  return (
    <mesh geometry={(nodes.yz).geometry}>
      // 这里添加物理材质粗糙度和颜色可以自行调整
      // 由于物理材质需要灯光才可以被看见,此时我们的模型会呈现一片黑色
      <meshPhysicalMaterial color={'white'} side={BackSide} roughness={0.4} />
    </mesh>
  );
};
...

app.tsx

...
import { Environment } from "@react-three/drei";

function App() {
  return (
    <>
      <Canvas>
        <Render />
        // 添加环境光照
        <Environment
          files={'/studio_small_09_4k.exr'}
          environmentRotation={[Math.PI / 2, 0, 0]}
          environmentIntensity={0.12}
        ></Environment>
      </Canvas>
    </>
  );
}
...

这时我们的隧道会有如下的效果

image.png

4.将碎片模型导入到场景中并添加玻璃材质

render.tsx

const TextCom = () => {
  const { nodes } = useGLTF('/sp1.glb');

  return (
    <group>
      <mesh
        geometry={(nodes.sp).geometry}
        position={[0, 0, 16]}
        scale={0.8}
      >
        // 玻璃材质
        <MeshTransmissionMaterial
          backsideThickness={5}
          thickness={2}
          side={BackSide}
          reflectivity={1.52}
          envMapIntensity={1}
          ior={1.72}
          roughness={0}
        />
      </mesh>
      <mesh
        geometry={(nodes.text).geometry}
        scale={1}
        position={[0, 0, 16]}
      >
        // 玻璃材质
        <MeshTransmissionMaterial
          backsideThickness={5}
          thickness={2}
          side={BackSide}
          reflectivity={1.52}
          roughness={0.8}
        />
      </mesh>
      <mesh
        geometry={(nodes.text).geometry}
        scale={0.96}
        position={[0, 0, 16]}
      >
        // 红色材质
        <meshBasicMaterial color={'red'} />
      </mesh>
    </group>
  );
};

const Render = () => {
  return (
    <>
      <Ware />
      <TextCom />
    </>
  );
};
app.tsx

...
// 设置摄像机的位置
<Canvas camera={{ position: [0, 0, 120] }}>
    <Render />
    <Environment
      files={'/studio_small_09_4k.exr'}
      environmentRotation={[Math.PI / 2, 0, 0]}
      environmentIntensity={0.12}
    ></Environment>
</Canvas>
...

这个时候我们得到如下效果

image.png

5.现在的隧道看着比较灰暗,我们在隧道里面加上灯光

render.tsx

// 灯光颜色、位置和强度可以自己调节
const Light = () => {
  return (
    <>
      <pointLight
        distance={4000}
        intensity={50000}
        color={'white'}
        position={[0, 0, -250]}
      />
      <pointLight
        distance={4000}
        intensity={50000}
        color={'white'}
        position={[0, 0, -150]}
      />
      <pointLight
        distance={1000}
        intensity={50000}
        color={'orange'}
        position={[0, 0, -100]}
      />
      <pointLight
        distance={1000}
        intensity={40000}
        color={'red'}
        position={[0, 0, 0]}
      />
      <pointLight
        distance={1000}
        intensity={50000}
        color={'red'}
        position={[0, 0, 100]}
      />
    </>
  );
};

const Render = () => {
  return (
    <>
      <Ware />
      <TextCom />
      <Light />
    </>
  );
};

这个时候我们就可以得到下面的效果

image.png

6.最后一步就是给场景中添加交互和动画

render.tsx

import {
  useGLTF,
  MeshTransmissionMaterial,
  Float,
  useTexture
} from '@react-three/drei';
import { BackSide, Mesh } from 'three';
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { easing } from 'maath';
const Ware = () => {
  const wareRef = useRef<Mesh>(new Mesh());
  const { nodes } = useGLTF('/ware.gltf');

  useFrame(({ camera, pointer }, delta) => {
    // 让相机沿z轴向前运动
    if (camera.position.z > 50) {
      camera.position.z -= 0.02;
    }
    // 设置相机的视角,跟随鼠标的位置移动
    easing.damp3(
      camera.position,
      [-pointer.x, -pointer.y, camera.position.z],
      0.2,
      delta
    );
    // 相机始终看到原点
    camera.lookAt(0, 0, 0);
  });

  return (
    <mesh geometry={(nodes.yz as Mesh).geometry} ref={wareRef}>
      <meshPhysicalMaterial color={'white'} side={BackSide} roughness={0.4} />
    </mesh>
  );
};

const TextCom = () => {
  const { nodes } = useGLTF('/sp1.glb');
  const envMap = useTexture('/studio_small_09_4k.exr');

  const spRef = useRef<Mesh>(new Mesh());

  useFrame(() => {
    // 碎片沿z轴旋转
    spRef.current.rotation.z += 0.001;
  });

  return (
    <group>
      <mesh
        geometry={(nodes.sp as Mesh).geometry}
        position={[0, 0, 16]}
        scale={0.8}
        ref={spRef}
      >
        <MeshTransmissionMaterial
          backsideThickness={5}
          thickness={2}
          side={BackSide}
          reflectivity={1.52}
          envMap={envMap}
          envMapIntensity={1}
          ior={1.72}
          roughness={0}
        />
      </mesh>
      // 碎片上下摆动
      <Float speed={10} rotationIntensity={1} floatIntensity={4} scale={0.8}>
        <mesh
          geometry={(nodes.text as Mesh).geometry}
          scale={1}
          position={[0, 0, 16]}
        >
          <MeshTransmissionMaterial
            backsideThickness={5}
            thickness={2}
            side={BackSide}
            reflectivity={1.52}
            roughness={0.8}
          />
        </mesh>
        <mesh
          geometry={(nodes.text as Mesh).geometry}
          scale={0.96}
          position={[0, 0, 16]}
        >
          <meshBasicMaterial color={'red'} />
        </mesh>
      </Float>
    </group>
  );
};

const Light = () => {
  return (
    <>
      <pointLight
        distance={4000}
        intensity={50000}
        color={'white'}
        position={[0, 0, -250]}
      />
      <pointLight
        distance={4000}
        intensity={50000}
        color={'white'}
        position={[0, 0, -150]}
      />
      <pointLight
        distance={1000}
        intensity={50000}
        color={'orange'}
        position={[0, 0, -100]}
      />
      <pointLight
        distance={1000}
        intensity={40000}
        color={'red'}
        position={[0, 0, 0]}
      />
      <pointLight
        distance={1000}
        intensity={50000}
        color={'red'}
        position={[0, 0, 100]}
      />
    </>
  );
};

const Render = () => {
  return (
    <>
      <Ware />
      <TextCom />
      <Light />
    </>
  );
};

export default Render;

最后的效果就是这样啦(风格可以自己控制喔)

Aug-09-2024 15-27-48.gif