内容摘要:使用Three.js库构建了一个交互式的3D场景。组件中创建了一个机器人模型,包括头部、眼睛、触角、身体和四肢,以及两个相同的机器人实例以实现动态效果。场景中还加入了粒子效果,模拟星系环境,增强了视觉效果。通过OrbitControls,用户可以对机器人进行旋转控制。组件在渲染时会根据用户界面的变化动态调整渲染,并在指定的div容器中显示。整体上,这个组件提供了一个基础的3D动画展示和用户交互的框架。
- 在React中集成Three.js库,创建一个动态渲染的3D场景。
- 定义和渲染多个Three.js几何体,如机器人身体、胳膊、腿、眼睛和触角。
- 添加交互性,如旋转和轨道控制器,以提供更好的用户体验。
- 创建粒子特效,模拟星系环境,与机器人形成对比。
一、项目搭建react+three.js
实现这样的效果需要安装three.js包;至于使用vue还是react框架都行,因为three.js只需要一个div作为挂载点即可。也用不到框架的细节。本文以react为例实现。
依次执行以下命令,完成react项目的初始化和threejs的安装
良好的编码习惯要求我们,在views里新增一个robot文件夹,根据react组件的特效,用.tsx后缀表示是组件。定义一个方法名为Robot的function并将其默认导出。react的特点,函数式组件。方法名就是组件名,这个是react内部进行编译处理的。跟vue差别很大。
在App.tsx中引入robot组件
npm run start 运行即可看到效果
二、实现细节
实现机器人及星空特效,其中机器人构建可以是批量的,机器人身体又可以拆分为脑袋、触角、眼睛、身体、胳膊、腿等细节。每个部分单独用有方法实现,逻辑拆分清晰。
对单个3D模型来说,需要三个东西:mesh=geometry(几何)+material(材料)
-
几何体(Geometry):
THREE.SphereGeometry
: 用于创建球形几何体。它接受几个参数,包括半径(radius)、宽度分段(widthSegments)、高度分段(heightSegments)、水平起始角度(phiStart)、水平扫描角度(phiLength)、垂直起始角度(thetaStart)和垂直扫描角度(thetaLength)。THREE.CapsuleGeometry
: 用于创建胶囊形状的几何体,可以看作是一个圆柱体两端加上半球体。它接受两个参数,分别是半径(radius)和高度(height)。THREE.CylinderGeometry
:创建圆柱体。圆柱体由两个圆形底面和一个侧面组成。这个类的作用是定义一个圆柱形状的3D几何体,它可以在 Three.js 的场景中被渲染。
-
材质(Material):
THREE.MeshStandardMaterial
: 用于创建标准网格材质,它提供了多种物理渲染特性,如颜色(color)、粗糙度(roughness)和金属度(metalness)等。
-
网格(Mesh):
THREE.Mesh
: 网格是几何体和材质的组合,可以通过它将几何体渲染到场景中。它接受一个几何体(geometry)和一个材质(material)作为参数。
这里方便解耦,单个方法只生成模型,在调用方法的地方确定模型的位置。
机器人脑袋
//机器人脑袋
function createHead() {//SphereGeometry创建球形几何体const head = new THREE.SphereGeometry(4, 32, 16, 0, Math.PI * 2, 0, Math.PI * 0.5);const headMaterial = new THREE.MeshStandardMaterial({color: 0x43b988,roughness: 0.5,metalness: 1.0,});const headMesh = new THREE.Mesh(head, headMaterial);return headMesh;
}
机器人触角
//触角
function generateHorn(y: number, z: number, angle: number) {//触角 CapsuleGeometry 创建胶囊形状的几何体。胶囊形状可以看作是一个圆柱体两端加上半球体const line = new THREE.CapsuleGeometry(0.1, 2);const lineMaterial = new THREE.MeshStandardMaterial({color: 0x43b988,roughness: 0.5,metalness: 1.0,});const lineMesh = new THREE.Mesh(line, lineMaterial);lineMesh.position.y = y;lineMesh.position.z = z;lineMesh.rotation.x = angle;return lineMesh;
}
机器人眼睛
//机器人眼睛
function generateEye(x: number, y: number, z: number) {//SphereGeometry创建球形几何体const eye = new THREE.SphereGeometry(0.5, 32, 16, 0, Math.PI * 2, 0, Math.PI * 2);const eyeMaterial = new THREE.MeshStandardMaterial({color: 0x212121,roughness: 0.5,metalness: 1.0,});const eyeMesh = new THREE.Mesh(eye, eyeMaterial);eyeMesh.position.x = x;eyeMesh.position.y = y;eyeMesh.position.z = z;return eyeMesh;
}
机器人身体
//机器人身体
function generateBody() {//CylinderGeometry第一个参数是上部分圆的半径,第二个参数是下部分圆的半径,第三个参数是高度,材质使用的跟腿一样const body = new THREE.CylinderGeometry(4, 4, 6);const bodyMaterial = new THREE.MeshStandardMaterial({color: 0x43b988,roughness: 0.5,metalness: 1.0,});const bodyMesh = new THREE.Mesh(body, bodyMaterial);return bodyMesh;
}
机器人胳膊
//胳膊、腿
function generateLegs(y: number, z: number) {const leg1 = new THREE.CapsuleGeometry(1, 4);const legMaterial1 = new THREE.MeshStandardMaterial({color: 0x43b988,roughness: 0.5,metalness: 1.0,});const leg1Mesh = new THREE.Mesh(leg1, legMaterial1);leg1Mesh.position.y = y;leg1Mesh.position.z = z;return leg1Mesh;
}
创建机器人
//创建机器人
function generateRobot() {// 创建一个Three.js对象,用于存放机器人const robot = new THREE.Object3D();const headMesh = createHead();headMesh.position.y = 6.5;robot.add(headMesh);//眼睛const leftEye = generateEye(3, 8, -2);const rightEye = generateEye(3, 8, 2);robot.add(leftEye);robot.add(rightEye);const leftHorn = generateHorn(11, -1, (-Math.PI * 30) / 180);const rightHorn = generateHorn(11, 1, (Math.PI * 30) / 180);robot.add(leftHorn);robot.add(rightHorn);const body = generateBody();body.position.y = 4;robot.add(body);// 生成机器人左腿robot.add(generateLegs(0, -2));// 生成机器人右腿robot.add(generateLegs(0, 2));//胳膊robot.add(generateLegs(3, 5));robot.add(generateLegs(3, -5));//物体缩放robot.scale.x = 0.3;robot.scale.y = 0.3;robot.scale.z = 0.3;return robot;
}
生成粒子场景
//创建粒子星星
function generateStarts(num: number) {//制作粒子特效const starts = new THREE.Object3D();const obj = new THREE.SphereGeometry(0.2, 3, 3);const material = new THREE.MeshStandardMaterial({color: 0x43b988,roughness: 0.5,metalness: 5,});const mesh = new THREE.Mesh(obj, material);for (let i = 0; i < num; i++) {const target = new THREE.Mesh();target.copy(mesh);target.position.x = Math.floor(Math.random() * 18 + Math.floor(Math.random() * -18));target.position.y = Math.floor(Math.random() * 18 + Math.floor(Math.random() * -18));target.position.z = Math.floor(Math.random() * 18 + Math.floor(Math.random() * -18));starts.add(target);}return starts;
}
主方法
Scene场景
scene
是一个THREE.Scene
对象,它是Three.js中场景的容器,用于组织和管理3D对象,如几何体、材质、相机和灯光等。
add(object)
: 这个方法用于将对象(例如机器人、光源、粒子等)添加到场景中。
示例代码中使用了多次:
scene.add(robot);
scene.add(robot2);
scene.add(straightLight);
scene.add(starts);
PerspectiveCamera透视相机
3D场景中的相机视角。一般都是使用透视相机PerspectiveCamera,第一个参数是fov,表示相机所成的一个四棱台远面与近面之间的夹角,夹角越小,看见的东西越少,夹角越大,看见的东西就越多,但是周围会显的比较模糊,一般取值以45~75最佳,第二个参数aspect是近裁面的一个宽高比,我们用窗口的宽除以窗口的高就可以了,第三个值near与第四个值far分别是与近面和远面的距离,这里设置的值分别为0.1与1000,调用camera.positon.set表示设置相机的位置,默认都是在(0,0,0)的位置,我们这里给相机设置的位置为(15,12,8),并且让相机正对(0,0,0)的位置
PerspectiveCamera
是Three.js中的透视投影相机,文中使用的参数解释如下:
75
:视角(Field of View,FOV),决定了视场的宽度,单位是度。window.innerWidth / window.innerHeight
:纵横比,根据浏览器窗口的宽度和高度计算,确保相机适应窗口大小。0.1
:近裁剪面(Near clipping plane),即相机能看到的最近物体的距离。1000
:远裁剪面(Far clipping plane),即相机能看到的最远物体的距离。
DirectionalLight光源
在Three.js中,DirectionalLight
是一种光源,它模拟从特定方向发出的平行光,类似于太阳光。这种光源的特点是所有从光源发出的光线都是平行的,不会随着距离的增加而发散。
DirectionalLight
在这里的作用包括:
-
照亮场景:它为场景提供光照,使得场景中的物体可以被看到,并根据材质属性产生不同的光照效果。
-
产生阴影:
DirectionalLight
可以产生阴影效果,使得场景更加真实。物体遮挡光线的地方会形成阴影,有助于表现物体的立体感和空间位置。 -
定义光照方向:通过设置
DirectionalLight
的位置和方向,可以定义光线照射到场景的角度,从而影响场景的整体光照效果。 -
调整光照强度:可以通过设置
intensity
属性来调整光线的亮度。
OrbitControls
控制器
Three.js 中的 OrbitControls
是一个控制器,它允许用户通过鼠标或触摸事件来控制相机的移动,从而实现对场景的旋转、缩放和平移操作。这个控制器使得用户可以更自然地与3D场景交互。
在使用
OrbitControls
时,需要注意以下几点:
- 控制器需要与相机和渲染器的DOM元素一起被初始化。
- 在动画循环中调用
controls.update()
方法来确保控制器可以更新相机状态。- 可以通过修改
OrbitControls
的各种属性来自定义控制行为,例如最小/最大缩放距离、旋转限制等。
OrbitControls
是Three.js中非常实用的一个工具,它大大简化了3D场景的用户交互开发过程。
以下是 OrbitControls
的一些主要作用:
-
旋转(Rotate):用户可以通过拖动鼠标来旋转相机,从而改变视角。
-
缩放(Zoom):通过滚动鼠标滚轮或触摸屏幕进行捏合操作,可以放大或缩小场景。
-
平移(Pan):通过按下鼠标右键并拖动,或者在某些触摸设备上通过特定的手势,可以在场景中平移相机。
以下是 OrbitControls
的基本用法:
// 引入OrbitControls
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';// 创建场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);// 设置相机位置
camera.position.set(0, 0, 5);// 创建OrbitControls实例并传入相机和渲染器
const controls = new OrbitControls(camera, renderer.domElement);// 创建一些对象添加到场景中
// ...// 渲染场景
function animate() {requestAnimationFrame(animate);controls.update(); // 更新控制器renderer.render(scene, camera);
}
animate();
position
位置信息
在 Three.js 中,position
属性的作用是用于确定 3D 对象在场景中的位置, 属性对于实现 3D 场景中对象的布局、动画和交互等方面都非常重要。通过设置对象的 position
属性,可以精确地指定该对象在三维空间中的坐标。这使得能够将对象放置在所需的位置,从而构建出具有特定布局和结构的 3D 场景。
例如,如果要将一个立方体放置在场景的特定点(比如 x 坐标为 10,y 坐标为 20,z 坐标为 30),可以这样设置:
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);cube.position.set(10, 20, 30);scene.add(cube);
这样,立方体就会出现在指定的位置上。
rotation旋转属性
在 Three.js 中,rotation
属性的作用是用于确定 3D 对象在场景中的旋转状态。通过设置对象的 rotation
属性,可以精确地指定该对象在三维空间中的旋转角度。这使得能够将对象旋转到所需的方向,从而构建出具有特定朝向和视角的 3D 场景。
rotation
属性使用的是欧拉角(Euler angles),它包括三个分量:x
、y
和 z
,分别表示绕 X 轴、Y 轴和 Z 轴的旋转角度(以弧度为单位)。
例如,如果要将一个立方体绕 Y 轴旋转 45 度(即 π/4 弧度),可以这样设置:
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);cube.rotation.y = Math.PI / 4; // 45 degrees in radiansscene.add(cube);
requestAnimationFrame实现动画
使用requestAnimationFrame
: requestAnimationFrame
是浏览器提供的API,用于在每一帧绘制动画。Three.js中的动画通常通过这个API来实现。
function animate() {requestAnimationFrame(animate);// 更新对象属性,例如位置、旋转等cube.rotation.x += 0.01;cube.rotation.y += 0.01;// 渲染场景renderer.render(scene, camera);
}
animate();
useEffect
在React组件中,
useEffect
钩子用于处理副作用操作,比如数据获取、订阅、手动更改DOM等。useEffect
钩子的主要作用是在组件挂载后将 Three.js 渲染器的 DOM 元素添加到指定的容器中,并在组件卸载时进行清理。
组件挂载时添加渲染器 DOM 元素:
useEffect
钩子在组件挂载时执行,确保containerRef.current
已经指向了实际的 DOM 元素。- 通过
containerRef.current.appendChild(renderer.domElement)
,将 Three.js 渲染器的 DOM 元素添加到指定的容器中。确保渲染器 DOM 元素正确添加:
- 如果不使用
useEffect
,直接在组件渲染时添加渲染器 DOM 元素,可能会导致containerRef.current
为null
,因为此时 DOM 元素可能还未挂载到页面上。
/*** 创建一个Three.js场景,包括相机和渲染器*/
function Robot() {// 创建一个div容器,用于存放渲染的Three.js场景const containerRef = useRef<HTMLDivElement>(null);const scene = new THREE.Scene();// 创建一个Three.js相机,包括透视投影、宽高比、近裁剪面和远裁剪面const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.set(15, 12, 8);camera.lookAt(0, 0, 0);// 创建一个Three.js渲染器,包括抗锯齿const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);const robot = generateRobot();const robot2 = generateRobot();robot2.position.x = 6;robot2.position.z = 6;// 将机器人身体添加到场景中scene.add(robot);scene.add(robot2);// 创建一个Three.js方向光,包括颜色、强度const straightLight = new THREE.DirectionalLight(0xffffff, 5);// 设置方向光的位置straightLight.position.set(5, 5, 10);// 将方向光添加到场景中scene.add(straightLight);const starts = generateStarts(200);scene.add(starts);//轨道控制器const controls = new OrbitControls(camera, renderer.domElement);controls.update();const update = () => {requestAnimationFrame(update);robot.rotation.y -= 0.005; //机器人旋转robot2.rotation.y -= 0.005;// 粒子旋转starts.rotation.y -= 0.001;starts.rotation.z += 0.001;starts.rotation.x += 0.001;renderer.render(scene, camera);};update(); //自动更新// 监听组件挂载和卸载useEffect(() => {// 如果div存在,将渲染器dom元素添加到div中if (containerRef.current) {containerRef.current.appendChild(renderer.domElement);// 渲染场景renderer.render(scene, camera);}}, [containerRef]);// 返回div容器,用于存放渲染的Three.js场景return <div ref={containerRef} style={{ width: "100vw", height: "100vh" }}></div>;
}