【可视化学习】51-了解react-fibre-three库
发表于:2023-10-27 |

前言

今天带大家了解一下react-fibre-three库,这是一个基于three,然后适配react开发3d的库,我觉得他可以简化我们的3d开发代码量,其实也没减少多少,哈哈,就给大家简单介绍一下好了,具体的大家看看github上的文档,如果是react技术栈想学3d的同学可以了解一下。

使用常规three+react开发3d

这里创建react项目的过程我省略了,学react的同学应该都会,这里直接上three结合react的简单开发过程,首先我们需要安装three

安装three

1
yarn add three

初始化样式

我们把App.css修改下

1
2
3
4
5
6
7
8
*{
margin: 0;
padding: 0;
}
canvas{
display: block;
position: fixed;
}

类组件使用three

这里的componentDidMount就相当于vue中onMounted的生命周期钩子,挂载完成的意思。

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
60
61
import "./App.css";
import React from "react";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

class App extends React.Component {
render() {
return <div className="App"></div>;
}
componentDidMount() {
// 1、创建场景
const scene = new THREE.Scene();

// 2、创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);

// 设置相机位置
camera.position.set(0, 0, 10);
scene.add(camera);

// 添加物体
// 创建几何体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 根据几何体和材质创建物体
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
// 将几何体添加到场景中
scene.add(cube);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);

// 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);

// 添加控制器
const controls = new OrbitControls(camera, renderer.domElement);

function render() {
controls.update();
// 渲染
renderer.render(scene, camera);
// 递归调用
requestAnimationFrame(render);
}
render();
}
}

export default App;

效果图

函数式组件使用three

这里我用了useEffect这个钩子,他相当于挂载完成,因为我们第二个参数是空的,如果是有的话,就还会在那个参数更新的时候执行这个方法,差不多是vue中的watch的意思。

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
import "./App.css";
import React from "react";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

function App() {
React.useEffect(() => {
// 1、创建场景
const scene = new THREE.Scene();

// 2、创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);

// 设置相机位置
camera.position.set(0, 0, 10);
scene.add(camera);

// 添加物体
// 创建几何体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
// 根据几何体和材质创建物体
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
// 将几何体添加到场景中
scene.add(cube);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// console.log(renderer);
// 将webgl渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement);

// 使用渲染器,通过相机将场景渲染进来
// renderer.render(scene, camera);

// 添加控制器
const controls = new OrbitControls(camera, renderer.domElement);

function render() {
controls.update();
// 渲染
renderer.render(scene, camera);
// 递归调用
requestAnimationFrame(render);
}
render();
}, []);
return <div className="App"></div>;
}

export default App;

这里实现出来的效果也是一样的,这里就不贴图了。接下来让我们使用react-fibre-three来实现一下three的开发。

使用react-fibre-three开发3d

安装react-fibre-three和react-three/drei

1
yarn add react-fibre-three react-three/drei

创建简单的立方体

这里需要将我们的3d代码使用canvas包裹,OrbitControls就是控制器,mesh里面就是写我们的物体的材质,是不是非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "./App.css";
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";

function App() {
return (
<div className="App">
<Canvas>
<OrbitControls />
<mesh>
<boxGeometry />
</mesh>
</Canvas>
</div>
);
}

export default App;

效果图

给物体打个光

这里我给这个立方体可以简单打个光,也非常简单,就是three中的属性都通过props的方法传递进去就行。

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
import "./App.css";
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";

function App() {
return (
<div className="App">
<Canvas>
<OrbitControls autoRotate={true} />
<mesh>
<boxGeometry />
<meshPhongMaterial />
</mesh>
<ambientLight args={[0xffffff]} intensity={0.5} />
<directionalLight
args={[0xffffff]}
position={[0, 5, 5]}
intensity={0.5}
/>
</Canvas>
</div>
);
}

export default App;

效果图

旋转物体

这里我们是旋转物体,不是上面的旋转控制器哦,这里需要用到useFrame这个钩子,这个钩子是每一帧都会执行的,我们可以在这里写一些动画,这里我就简单的旋转一下物体。

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
import "./App.css";
import { Canvas, useFrame } from "@react-three/fiber";
import { useRef} from "react";

