• R3F(React Three Fiber)基础篇


    之前一直在做ThreeJS方向,整理了两篇R3F(React Three Fiber)的文档,这是基础篇,如果您的业务场景需要使用R3F,您又对R3F不太了解,或者不想使用R3F全英文文档,您可以参考一下这篇,作为学习R3F的基础教程使用。

    经验篇:⬇️

    R3F(React Three Fiber)经验篇

    一、R3F基础

    一、React Three Fiber基础

    1. 基础场景搭建

    function App() {
    
      const cameraSettings = {
        fov: 1,
        zoom: 100,
        near: 0.1,
        far: 200,
        position: new Vector3(3, 2, 6)
      }
    
      return (
        <>
          <Canvas
            orthographic={true}
            camera={{ ...cameraSettings }}
          >
            <Experience />
          </Canvas>
        </>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    相机场景配置

    const cameraSettings = {
      // fov: 1,
      // zoom: 100,
      near: 0.1,
      far: 200,
      position: new Vector3(3, 2, 6)
    }
    
    <Canvas
      gl={ {
        antialias: true,
        toneMapping: ACESFilmicToneMapping,
        outputEncoding: LinearEncoding
      } }
      // orthographic={true}  // orthographic 会影响Environment组件
      camera={{ ...cameraSettings }}
      shadows={true}
      onCreated={created}
    >
      <Experience />
    </Canvas>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Experience.tsx

    import "@react-three/fiber";
    import { useRef } from "react";
    import { Group } from "three";
    import {extend, useFrame, useThree} from "@react-three/fiber";
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
    import CustomObject from "./CustomObject.tsx";
    
    extend({ OrbitControls })
    
    export default function Experience() {
    
    	const { camera, gl } = useThree();
    
    	const cubeRef = useRef<any>(null!);
    	const groupRef = useRef<Group>(null!);
    
    	useFrame(() => {
    		cubeRef.current.rotation.y += 0.01;
    	})
        
    	return <>
    
    		{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
    		{/* @ts-ignore */}
    		<orbitControls args={ [camera, gl.domElement] } />
    		<directionalLight position={ [ 1, 2, 3 ] } intensity={ 1.5 } />
    		<ambientLight intensity={ 0.5 } />
    
    		<group ref={groupRef}>
    			<mesh>
    				<sphereGeometry/>
    				<meshStandardMaterial color={'orange'}/>
    			</mesh>
    			<mesh ref={cubeRef} rotation-y={Math.PI * 0.25} position-x={2} scale={1.5}>
    				<boxGeometry/>
    				<meshStandardMaterial color={"mediumpurple"} wireframe={false}/>
    			</mesh>
    			<mesh position-y={-1} rotation-x={-Math.PI * 0.5} scale={10}>
    				<planeGeometry/>
    				<meshStandardMaterial color={"greenyellow"}/>
    			</mesh>
    		</group>
    	</>
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    2. 自定义Geometry

    import { DoubleSide, BufferGeometry } from "three";
    import {useEffect, useMemo, useRef} from "react";
    
    export default function CustomObject() {
    
    	const geometryRef = useRef<BufferGeometry>(null!);
    
    	const verticesCount = 10 * 3;
    	
    	const positions = useMemo(() => {
    		const positions = new Float32Array(verticesCount * 3);
    		for (let i = 0; i < verticesCount; i++) {
    			positions[i] = (Math.random() - 0.5) * 3;
    		}
    		
    		return positions;
    	}, [verticesCount]);
    
    	useEffect(() => {
    		geometryRef.current.computeVertexNormals();
    	}, []);
    
    	return <mesh>
    		<bufferGeometry ref={geometryRef}>
    			<bufferAttribute
    				attach={"attributes-position"}
    				count={verticesCount}
    				itemSize={3}
    				array={positions}
    			/>
    		</bufferGeometry>
    		<meshBasicMaterial color={"red"} side={ DoubleSide } />
    	</mesh>
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    3. useFrame

    useFrame((state, delta) => {
    
      // delta 一直是 1.33
      console.log(delta);
      // 开始渲染时间
      console.log(state.clock.getElapsedTime());
      console.log(state.clock.elapsedTime);
    
      cubeRef.current.rotation.y += delta;
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    摄像机周期运动

    useFrame((state, delta) => {
        const angle = state.clock.elapsedTime;
        state.camera.position.x = Math.sin(angle);
        state.camera.position.z = Math.cos(angle);
        state.camera.lookAt(0, 0, 0);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    让Canvas绘制得更好的一些效果

    <Canvas
        gl={ {
        	antialias: true // 抗锯齿
        } }
        orthographic={true} // 效果不详
        camera={{ ...cameraSettings }}
    >
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. toneMapping (色调映射)

    https://threejs.org/docs/#api/en/constants/Renderer

    CineonToneMapping,ACESFilmicToneMapping(HDR

    <Canvas
        gl={ {
        	antialias: true,
        	toneMapping: ACESFilmicToneMapping
        } }
    	orthographic={true}
    	camera={{ ...cameraSettings }}
    >
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二、@react-three/drei

    1. OrbitControls

    自由旋转镜头组件

    import {extend, useFrame, useThree} from "@react-three/fiber";
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
    
    extend({ OrbitControls })
    
    const { camera, gl } = useThree();
    <orbitControls args={ [camera, gl.domElement] } />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    import {OrbitControls} from "@react-three/drei";
    <OrbitControls />
    
    • 1
    • 2

    2. TransformControls

    物体Transform组件(会在物体中心多出一个坐标系)

    import { TransformControls } from "@react-three/drei";
    <TransformControls>
        <mesh ref={cubeRef} rotation-y={Math.PI * 0.25} position-x={2} scale={1.5}>
        	<boxGeometry/>
        	<meshStandardMaterial color={"mediumpurple"} wireframe={false}/>
        </mesh>
    </TransformControls>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    另一种写法:

    <TransformControls object={ cubeRef } />
    
    • 1

    注意

    同时使用OrbitControls和TransformControls,OrbitControls要给makeDefault属性。

    <OrbitControls makeDefault={true}/>
    
    • 1

    Props

    TransformControlsProps:
    	mode?: 'translate' | 'rotate' | 'scale';
    
    • 1
    • 2

    3. PivotControls

    效果类似TransformControls,但是好像比它好用。

    <PivotControls anchor={[2, 0, 0]} depthTest={false}>
    
    • 1

    Props

    PivotControlsProps
    	scale?: number;
    	lineWidth?: number;
    	rotation?: [number, number, number];
    	axisColors?: [string | number, string | number, string | number];
    	anchor?: [number, number, number];
    	depthTest?: boolean;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. Html

    创建3D字体(HTML标签)

    <mesh position-x={-2}>
        <sphereGeometry/>
        <meshStandardMaterial color={'orange'}/>
        <Html
            wrapperClass={ 'label' } // label > div { color: white }
            position={ [1, 1, 0] }
            center
            distanceFactor={ 0.01 } // 越大,字体越大
            occlude={ [ cubeRef ] } // 文字遮挡效果
        >
        	This is sphere.
        </Html>
    </mesh>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Generating a 3D text geometry has its limits

    1. We can notice the polygons
    2. Takes a lot of CPU resources
    3. Some fonts won’t look very good
    4. Doesn’t support line breaks

    5. Text

    一个更好的,性能开销更少的文字组件,但是不支持occlude。

    <Text
    	font={ '' }
        fontSize={ 1 }
        color={'salmon'}
        position-y={ 2 }
        maxWidth={ 3 }
        textAlign={ 'center' }
    >
    	I Love R3F
    </Text>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6. Float

    让一个物体 飘来飘去

    <Float speed={4}>
        <Text
            font={''}
            fontSize={ 1 }
            color={'salmon'}
            position-y={ 2 }
            maxWidth={ 3 }
            textAlign={ 'center' }
        >
        I Love R3F
        </Text>
    </Float>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    7. 镜面反射材质

    注意:仅可用于平面

    <mesh rotation={[-Math.PI / 2, 0, 0]} position={[-10, 0, 25]}>
        <planeGeometry args={[250, 250]} />
        <MeshReflectorMaterial
            blur={[300, 100]}
            resolution={2048}
            mixBlur={1}
            mixStrength={80}
            roughness={1}
            depthScale={1.2}
            minDepthThreshold={0.4}
            maxDepthThreshold={1.4}
            color="#050505"
            metalness={0.5}
            mirror={0}
        />
    </mesh>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    三、Debugger

    1. leva

    useControls

    import { useControls, button } from 'leva';
    const {
    	position: ct_position,
    	color: ct_color,
    	visible: ct_visible,
    } = useControls('sphere', {
    	position: {
    		value: { x: -2, y: 0 },
    		step: 0.01,
    		joystick: 'invertY'
    	},
    	color: '#ff0000',
    	visible: true,
    	myInterval: {
    		min: 0,
    		max: 10,
    		value: [ 4, 5 ]
    	},
    	choice: { options: ['a', 'b', 'c'] },
    	clickMe: button(() => console.log('ok'))
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2. r3f-perf

    import { Perf } from "r3f-perf";
    <Perf position={'top-left'}/>
    
    • 1
    • 2

    3. useHelper

    可以展示光线的路径

    import { useHelper, } from "@react-three/drei";
    import { DirectionalLightHelper, DirectionalLight } from "three";
    
    const directionalLight = useRef<DirectionalLight>(null!);
    useHelper(directionalLight, DirectionalLightHelper);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、Environment

    1. 设置背景颜色

    1. 通过 color 标签
    <Canvas
      gl={ {
        antialias: true,
        toneMapping: ACESFilmicToneMapping,
        outputEncoding: LinearEncoding
      } }
      orthographic={true}
      camera={{ ...cameraSettings }}
      shadows={true}
    >
      <color args={ ['#ff0000'] } attach={"background"} />
      <Experience />
    </Canvas>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 通过 onCreated 钩子函数
    const created = (state: RootState) => {
      console.log('canvas created! ');
      const { gl, scene } = state;
      gl.setClearColor('#ff0000', 1);
      scene.background = new Color('red');
    }
    
    <Canvas
      gl={ {
        antialias: true,
        toneMapping: ACESFilmicToneMapping,
        outputEncoding: LinearEncoding
      } }
      orthographic={true}
      camera={{ ...cameraSettings }}
      shadows={true}
      onCreated={created}
    >
      <Experience />
    </Canvas>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. 通过CSS样式
    *, html, body {
      padding: 0;
      margin: 0;
    }
    
    html,
    body,
    #root {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. 配置阴影

    1. 阴影参数
    {/*shadow-mapSize:阴影精度,越大精度越高*/}
    {/*shadow-camera-top,right,bottom,left 阴影是否柔和,越大阴影越柔和*/}
    <directionalLight
      ref={ directionalLight }
      position={ [ 1, 2, 3 ] }
      intensity={ 1.5 }
      castShadow={ true }
      shadow-mapSize={ [1024 * 4, 1024 * 4] }
      shadow-camera-near={ 1 }
      shadow-camera-far={ 10 }
      shadow-camera-top={ 200 }
      shadow-camera-right={ 200 }
      shadow-camera-bottom={ - 200 }
      shadow-camera-left={ - 200 }
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 阴影烘焙,在适当的场景下 添加BakeShadows组件
    <BakeShadows />
    
    • 1
    1. 柔和阴影SoftShadows
    <SoftShadows
      size={100}
      focus={0}
      samples={10}
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 累积阴影AccumulativeShadows
    <AccumulativeShadows
      position={ [ 0, - 0.99, 0 ] }
      scale={ 10 }
      color={ '#316d39' }
      opacity={ 0.8 }
      // frames={ Infinity }
      // temporal={ true }
      // blend={ 100 }
    >
      <RandomizedLight
        amount={ 8 }
        radius={ 1 }
        ambient={ 0.5 }
        intensity={ 1 }
        position={ [ 1, 2, 3 ] }
        bias={ 0.001 }
      />
    </AccumulativeShadows>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 接触阴影ContactShadows
    const {
      position: cs_position,
      color: cs_color,
      opacity: cs_opacity,
      blur: cs_blur,
    } = useControls('ContactShadows', {
      position: { value: { x: 0, y: - 0.99 }, step: 0.01, joystick: 'invertY' },
      color: '#000000',
      opacity: { value: 0.5, min: 0, max: 1 },
      blur: { value: 1, min: 0, max: 10 },
      clickMe: button(() => console.log('ok'))
    })
    
    <ContactShadows
      position={ [cs_position.x, cs_position.y, 0] }
      scale={ 10 }
      resolution={ 128 }
      far={ 5 }
      color={ cs_color }
      blur={ cs_blur }
      opacity={ cs_opacity }
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3. 天空盒

    <Sky distance={20} />
    
    • 1

    4. 场景HDR文件

    HDR文件下载:https://polyhaven.com/

    <Environment
    	background
    	files={ '/industrial.hdr' }
        // ground={{
    	//   radius: 1,
    	//   scale: 100,
    	//   height: 0
    	// }}
        // preset="apartment" 预设场景
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过suspend-react,可将Environment转为异步组件,支持Suspense的方式调用

    import { suspend } from 'suspend-react'
    const city = import('@pmndrs/assets/hdri/city.exr').then((module) => module.default)
    
    <Environment files={suspend(city)} />
    
    • 1
    • 2
    • 3
    • 4

    四、Load Models

    1. useLoader

    import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js";
    
    const modelCyberpunk = useLoader(GLTFLoader, './cyberpunk.glb');
    
    • 1
    • 2
    • 3

    自定义加载器

    https://threejs.org/docs/#examples/en/loaders/DRACOLoader

    Draco是一个用于压缩和解压缩 3D 网格和点云的开源库。压缩后的几何图形可以明显更小,但代价是客户端设备上需要额外的解码时间。

    const modelCyberpunk = useLoader(GLTFLoader, './cyberpunk.glb', loader => {
    	const dracoLoader = new DRACOLoader()
    	dracoLoader.setDecoderPath('./draco/')
    	loader.setDRACOLoader(dracoLoader);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. Suspense

    <Suspense fallback={<PlaceHolder scale={5}/>}>
    	<ModelCyberpunk />
    </Suspense>
    
    • 1
    • 2
    • 3

    PlaceHolder.tsx

    const PlaceHolder = (props: MeshProps) => {
    	return <mesh {...props}>
    		<boxGeometry />
    		<meshStandardMaterial wireframe={true} color={ 'red' } />
    	</mesh>
    }
    
    export default PlaceHolder;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3. useGLTF

    Secondary Encapsulation of useLoadler(useLoadler的二次封装)

    export declare function useGLTF<T extends string | string[]>(path: T, useDraco?: boolean | string, useMeshOpt?: boolean, extendLoader?: (loader: GLTFLoader) => void): T extends any[] ? import("three-stdlib").GLTF[] : import("three-stdlib").GLTF;
    
    • 1

    可选配置:useDraco,useMeshOpt,extendLoader

    Extend the useGLTF return value type(对useGLTF 的返回值类型进行拓展)

    declare type GLTFEnhance = import('three-stdlib').GLTF & {
    	nodes: Record<string, import("three").Mesh>;
    	materials: Record<string, import("three").MeshStandardMaterial>;
    };
    
    const { nodes, materials } = useGLTF('/cyberpunk.glb', true) as C_GLTF;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    GLTF 预加载(preload)

    // Remember to write outside the component.
    useGLTF.preload('/cyberpunk.glb')
    
    • 1
    • 2

    4. GLTF Clone(模型克隆)

    Object3D.clone()

    /**
      * Returns a clone of `this` object and optionally all descendants.
      * @param recursive If true, descendants of the object are also cloned. Default `true`
      *
      * clone(recursive?: boolean): this;
      */
    <primitive object={scene.clone()}></primitive>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Clone Component

    https://github.com/pmndrs/drei#clone

    Declarative abstraction around THREE.Object3D.clone. This is useful when you want to create a shallow copy of an existing fragment (and Object3D, Groups, etc) into your scene, for instance a group from a loaded GLTF. This clone is now re-usable, but it will still refer to the original geometries and materials.

    import {Clone} from "@react-three/drei";
    
    <group {...props} dispose={null}>
        <Clone object={scene.clone()} position-x={ -10 }></Clone>
        <Clone object={scene.clone()} position-x={ 0 }></Clone>
        <Clone object={scene.clone()} position-x={ 10 }></Clone>
    </group>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5. GLTF Animation

    const { animations: gAnimations, scene} = useGLTF('./dog.glb', true) as GLTFEnhance;
    const { actions } = useAnimations(gAnimations, scene);
    
    useEffect(() => {
      const play_dead =  actions["0|play_dead_0"]!
      const rollover =  actions["0|rollover_0"]!
      const shake =  actions["0|shake_0"]!
      const sitting =  actions["0|sitting_0"]!
      const standing =  actions["0|standing_0"]!
    
    
      shake.play();
    
      window.setTimeout(() => {
        rollover.play();
        rollover.crossFadeFrom(
          shake, 1, false
        )
      }, 10000)
    
    }, []);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    useController

    const { 
      animations: gAnimations, 
      scene
    } = useGLTF('./dog.glb', true) as GLTFEnhance;
    
    const { actions, names } = useAnimations(gAnimations, scene);
    const { animationName } = useControls({ animationName: { options: names } })
    
    useEffect(() => {
      const action = actions[animationName]!
      action.fadeIn(0.5).play()
      return () => { 
        action.fadeOut(0.5) 
      }
    }, [animationName]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    6. Text3D

    Documentation:https://github.com/pmndrs/drei#text3d

    Example:

    const [ matcapTexture ] = useMatcapTexture('3E2335_D36A1B_8E4A2E_2842A5', 256);
    
    <Text3D
      font={'./Regular.json'}
      size={ 0.75 }
      height={ 0.2 }
      curveSegments={ 12 }
      bevelEnabled={ true }
      bevelThickness={ 0.02 }
      bevelSize={ 0.02 }
      bevelOffset={ 0 }
      bevelSegments={ 5 }
    >
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    The purpose of bevel-ralated properties is to make the font smoother.

    bevel 的作用是让字体变得更加圆滑。

    font属性需要填写一个被称作 typeface.json的字体文件,可在这个网站 https://gero3.github.io/facetype.js/ 将原始的ttf文件经过转化后得到。

    The font props requires filling in a font file called typeface.json,which can be obtained by converting the orignal ttf file on https://gero3.github.io/facetype.js website.

    7. useMatcapTexture

    https://github.com/pmndrs/drei#usematcaptexture

    The built in Texture can use in testing, not in the production environment.

    内置的Texture,可用于测试,别使用在生产环境。

    /**
      * The name of seconds parameters is format, we can choose between 64, 128, 256, 512, 1024
      * In our case, 256 is more than enough and you should try to use the smallest possible size   for performance reasons.
      */
    const [ matcapTexture ] = useMatcapTexture('3E2335_D36A1B_8E4A2E_2842A5', 256);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    8. Multiple model processing

    You shoud write the geometry, materal in mesh property when repeatedly rendering a model of the same geometry with the same materal. It performance better this way.

    {
    	[...Array(100)].map((_, index) =>
    		<mesh
    			key={new Date().toString() + index}
    			position={[
    				(Math.random() - 0.5) * 10,
    				(Math.random() - 0.5) * 10,
    				(Math.random() - 0.5) * 10,
    			]}
    			scale={0.2 + Math.random() * 0.2}
    			rotation={[
    				Math.random() + Math.PI,
    				Math.random() + Math.PI,
    				Math.random() + Math.PI,
    			]}
    			geometry={torusGeometry}
    			material={material}
    		>
    			<torusGeometry args={[1, 0.6, 16, 32]} />
    			<meshMatcapMaterial matcap={matcapTexture}/>
    		</mesh>
    	)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    ↓↓↓

    const Text3DHello: FC = memo(() => {
    	
    	const [matcapTexture] = useMatcapTexture('3E2335_D36A1B_8E4A2E_2842A5', 256);
    
    	const [torusGeometry, setTorusGeometry] = useState<TorusGeometry>();
    	const [material, setMaterial] = useState<MeshMatcapMaterial>();
    
    	return <>
    		<torusGeometry ref={(torusGeometry) => setTorusGeometry(torusGeometry!)} args={[1, 0.6, 16, 32]} />
    		<meshMatcapMaterial ref={(material) => setMaterial(material!) } matcap={matcapTexture}/>
    
    		<Center>
    			<Text3D
    				font={'./Regular.json'}
    				size={0.75}
    				height={0.2}
    				curveSegments={12}
    				bevelEnabled={true}
    				bevelThickness={0.02}
    				bevelSize={0.02}
    				bevelOffset={0}
    				bevelSegments={5}
    			>
    				你好,React Three Fiber !
    				<meshMatcapMaterial matcap={matcapTexture}/>
    			</Text3D>
    		</Center>
    		{
    			[...Array(100)].map((_, index) =>
    				<mesh
    					key={new Date().toString() + index}
    					position={[
    						(Math.random() - 0.5) * 10,
    						(Math.random() - 0.5) * 10,
    						(Math.random() - 0.5) * 10,
    					]}
    					scale={0.2 + Math.random() * 0.2}
    					rotation={[
    						Math.random() + Math.PI,
    						Math.random() + Math.PI,
    						Math.random() + Math.PI,
    					]}
    					geometry={torusGeometry}
    					material={material}
    				/>
    			)
    		}
    	</>
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    The better approach is to use OOP.

    import {FC, memo, useEffect} from "react";
    import {Center, Text3D, useMatcapTexture} from "@react-three/drei";
    import {MeshMatcapMaterial, TorusGeometry} from "three";
    
    const torusGeometry = new TorusGeometry(1, 0.6, 16, 32);
    const material = new MeshMatcapMaterial();
    
    const Text3DHello: FC = memo(() => {
    	
    	const [matcapTexture] = useMatcapTexture('3E2335_D36A1B_8E4A2E_2842A5', 256);
    
    	useEffect(() => {
            matcapTexture.needsUpdate = true;
    		material.matcap = matcapTexture;
    		material.needsUpdate = true;
    	}, [matcapTexture]);
    
    	return <>
    
    		<Center>
    			<Text3D
    				font={'./Regular.json'}
    				size={0.75}
    				height={0.2}
    				curveSegments={12}
    				bevelEnabled={true}
    				bevelThickness={0.02}
    				bevelSize={0.02}
    				bevelOffset={0}
    				bevelSegments={5}
    			>
    				你好,React Three Fiber !
    				<meshMatcapMaterial matcap={matcapTexture}/>
    			</Text3D>
    		</Center>
    		{
    			[...Array(100)].map((_, index) =>
    				<mesh
    					key={new Date().toString() + index}
    					position={[
    						(Math.random() - 0.5) * 10,
    						(Math.random() - 0.5) * 10,
    						(Math.random() - 0.5) * 10,
    					]}
    					scale={0.2 + Math.random() * 0.2}
    					rotation={[
    						Math.random() + Math.PI,
    						Math.random() + Math.PI,
    						Math.random() + Math.PI,
    					]}
    					geometry={torusGeometry}
    					material={material}
    				/>
    			)
    		}
    	</>
    });
    
    export default Text3DHello;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    Use useFrame and useRef to add animation.

    const donuts = useRef<Mesh[]>([]);
    
    useFrame((_, delta) => {
    	for (const donut of donuts.current) donut.rotation.y += delta * 0.5
    })
    
    // ...
    <mesh ref={(mesh) => { donuts.current[index] = mesh! }}
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    or use group tag**(not recommanded)**

    const donutsGroup = useRef<Group>(null!);
    
    useFrame((_, delta) => {
    	for (const donut of donutsGroup.current.children) donut.rotation.y += delta * 0.1
    }
    
    <group ref={ donutsGroup }>
    	{ [...Array(100)].map((_, index) => <mesh 
    	// ... }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    五、Mouse Event

    1. EventHandler

    const eventHandler = (event: ThreeEvent<MouseEvent>) => {
    	console.log('event.uv', event.distance) // distance between camera and hit point.
    	console.log('event.uv', event.uv)
    	console.log('event.point', event.point) // Hit point coordinates (坐标).
    	console.log('event.object', event.object)
    	console.log('event.eventObject', event.eventObject) // Usually, eventObject is the same as object
    	console.log('event.x', event.x) // 2D Screen coordinates of the pointer
    	console.log('event.y', event.y)
    	console.log('event.shiftKey', event.shiftKey)
    	console.log('event.ctrlKey', event.ctrlKey)
    	console.log('event.metaKey', event.metaKey) // Click while holding down command / win key.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. Event Kind

    • onClick

      • CLICK or CLICK with CTRL、SHIFT、COMMAND(WIN)、ALT
      • shiftKey,ctrlKey,metaKey,altKey
    • onContextMenu

      • RIGHT CLICK or CTRL + LEFT CLICK.
      • On a mobile, by pressing down for some time.
    • onDoubleClick

      • It works bisically the same as onClick.
      • The delay between the first and second click/tap is defined by the OS
    • onPointerUp

    • onPointerDown

    • onPointerOver and onPointerEnter

      • When the cursor or finger just went above the object
    • onPointerMove

    • onPointerMissed

      • When the user clicks outside of the object. ( Cannot get the event.object parameter ).

    六、Post Processing

    1. Install

    We need tow dependencies,@react-three/postprocessing,postprocesssing, But for now, the only we neeed to install is @react-three/postprocessing since the dependency will also install postprocesssing.

    "@react-three/drei": "^9.85.1",
    "@react-three/fiber": "^8.14.2",
    "@react-three/postprocessing": "2.6",
    "postprocessing": "~6.31.2",
    "r3f-perf": "^7.1.2",
    "three": "~0.151.0",
    "three-stdlib": "^2.27.0"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2. multisampling 多重采样

    The default value is 8.

    <EffectComposer multisampling={16} />
    
    • 1

    3. vignette 晕映

    The default effect is add a black mask around the sceen.

    <Vignette offset={0.3} darkness={0.9} />
    
    • 1

    You can specify the blending(混合、交融) method.

    import { BlendFunction } from "postprocessing";
    <Vignette
        offset={0.3}
        darkness={0.9}
        blendFunction={ BlendFunction.ALPHA }
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4. Glitch 失灵

    Create snowflake(雪花) glitch effect like an old-fashioned TV.

    <Glitch delay={ new Vector2(1, 1) } mode={ GlitchMode.SPORADIC } />
    
    • 1

    Delay attribute reviews a value of type Vector2.It represents the delay time for the horizontal and vertical axes.

    The same effect to other attributes.

    delay?: import("three").Vector2;
    duration?: import("three").Vector2;
    strength?: import("three").Vector2;
    
    • 1
    • 2
    • 3

    Effect Mode

    mode: typeof GlitchMode[keyof typeof GlitchMode];
    
    export enum GlitchMode {
    	DISABLED,
        SPORADIC,
        CONSTANT_MILD,
        CONSTANT_WILD,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5. Noise 噪点

    <Noise 
        blendFunction={ BlendFunction.SOFT_LIGHT } 
        premultiply  // effect overlay
    />
    
    • 1
    • 2
    • 3
    • 4

    BlendFunction

    BlendFunction.OVERLAY // 叠加
    BlendFunction.SCREEN  // It doesn't work well in bright scenes
    BlendFunction.SOFT_LIGHT
    BlendFunction.AVERAGE
    
    • 1
    • 2
    • 3
    • 4

    6. Bloom

    Bloom can be used to build an object glow(发光,同luminescence)effect

    1、Set material attriblue.

    Set larger value for color attriblue.

    <meshStandardMaterial 
    	color={ [ 1.5 * 30, 1 * 30, 4 * 30 ] } 
    	toneMapped={ false } 
    />
    
    • 1
    • 2
    • 3
    • 4

    Or set standard color, and set emissive attriblue and emissiveIntensity attibute.

    <meshStandardMaterial 
    	color={ 'white' } 
    	emissive={ 'yellow' } 
    	emissiveIntensity={ 10 } 
    	toneMapped={ false } 
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、Set Bloom effect component attriblue.

    <Bloom
    	mipmapBlur={ true } // always true
    	intensity={ 1 }
    	luminanceSmoothing={ 2 } // 滤波
    	luminanceThreshold={ 10 } // 阈值
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7. DepthOfField 景深

    <DepthOfField
        focusDistance={ 0.025 }
        focalLength={ 0.025 }
        bokehScale={ 6 }
    />
    
    • 1
    • 2
    • 3
    • 4
    • 5

    七、Physics

    1. Installation

    pnpm install @react-three/rapier
    
    • 1

    2. RigidBody:刚体

    • colliders:对撞机,设置刚体碰撞形状,ball 球,cuboid 矩形,hull Mesh的船体形状,trimesh Mesh网线形状

      export declare type RigidBodyAutoCollider = "ball" | "cuboid" | "hull" | "trimesh" | false;
      
      • 1

    Scene Example:

    
    
    	
    		
    			
    			
    		
    	
    
    	
    		
    			
    			
    		
    	
    
    	
    		
    			
    			
    		
    	
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    You can use CuboidCollider Component to add rigid body shape manually.

    
    	
    	
    	
    		
    		
    	
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    BallCollider, the ball shape of rigid bidy.

    <RigidBody colliders={false} position={[0, 1, 0]} rotation={[Math.PI / 2, 0, 0]}>
    	<BallCollider args={[1.5]} />
    	<mesh castShadow={true}>n
    		<torusGeometry args={[1, 0.5, 16, 32]} />
    		<meshStandardMaterial color={'mediumpurple'} />
    	</mesh>
    </RigidBody>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3. Controll rigidbody movement

    const cubeRigid = useRef<RapierRigidBody>(null!);
    
    const { camera } = useThree();
    
    const cubeJump = (event: ThreeEvent<MouseEvent>) => {
    	cubeRigid.current.applyImpulse({ x: 0, y: 2, z: 0 }, false)
    	cubeRigid.current.applyTorqueImpulse({ x: 0, y: 1, z: 0 }, false)
    
    	const { eventObject } = event;
    
    	// console.log(eventObject.position)
    
    	const [epx,epy,epz] = eventObject.position
    
    	camera.position.set(epx, epy - 4, epz + 4);
    	camera.rotation.set(0, 0, 0);
    }
    
    <Physics debug={true}>
    
    	<RigidBody
    		colliders={'cuboid'}
    		type={"dynamic"}
    		ref={cubeRigid}
    	>
    		<mesh
    			castShadow={true}
    			position={[0, 10, 0]}
    			onClick={cubeJump}
    		>
    			<boxGeometry/>
    			<meshStandardMaterial color={'orange'} />
    		</mesh>
    	</RigidBody>
    
    	<RigidBody type={"fixed"}>
    		<mesh receiveShadow={true} position={[0, 0, 0]} scale={1}>
    			<boxGeometry args={[10, 0.5, 10]}/>
    			<meshStandardMaterial color={"greenyellow"}/>
    		</mesh>
    	</RigidBody>
    
    </Physics>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    4. grvity 重力

    You can set the gravity size and direction.

    <Physics
      debug={true}
      gravity={[0, -1.6, 0]}
    >
    
    • 1
    • 2
    • 3
    • 4

    5. gravityScale,restitution,friction

    • gravityScale 重力倍率
    • restitution 恢复原状
    • friction 摩擦力(摩擦力是两个对象作用)
    <RigidBody
        colliders={'cuboid'}
        type={"dynamic"}
        ref={cubeRigid}
        gravityScale={ 1 }
        restitution={ 1 }
    >
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6. RigidBody mass 刚体重力

    const cubeRigid = useRef<RapierRigidBody>(null!);
    const cubeMesh = useRef<Mesh>(null!);
    const cubeJump = (_: ThreeEvent<MouseEvent>) => {
    	const mass = cubeRigid.current.mass();
    	cubeRigid.current.applyImpulse({ x: 0, y: 5 * mass, z: 0 }, false)
    	cubeRigid.current.applyTorqueImpulse({ x: 0, y: 1 * mass, z: 0 }, false)
    }
    return <Physics
    	debug={true}
    	gravity={[0, -8, 0]}
    >
        <RigidBody
            colliders={false}
            type={"dynamic"}
            ref={cubeRigid}
            gravityScale={ 1 }
            restitution={ 1 }
            friction={1}
        >
            <CuboidCollider
                args={[0.5, 0.5, 0.5]}
                position={[0, 10, 0]}
                mass={10}
            />
            <mesh
                ref={cubeMesh}
                castShadow={true}
                position={[0, 10, 0]}
                onClick={cubeJump}
            >
                <boxGeometry/>
                <meshStandardMaterial color={'orange'} />
            </mesh>
        </RigidBody>
    </Physics>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
  • 相关阅读:
    SOLIDWORKS 2023新产品发布会!带你领略SOLIDWORKS 2023新增功能!
    SpringBoot使用Swagger2创建API文档
    377. 组合总和 Ⅳ【完全背包】求排列数:外层for背包,内层for物品;求组合数:外层for物品,内层for背包;
    FreeRTOS任务管理
    Android系统服务DropBoxManagerService详解与实践应用
    计算机毕业设计JavaWeb美食网站设计(源码+系统+mysql数据库+lw文档)
    Java 中 Volatile 关键字
    学生个人网页设计作品:基于HTML+CSS+JavaScript实现摄影艺术网站 DIV布局简单的摄影主题网站
    Kimi Chat,不仅仅是聊天!深度剖析Kimi Chat 5大使用场景!
    【LeetCode刷题】-- 29.两数相除
  • 原文地址:https://blog.csdn.net/qq_36833171/article/details/136276945