ThreeJS是一个基于WebGL的图形渲染框架,封装了诸如场景、灯光、阴影、材质、贴图、空间运算等一系列功能,让你不必要再从底层WebGL开始写起。
一个ThreeJS的工作流程相当是这样的:
Scene
,相机Camera
,以及里面的各种元素Render
负责进行渲染,并对接下来的帧的内容进行监控处理在使用ThreeJS之前,我们需要进行导入。对于React项目来说,通常我们将3D内容写进组件中进行封装,并交给上层父组件调用。
组件的书写结构如下:
JS// 导入threejs
import * as THREE from "three";
// 在react中我们使用useEffect调用threejs
import {useEffect} from "react";
function SampleComponent() {
useEffect(() => {
// 这里书写threejs主体内容
return () => {
// 这里一般用于在结束时删除组件防止内存泄露
}
}, []);
return null
}
export default SampleComponent;
在使用 React 编写 Three.js 应用时,将代码写在 useEffect
里,并返回 null
,是一种常见的模式。采用这种形式的原因如下:
副作用管理:useEffect
是 React 用来管理副作用的钩子函数。Three.js 场景的创建和渲染属于副作用,因为它们直接操作 DOM 或者 Canvas,不属于 React 的渲染流程。因此,将这些逻辑放在 useEffect
里可以确保它们在组件挂载或更新时正确执行。
只在组件挂载时执行:通过指定依赖数组(如 []
),可以让 useEffect
只在组件挂载时执行一次。这样可以防止 Three.js 场景的重复创建,提升性能。
返回 null
只是不渲染 DOM 元素:在 React 组件中返回 null
意味着这个组件不渲染任何 DOM 元素。但在 useEffect
里执行的 Three.js 逻辑仍然会创建并渲染到指定的 Canvas 上。这种方式实际上将 Three.js 的渲染与 React 的 DOM 渲染分离开来,避免 React 重新渲染时不必要的 DOM 操作干扰 Three.js 的渲染。
清理资源:useEffect
可以返回一个清理函数,当组件卸载或依赖项改变时执行。这样可以确保 Three.js 场景的资源(比如几何体、材质等)能够在组件销毁时正确释放,避免内存泄漏。
将 Three.js 的代码放在 useEffect
里,并返回 null
,可以让你更好地控制 Three.js 的生命周期管理,同时与 React 的组件渲染逻辑保持分离。这样不仅能确保性能优化,还能避免不必要的资源浪费。
与Unity类似, 场景Scene代表了一个视图,它是所有需要显示的元素的集合。它往往在最开始声明,声明之后只需要通过add
向其中增加希望显示的东西即可。
JS// 声明
const scene = new THREE.Scene();
// 添加元素
scene.add(sampleElement);
同样与Unity类似,相机Camera代表了显示元素的视角,它能够按需变换,以达到不同的目的。声明后,通常只需要通过改变它的位置、角度等,也可以通过轨道进行自由变换。
JSconst camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
)
相机的常用类型有以下几种:
我们在这里只创建最简单的透视相机。透视相机接受四个参数,分别是:
fov
: 相机视角,视角越大范围越广aspect
: 图像宽高比,实例声明为窗口宽高比near
: 近裁剪面,与相机的距离小于这个范围的元素将不会被渲染far
: 远裁剪面,与相机的距离大于这个范围的元素将不会被渲染物体三巨头。这三个元素构成了渲染一个显示物体的关键。
以面向对象的思想来说,几何相当于一个抽象出来的类。它代表了一种形状,而不代表一个具体物体。它的声明如下所示:
JSconst cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
在这里,我们取了一个 BoxGeometry
,大小为 (1, 1, 1)
,并将这个大小的盒子形状命名为 cubeGeometry
。在这里,我们并没有完成一个具体的物体的创建,而是 创建了一个特定大小、特定形状的模板。它需要经过处理后才能表示一个具体的物体。
材质可以理解为具体物体的表面的样式,它决定了这个物体的外观。它的声明如下所示:
JSconst cubeMaterial = new THREE.MeshBasicMaterial({color: 0xffff00});
在这里,我们使用了基本的材质,并把颜色定为 0xffff00
。
虽然它被称为网格,但它才指代一个具体的物体。具体来说,它会使用Mesh
方法结合几何与材质,并将其统一为一个Mesh
对象,这个对象表示一个在视图中具体的物体。最后,它需要被添加进scene
,从而显示出来。
JSconst cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);
至此,一个颜色为0xffff00
,形状为BoxGeometry
,大小为(1, 1, 1)
的物体cube
就被创建完毕了。
光照能够让几何体显现明暗关系,进而出现能够让人脑接受的3D效果。Light有很多类型,这里先使用一束平行光。
JSconst color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
那么,我们场景已经做好了,物体已经添加了,摄像机已经就位了,接下来就要把三维空间里的所有可视元素渲染到二维平面的网页中,这就是渲染器Renderer的作用。它的声明如下:
JSconst renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
第一行声明了一个基于WebGL渲染器的渲染器实例,第二行则是规定了这个渲染器的尺寸,也就是画布的范围。
第三行将这个渲染器的domElement直接放在了HTML文件的body下充当子元素。这样,浏览器就会显示这个渲染好的结果。
同时,我们需要声明一个 render()
函数:
JSfunction render() {
renderer.render(scene, camera)
requestAnimationFrame(render)
}
render()
这个函数用于执行渲染操作。
其中,renderer.render(scene, camera)
是核心,它将指定的场景通过指定的相机渲染出来。
requestAnimationFrame
是一个浏览器提供的API,用于高效地循环动画。
调用 requestAnimationFrame(render)
会在下一次重绘时调用 render
函数,实现每帧都进行渲染。这种方式比使用 setTimeout
或 setInterval
更加节能且平滑,因为它能够使浏览器根据显示器的刷新率来优化渲染。
最后一行 render()
,是第一次调用,它启动了渲染循环,使得 requestAnimationFrame
开始调用 render
,进而实现持续的动画效果。
那么,回过头来看这个结构图:
这个结构图代表了:使用WebGL渲染器渲染一个透视相机,内容是一个场景,内含一个平行光源,以及三个立方体网格对象。这三个对象使用了同一个BoxGeometry形状,但是使用了三种不同的材质。
本文作者:Jeff Wu
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!