function App() {
return (
<div className="App">
<Canvas>
<BoxRotation></BoxRotation>
<ambientLight args={[0xffffff]} intensity={0.5} />
<directionalLight
args={[0xffffff]}
position={[0, 5, 5]}
intensity={0.5}
/>
</Canvas>
</div>
);
}

function BoxRotation() {
let mesh = useRef();

useFrame(({ clock }) => {
console.log(clock.getElapsedTime());
mesh.current.rotation.x = clock.getElapsedTime();
mesh.current.rotation.y = clock.getElapsedTime();
});

return (
<mesh ref={mesh}>
<boxGeometry />
<meshPhongMaterial />
</mesh>
);
}

export default App;

使用useThree获取three信息

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
import "./App.css";
import { Canvas, useThree } from "@react-three/fiber";
import { OrbitControls} from "@react-three/drei";

function App() {
return (
<div className="App">
<Canvas>
<InfoThree></InfoThree>
<OrbitControls />
<ambientLight args={[0xffffff]} intensity={0.5} />
<directionalLight
args={[0xffffff]}
position={[0, 5, 5]}
intensity={0.5}
/>
</Canvas>
</div>
);
}


function InfoThree() {
const { camera, gl } = useThree();
const three = useThree();
console.log(camera, gl);
console.log(three);
return null;
}

export default App;

效果图

useLoader使用模型

在react-fibre-three中,我们使用useLoader这个hook来加载gltf的模型,这里我还配置了一个环境贴图,使用了Environment,通过background设置为背景,将相机的位置Canvas camera={{ position: [0, 2, 2] }}进行了调整。

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
import "./App.css";
import { Canvas, useThree, useLoader } from "@react-three/fiber";
import { OrbitControls, Environment } from "@react-three/drei";
import { Suspense } from "react";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";

function App() {
return (
<div className="App">
<Canvas camera={{ position: [0, 2, 2] }}>
<InfoThree></InfoThree>
<OrbitControls />
<ambientLight args={[0xffffff]} intensity={0.5} />
<directionalLight
args={[0xffffff]}
position={[0, 5, 5]}
intensity={0.5}
/>
<Suspense fallback={null}>
<Model></Model>
<Environment files="./assets/texture/024.hdr" background />
</Suspense>
</Canvas>
</div>
);
}

function Model() {
const gltf = useLoader(GLTFLoader, "./assets/model/pad.gltf");
return <primitive object={gltf.scene} scale={5} />;
}

function InfoThree() {
const { camera, gl } = useThree();
const three = useThree();
console.log(camera, gl);
console.log(three);
return null;
}

export default App;

纹理材质的使用

搞到这里大家应该也都能看懂了,就是使用了标准材质,配了那些贴图

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
60
61
62
63
import "./App.css";
import { Canvas, useThree, useLoader } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import { Suspense } from "react";
import * as THREE from "three";

function App() {

return (
<div className="App">
<Canvas camera={{ position: [0, 4, 4] }}>
<InfoThree></InfoThree>
<OrbitControls />
<ambientLight args={[0xffffff]} intensity={0.5} />
<directionalLight
args={[0xffffff]}
position={[0, 5, 5]}
intensity={0.5}
/>
<Suspense fallback={null}>
<Model></Model>
</Suspense>
</Canvas>
</div>
);
}

function Model() {
const [colorMap, displacementMap, normalMap, roughnessMap, aoMap] = useLoader(
THREE.TextureLoader,
[
"./assets/texture/PavingStones092_1K_Color.jpg",
"./assets/texture/PavingStones092_1K_Displacement.jpg",
"./assets/texture/PavingStones092_1K_Normal.jpg",
"./assets/texture/PavingStones092_1K_Roughness.jpg",
"./assets/texture/PavingStones092_1K_AmbientOcclusion.jpg",
]
);
return (
<mesh>
<sphereGeometry args={[1, 100, 100]} />
<meshStandardMaterial
map={colorMap}
displacementScale={0.3}
displacementMap={displacementMap}
normalMap={normalMap}
roughnessMap={roughnessMap}
aoMap={aoMap}
/>
</mesh>
);
}

function InfoThree() {
const { camera, gl } = useThree();
const three = useThree();
console.log(camera, gl);
console.log(three);
return null;
}

export default App;

效果图

demo-3d立体字

ok,讲了那么多,顺便分享个使用three来搭建3d立体字吧。

初步搭建

这里使用了Center居中,以及它自带的Text3D效果,这里的字体我是在网上找的,大家可以自己找一下,然后放到assets/font,这里我用的仿宋,这里字体必须要有,不然会报错。

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
import "./App.css";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Text3D, Center } from "@react-three/drei";

function App() {

return (
<div className="App">
<Canvas>
<OrbitControls />
<ambientLight args={[0xffffff]} intensity={0.5} />
<directionalLight
args={[0xffffff]}
position={[0, 5, 5]}
intensity={0.5}
/>
<Text />
</Canvas>
</div>
);
}

function Text() {
let fontUrl = "./assets/font/FangSong_Regular.json";
return (
<Center>
<Text3D scale={3} height={0.25} font={fontUrl}>
Codesigner
</Text3D>
</Center>
);
}
export default App;

效果图

调整字体显示样式

这里字体的调整大家可以去看three文档中的TextGeometry这块,那里有具体的文档,比我这详细

  • 这里我在canvas里面修改了相机为正交相机,并且设置了位置和缩放比例。
  • 我将字体默认旋转了一下,添加了字体间隔属性letterSpacing
  • bevelEnabled 该属性指定文本拉伸时是否启用斜角,默认false
  • bevelThickness 该属性指定文本拉伸体斜角的厚度,默认值是10
  • bevelSize 该属性指定文本拉伸体斜角的高度。默认值是8
  • bevelSegments 该属性指定文本拉伸体斜角的分段数,段数越多斜角越光滑,默认值是3
  • curveSegments 该属性指定文本拉伸时拉伸曲线的分段数,段数越多曲线越光滑,默认值是4
    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
    import "./App.css";
    import { Canvas } from "@react-three/fiber";
    import { OrbitControls, Text3D, Center } from "@react-three/drei";

    function App() {

    return (
    <div className="App">
    <Canvas
    orthographic
    camera={{ position: [10, 20, 20], zoom: 80 }}
    >
    <OrbitControls />
    <ambientLight args={[0xffffff]} intensity={0.5} />
    <directionalLight
    args={[0xffffff]}
    position={[0, 5, 5]}
    intensity={0.5}
    />
    <Text />
    </Canvas>
    </div >
    );
    }

    function Text() {
    let fontUrl = "./assets/font/FangSong_Regular.json";
    return (
    <Center>
    <Text3D
    scale={3}
    height={0.25}
    font={fontUrl}
    rotation={[-Math.PI / 2, 0, 0]}
    letterSpacing={-0.03}
    bevelEnabled
    bevelSize={0.01}
    bevelSegments={10}
    bevelThickness={0.01}
    curveSegments={128}
    >
    Codesigner
    <meshBasicMaterial color={0xff0000} />
    </Text3D>
    </Center>
    );
    }
    export default App;

添加阴影

这里我使用了累计阴影:AccumulativeShadows
以及随机光源:RandomizedLight
随机光源的定义是这样的:一种随机光源,可在内部运行多个光源并使它们抖动。见下文,您通常会将其与累积阴影配对。该组件是上下文感知的,与累积阴影配对,它将从其父级获取帧数。
我把这些参数文档截图发一下,大家就理解了

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
60
61
62
63
64
65
66
67
68
69
70
71
72
import { Canvas,  useLoader } from "@react-three/fiber";
import "./App.css";
import {
Center,
OrbitControls,
Text3D,
AccumulativeShadows,
RandomizedLight,
} from "@react-three/drei";

export default function App() {
return (
<Canvas
shadows
orthographic
camera={{ position: [10, 20, 20], zoom: 80 }}
gl={{ preserveDrawingBuffer: true }}
>
<ambientLight intensity={0.5} />
<directionalLight position={[0, 5, 5]} intensity={0.5}></directionalLight>
<OrbitControls autoRatate={true} />
<Text />
{/* 设置软阴影 */}
<AccumulativeShadows
// 随着时间推移进行累积阴影
temporal
frames={100}
// 阴影的颜色
color="blue"
colorBlend={10}
toneMapped={true}
opacity={1}
scale={30}
position={[0, -0.45, 0]}
>
<RandomizedLight
amount={4}
radius={10}
intensity={1}
position={[0, 10, -10]}
size={15}
mapSize={1024}
bias={0.0001}
/>
</AccumulativeShadows>
</Canvas>
);
}

function Text() {
let fontUrl = "./assets/font/FangSong_Regular.json";
return (
<Center>
<Text3D
castShadow
bevelEnabled
font={fontUrl}
scale={3}
letterSpacing={-0.03}
height={0.25}
bevelSize={0.01}
bevelSegments={10}
curveSegments={128}
bevelThickness={0.01}
rotation={[-Math.PI / 2, 0, 0]}
>
Codesigner
<meshBasicMaterial color={0xff0000} />
</Text3D>
</Center>
);
}

参数截图
参数截图

绘制材质

整体逻辑

  1. 导入了Three.js库和一些相关的模块。
  2. 创建了一个继承自MeshPhysicalMaterial的类MeshRefractionMaterialImpl,该类表示折射材质的实现。
  3. 构造函数中初始化了一些uniform变量,这些变量将在着色器中使用。
  4. 在onBeforeCompile函数中,对着色器进行修改和替换操作,以实现折射效果。
  5. 最后通过extend函数将自定义的材质注册到Three.js中,使其可以在场景中使用。
  6. 导出了一个React组件MeshRefractionMaterial,该组件接受一些属性参数,并将窗口分辨率传递给材质。

代码注解

在onBeforeCompile函数中,代码对着色器进行了修改和替换操作,以实现折射效果。具体来说,代码做了以下几件事情:

添加uniform变量:通过在shader.uniforms中添加自定义的uniform变量,将这些变量传递给着色器程序。
修改fragmentShader:通过替换shader.fragmentShader中的特定代码片段,将折射效果的计算和渲染逻辑插入到原有的渲染流程中。
具体来看,代码的修改步骤如下:

添加uniform变量:通过shader.uniforms = {…shader.uniforms, …this.uniforms};将自定义的uniform变量合并到原有的uniform变量集合中。

uTransparent: 控制折射的透明度。
winResolution: 窗口分辨率,用于计算uv坐标。
uRefractPower: 控制折射的强度。
uRefractNormal: 控制折射的法线方向。
uNoise: 控制折射的噪声。
uSat: 控制颜色饱和度。
uIntensity: 控制折射的强度。
uColor: 折射颜色。
uSceneTex: 场景纹理。
修改fragmentShader:通过字符串替换的方式,在原有的fragmentShader中插入自定义的折射计算和渲染逻辑。

首先,添加了一些自定义的辅助函数:random和sat,用于生成随机数和调整颜色饱和度。
然后,在原有的渲染逻辑之前插入了折射计算的代码。
折射计算部分的代码使用了循环进行多次采样,通过在uv坐标上根据折射方向和强度进行采样,将采样到的颜色累加到refractCol中。
最后,将refractCol除以采样次数,得到平均值,并将其与uIntensity和uColor进行混合,得到最终的折射颜色。
最后,使用替换后的fragmentShader替换掉原有的fragmentShader。
总的来说,onBeforeCompile函数通过修改和替换着色器代码,实现了在场景中物体上的折射效果。这个折射效果是通过对纹理进行采样、颜色调整和混合等操作得到的。

材质代码

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import * as THREE from "three";
import { extend, useThree } from "@react-three/fiber";

class MeshRefractionMaterialImpl extends THREE.MeshPhysicalMaterial {
constructor(args) {
super(args);

this.uniforms = {
uRefractPower: { value: 0.3 },
uRefractNormal: { value: 0.85 },
uSceneTex: { value: null },
uTransparent: { value: 0.5 },
uNoise: { value: 0.03 },
uColor: { value: new THREE.Color("white") },
uSat: { value: 0.0 },
uIntensity: { value: 1.0 },
winResolution: { value: new THREE.Vector2() },
};

this.onBeforeCompile = (shader) => {
shader.uniforms = {
...shader.uniforms,
...this.uniforms,
};

shader.fragmentShader = shader.fragmentShader.replace(
"outgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;",
""
);

shader.fragmentShader =
`uniform float uTransparent;
uniform vec2 winResolution;
uniform float uRefractPower;
uniform float uRefractNormal;
uniform float uNoise;
uniform float uSat;
uniform float uIntensity;
uniform vec3 uColor;
uniform sampler2D uSceneTex;

float random(vec2 p) {
return fract(sin(dot(p.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

vec3 sat(vec3 rgb, float adjustment) {
const vec3 W = vec3(0.2125, 0.7154, 0.0721);
vec3 intensity = vec3(dot(rgb, W));
return mix(intensity, rgb, adjustment);
}
` + shader.fragmentShader;
shader.fragmentShader = shader.fragmentShader.replace(
"#include <output_fragment>",
`vec2 uv = gl_FragCoord.xy / winResolution.xy;
vec2 refractNormal = vNormal.xy * (1.0 - vNormal.z * uRefractNormal);
vec3 refractCol = vec3( 0.0 );

float slide;
vec2 refractUvR;
vec2 refractUvG;
vec2 refractUvB;
#pragma unroll_loop_start
for ( int i = 0; i < 16; i ++ ) {
slide = float(UNROLLED_LOOP_INDEX) / float(16) * 0.1 + random(uv) * uNoise;
refractUvR = uv - refractNormal * ( uRefractPower + slide * 1.0 ) * uTransparent;
refractUvG = uv - refractNormal * ( uRefractPower + slide * 2.0 ) * uTransparent;
refractUvB = uv - refractNormal * ( uRefractPower + slide * 3.0 ) * uTransparent;
refractCol.r += texture2D( uSceneTex, refractUvR ).r;
refractCol.g += texture2D( uSceneTex, refractUvG ).g;
refractCol.b += texture2D( uSceneTex, refractUvB ).b;
refractCol = sat(refractCol, uSat);
}
#pragma unroll_loop_end

refractCol /= float( 16 );

outgoingLight = mix(refractCol * uIntensity, uColor, 0.25) * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;
#include <output_fragment>`
);
};

Object.keys(this.uniforms).forEach((name) =>
Object.defineProperty(this, name, {
get: () => this.uniforms[name].value,
set: (v) => (this.uniforms[name].value = v),
})
);
}
}

extend({ MeshRefractionMaterial: MeshRefractionMaterialImpl });

export function MeshRefractionMaterial(props) {
const { size, viewport } = useThree();
return (
<meshRefractionMaterial
winResolution={[size.width * viewport.dpr, size.height * viewport.dpr]}
{...props}
/>
);
}

使用材质

这段代码,是用来留下快照的,我这里是用来留下背景的,然后在使用这个背景作为材质的贴图,这里我使用了useFBO这个hook,这个hook是用来创建一个帧缓冲区的,然后我们就可以将这个帧缓冲区的内容作为材质的贴图了。

1
2
3
4
5
6
7
8
9
10
const fbo = useFBO(1024);
let oldBg = null;
useFrame((state) => {
state.gl.setRenderTarget(fbo);
oldBg = state.scene.background;
state.scene.background = texture;
state.gl.render(state.scene, state.camera);
state.scene.background = oldBg;
state.gl.setRenderTarget(null);
});

完整代码

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { RGBELoader } from "three-stdlib";
import { Canvas, useFrame, useLoader } from "@react-three/fiber";
import "./App.css";
import {
Center,
OrbitControls,
Text3D,
AccumulativeShadows,
RandomizedLight,
useFBO,
} from "@react-three/drei";
import { MeshRefractionMaterial } from "./material/MeshRefractionMaterial";

export default function App() {
return (
<Canvas
shadows
orthographic
camera={{ position: [10, 20, 20], zoom: 80 }}
gl={{ preserveDrawingBuffer: true }}
>
<ambientLight intensity={0.5} />
<directionalLight position={[0, 5, 5]} intensity={0.5}></directionalLight>
<OrbitControls autoRatate={true} />
<Text />
{/* 设置软阴影 */}
<AccumulativeShadows
// 随着时间推移进行累积阴影
temporal
frames={100}
// 阴影的颜色
color="blue"
colorBlend={10}
toneMapped={true}
opacity={1}
scale={30}
position={[0, -0.45, 0]}
>
<RandomizedLight
amount={4}
radius={10}
intensity={1}
position={[0, 10, -10]}
size={15}
mapSize={1024}
bias={0.0001}
/>
</AccumulativeShadows>
</Canvas>
);
}

function Text() {
const texture = useLoader(RGBELoader, "./assets/texture/024.hdr");
let fontUrl = "./assets/font/FangSong_Regular.json";

const fbo = useFBO(1024);
let oldBg = null;
useFrame((state) => {
state.gl.setRenderTarget(fbo);
oldBg = state.scene.background;
state.scene.background = texture;
state.gl.render(state.scene, state.camera);
state.scene.background = oldBg;
state.gl.setRenderTarget(null);
});

return (
<Center>
<Text3D
castShadow
bevelEnabled
font={fontUrl}
scale={3}
letterSpacing={-0.03}
height={0.25}
bevelSize={0.01}
bevelSegments={10}
curveSegments={128}
bevelThickness={0.01}
rotation={[-Math.PI / 2, 0, 0]}
>
Codesigner
{/* <meshBasicMaterial color={0xff0000} /> */}
<MeshRefractionMaterial
uSceneTex={fbo.texture}
clearcoat={1}
clearcoatRoughness={0.1}
uRefractPower={0.5}
uTransparent={0.5}
uIntensity={1.3}
uNoise={0.03}
uSat={1}
uColor={0x8284e5}
gColor={0xa6a6e5}
></MeshRefractionMaterial>
</Text3D>
</Center>
);
}

效果图

添加gui效果

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import { useRef } from "react";
import { RGBELoader } from "three-stdlib";
// threejs/examples一直被认为是您需要复制/粘贴到您的项目中并适应您的需求的东西。但这不是人们使用它们的方式。这会导致许多问题和很少的支持。
// 真实的,npm/node 符合带有标记依赖项的 esm 模块,基于类,针对 tree-shaking 进行了优化,没有全局污染,导出而不是集合,esm 和 cjs 的构建系统
import { Canvas, useFrame, useLoader } from "@react-three/fiber";
import "./App.css";
import {
useFBO,
Center,
Text3D,
OrbitControls,
RandomizedLight,
AccumulativeShadows,
} from "@react-three/drei";
import { useControls, button } from "leva";
import { MeshRefractionMaterial } from "./material/MeshRefractionMaterial";

export default function App() {
const { autoRotate, text, shadow, ...config } = useControls({
text: "Codesigner",
clearcoat: { value: 1, min: 0.1, max: 1 },
clearcoatRoughness: { value: 0.25, min: 0, max: 1 },
uRefractPower: { value: 0.35, min: 0, max: 5 },
uTransparent: { value: 0.25, min: 0, max: 5 },
uIntensity: { value: 1.3, min: 0.0, max: 5.0 },
uNoise: { value: 0.03, min: 0, max: 1, step: 0.01 },
uSat: { value: 1.0, min: 1, max: 1.25, step: 0.01 },
uColor: "#8284e5",
gColor: "#a6a6e5",
shadow: "#a0a7e5",
autoRotate: false,
screenshot: button(() => {
// Save the canvas as a *.png
const link = document.createElement("a");
link.setAttribute("download", "canvas.png");
link.setAttribute(
"href",
document
.querySelector("canvas")
.toDataURL("image/png")
.replace("image/png", "image/octet-stream")
);
link.click();
}),
});
return (
<Canvas
shadows
orthographic
camera={{ position: [10, 20, 20], zoom: 80 }}
gl={{ preserveDrawingBuffer: true }}
>
<color attach="background" args={["#f2f2f5"]} />
{/** The text and the grid */}
<Text
config={config}
rotation={[-Math.PI / 2, 0, 0]}
position={[0, -1, 2.25]}
>
{text}
</Text>
{/** Controls */}
<OrbitControls autoRotate={autoRotate} />
{/** The environment is just a bunch of shapes emitting light. This is needed for the clear-coat */}
{/* 环境只是一堆发光的形状。这是透明涂层所需的 */}
<ambientLight args={[0xffffff]} intensity={0.5} />
<directionalLight
args={[0xffffff]}
position={[0, 5, 5]}
intensity={0.5}
/>
{/**
* Soft shadows
* 一个平面的、Y 向上方向的阴影捕捉器,可以累积成柔和的阴影,并且在累积所有帧后对性能的影响为零。
* 它可以是暂时的,它会随着时间的推移而累积,或者是瞬时的,这可能会很昂贵,具体取决于您渲染的帧数。
* 您必须将它与投射阴影的光源(和场景对象!)配对,这些阴影进入子槽。
* 最好将它与RandomizedLight组件一起使用,它会在周围晃动一组灯光,创建逼真的光线投射式阴影和环境光遮蔽。
* */}
<AccumulativeShadows
// 随着时间的推移,“时间会累积阴影这更具表现力但对即时结果有视觉回归
temporal
// 它可以渲染多少帧越多就产生更干净的结果但需要更多的时间
frames={100}
// 阴影的颜色
color={shadow}
colorBlend={10}
toneMapped={true}
opacity={1}
scale={30}
position={[0, -1.01, 0]}
>
<RandomizedLight
amount={4}
radius={10}
ambient={0.5}
intensity={1}
position={[0, 10, -10]}
size={15}
mapSize={1024}
bias={0.0001}
/>
</AccumulativeShadows>
</Canvas>
);
}

function Text({
children,
config,
font = "./assets/font/FangSong_Regular.json",
...props
}) {
const ref = useRef();
const fbo = useFBO(1024);
const texture = useLoader(RGBELoader, "./assets/texture/024.hdr");

let oldBg;
useFrame((state) => {
// Hide the outer groups contents
ref.current.visible = false;
// Set render target to the local buffer
state.gl.setRenderTarget(fbo);
// Save the current background and set the HDR as the new BG
// This is what creates the reflections
oldBg = state.scene.background;
state.scene.background = texture;
// Render into the buffer
state.gl.render(state.scene, state.camera);
// Set old state back
state.scene.background = oldBg;
state.gl.setRenderTarget(null);
ref.current.visible = true;
});

return (
<>
<group ref={ref}>
<Center scale={[0.8, 1, 1]} front top {...props}>
<Text3D
castShadow
bevelEnabled
font={font}
scale={5}
letterSpacing={-0.03}
height={0.25}
bevelSize={0.01}
bevelSegments={10}
curveSegments={128}
bevelThickness={0.01}
>
{children}
{/** Pass the rendered buffer into the refraction shader */}
<MeshRefractionMaterial uSceneTex={fbo.texture} {...config} />
</Text3D>
</Center>
</group>
{/** Double up the text as a flat layer at the bottom for more interesting refraction */}
<Center scale={[0.8, 1, 1]} front top {...props}>
<Text3D
font={font}
scale={5}
letterSpacing={-0.03}
height={0.01}
curveSegments={32}
>
{children}
<meshBasicMaterial color={config.gColor} />
</Text3D>
</Center>
</>
);
}

结语

好了,本篇文章就介绍到这里,更多内容,敬请期待~~~

上一篇:
vue双向prop
下一篇:
浏览器调用系统通知