当前位置:   article > 正文

Three.js机器人与星系动态场景:实现3D渲染与交互式控制

Three.js机器人与星系动态场景:实现3D渲染与交互式控制

内容摘要:使用Three.js库构建了一个交互式的3D场景。组件中创建了一个机器人模型,包括头部、眼睛、触角、身体和四肢,以及两个相同的机器人实例以实现动态效果。场景中还加入了粒子效果,模拟星系环境,增强了视觉效果。通过OrbitControls,用户可以对机器人进行旋转控制。组件在渲染时会根据用户界面的变化动态调整渲染,并在指定的div容器中显示。整体上,这个组件提供了一个基础的3D动画展示和用户交互的框架。

  1. 在React中集成Three.js库,创建一个动态渲染的3D场景。
  2. 定义和渲染多个Three.js几何体,如机器人身体、胳膊、腿、眼睛和触角。
  3. 添加交互性,如旋转和轨道控制器,以提供更好的用户体验。
  4. 创建粒子特效,模拟星系环境,与机器人形成对比。

 一、项目搭建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(材料)

  1. 几何体(Geometry):

    • THREE.SphereGeometry: 用于创建球形几何体。它接受几个参数,包括半径(radius)、宽度分段(widthSegments)、高度分段(heightSegments)、水平起始角度(phiStart)、水平扫描角度(phiLength)、垂直起始角度(thetaStart)和垂直扫描角度(thetaLength)。
    • THREE.CapsuleGeometry: 用于创建胶囊形状的几何体,可以看作是一个圆柱体两端加上半球体。它接受两个参数,分别是半径(radius)和高度(height)。
    • THREE.CylinderGeometry :创建圆柱体。圆柱体由两个圆形底面和一个侧面组成。这个类的作用是定义一个圆柱形状的3D几何体,它可以在 Three.js 的场景中被渲染。
  2. 材质(Material):

    • THREE.MeshStandardMaterial: 用于创建标准网格材质,它提供了多种物理渲染特性,如颜色(color)、粗糙度(roughness)和金属度(metalness)等。
  3. 网格(Mesh):

    • THREE.Mesh: 网格是几何体和材质的组合,可以通过它将几何体渲染到场景中。它接受一个几何体(geometry)和一个材质(material)作为参数。

这里方便解耦,单个方法只生成模型,在调用方法的地方确定模型的位置。

 机器人脑袋

  1. //机器人脑袋
  2. function createHead() {
  3. //SphereGeometry创建球形几何体
  4. const head = new THREE.SphereGeometry(4, 32, 16, 0, Math.PI * 2, 0, Math.PI * 0.5);
  5. const headMaterial = new THREE.MeshStandardMaterial({
  6. color: 0x43b988,
  7. roughness: 0.5,
  8. metalness: 1.0,
  9. });
  10. const headMesh = new THREE.Mesh(head, headMaterial);
  11. return headMesh;
  12. }

机器人触角 

  1. //触角
  2. function generateHorn(y: number, z: number, angle: number) {
  3. //触角 CapsuleGeometry 创建胶囊形状的几何体。胶囊形状可以看作是一个圆柱体两端加上半球体
  4. const line = new THREE.CapsuleGeometry(0.1, 2);
  5. const lineMaterial = new THREE.MeshStandardMaterial({
  6. color: 0x43b988,
  7. roughness: 0.5,
  8. metalness: 1.0,
  9. });
  10. const lineMesh = new THREE.Mesh(line, lineMaterial);
  11. lineMesh.position.y = y;
  12. lineMesh.position.z = z;
  13. lineMesh.rotation.x = angle;
  14. return lineMesh;
  15. }

机器人眼睛 

  1. //机器人眼睛
  2. function generateEye(x: number, y: number, z: number) {
  3. //SphereGeometry创建球形几何体
  4. const eye = new THREE.SphereGeometry(0.5, 32, 16, 0, Math.PI * 2, 0, Math.PI * 2);
  5. const eyeMaterial = new THREE.MeshStandardMaterial({
  6. color: 0x212121,
  7. roughness: 0.5,
  8. metalness: 1.0,
  9. });
  10. const eyeMesh = new THREE.Mesh(eye, eyeMaterial);
  11. eyeMesh.position.x = x;
  12. eyeMesh.position.y = y;
  13. eyeMesh.position.z = z;
  14. return eyeMesh;
  15. }

机器人身体 

  1. //机器人身体
  2. function generateBody() {
  3. //CylinderGeometry第一个参数是上部分圆的半径,第二个参数是下部分圆的半径,第三个参数是高度,材质使用的跟腿一样
  4. const body = new THREE.CylinderGeometry(4, 4, 6);
  5. const bodyMaterial = new THREE.MeshStandardMaterial({
  6. color: 0x43b988,
  7. roughness: 0.5,
  8. metalness: 1.0,
  9. });
  10. const bodyMesh = new THREE.Mesh(body, bodyMaterial);
  11. return bodyMesh;
  12. }

机器人胳膊 

  1. //胳膊、腿
  2. function generateLegs(y: number, z: number) {
  3. const leg1 = new THREE.CapsuleGeometry(1, 4);
  4. const legMaterial1 = new THREE.MeshStandardMaterial({
  5. color: 0x43b988,
  6. roughness: 0.5,
  7. metalness: 1.0,
  8. });
  9. const leg1Mesh = new THREE.Mesh(leg1, legMaterial1);
  10. leg1Mesh.position.y = y;
  11. leg1Mesh.position.z = z;
  12. return leg1Mesh;
  13. }

创建机器人 

  1. //创建机器人
  2. function generateRobot() {
  3. // 创建一个Three.js对象,用于存放机器人
  4. const robot = new THREE.Object3D();
  5. const headMesh = createHead();
  6. headMesh.position.y = 6.5;
  7. robot.add(headMesh);
  8. //眼睛
  9. const leftEye = generateEye(3, 8, -2);
  10. const rightEye = generateEye(3, 8, 2);
  11. robot.add(leftEye);
  12. robot.add(rightEye);
  13. const leftHorn = generateHorn(11, -1, (-Math.PI * 30) / 180);
  14. const rightHorn = generateHorn(11, 1, (Math.PI * 30) / 180);
  15. robot.add(leftHorn);
  16. robot.add(rightHorn);
  17. const body = generateBody();
  18. body.position.y = 4;
  19. robot.add(body);
  20. // 生成机器人左腿
  21. robot.add(generateLegs(0, -2));
  22. // 生成机器人右腿
  23. robot.add(generateLegs(0, 2));
  24. //胳膊
  25. robot.add(generateLegs(3, 5));
  26. robot.add(generateLegs(3, -5));
  27. //物体缩放
  28. robot.scale.x = 0.3;
  29. robot.scale.y = 0.3;
  30. robot.scale.z = 0.3;
  31. return robot;
  32. }

生成粒子场景 

  1. //创建粒子星星
  2. function generateStarts(num: number) {
  3. //制作粒子特效
  4. const starts = new THREE.Object3D();
  5. const obj = new THREE.SphereGeometry(0.2, 3, 3);
  6. const material = new THREE.MeshStandardMaterial({
  7. color: 0x43b988,
  8. roughness: 0.5,
  9. metalness: 5,
  10. });
  11. const mesh = new THREE.Mesh(obj, material);
  12. for (let i = 0; i < num; i++) {
  13. const target = new THREE.Mesh();
  14. target.copy(mesh);
  15. target.position.x = Math.floor(Math.random() * 18 + Math.floor(Math.random() * -18));
  16. target.position.y = Math.floor(Math.random() * 18 + Math.floor(Math.random() * -18));
  17. target.position.z = Math.floor(Math.random() * 18 + Math.floor(Math.random() * -18));
  18. starts.add(target);
  19. }
  20. return starts;
  21. }

 主方法

Scene场景

scene是一个THREE.Scene对象,它是Three.js中场景的容器,用于组织和管理3D对象,如几何体、材质、相机和灯光等。

  • add(object): 这个方法用于将对象(例如机器人、光源、粒子等)添加到场景中。

示例代码中使用了多次:

  1. scene.add(robot);
  2. scene.add(robot2);
  3. scene.add(straightLight);
  4. 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 在这里的作用包括:

  1. 照亮场景:它为场景提供光照,使得场景中的物体可以被看到,并根据材质属性产生不同的光照效果。

  2. 产生阴影DirectionalLight 可以产生阴影效果,使得场景更加真实。物体遮挡光线的地方会形成阴影,有助于表现物体的立体感和空间位置。

  3. 定义光照方向:通过设置 DirectionalLight 的位置和方向,可以定义光线照射到场景的角度,从而影响场景的整体光照效果。

  4. 调整光照强度:可以通过设置 intensity 属性来调整光线的亮度。

OrbitControls 控制器

Three.js 中的 OrbitControls 是一个控制器,它允许用户通过鼠标或触摸事件来控制相机的移动,从而实现对场景的旋转、缩放和平移操作。这个控制器使得用户可以更自然地与3D场景交互。

在使用 OrbitControls 时,需要注意以下几点:

  • 控制器需要与相机和渲染器的DOM元素一起被初始化。
  • 在动画循环中调用 controls.update() 方法来确保控制器可以更新相机状态。
  • 可以通过修改 OrbitControls 的各种属性来自定义控制行为,例如最小/最大缩放距离、旋转限制等。

OrbitControls 是Three.js中非常实用的一个工具,它大大简化了3D场景的用户交互开发过程。

以下是 OrbitControls 的一些主要作用:

  1. 旋转(Rotate):用户可以通过拖动鼠标来旋转相机,从而改变视角。

  2. 缩放(Zoom):通过滚动鼠标滚轮或触摸屏幕进行捏合操作,可以放大或缩小场景。

  3. 平移(Pan):通过按下鼠标右键并拖动,或者在某些触摸设备上通过特定的手势,可以在场景中平移相机。

以下是 OrbitControls 的基本用法:

  1. // 引入OrbitControls
  2. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
  3. // 创建场景、相机和渲染器
  4. const scene = new THREE.Scene();
  5. const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  6. const renderer = new THREE.WebGLRenderer();
  7. renderer.setSize(window.innerWidth, window.innerHeight);
  8. document.body.appendChild(renderer.domElement);
  9. // 设置相机位置
  10. camera.position.set(0, 0, 5);
  11. // 创建OrbitControls实例并传入相机和渲染器
  12. const controls = new OrbitControls(camera, renderer.domElement);
  13. // 创建一些对象添加到场景中
  14. // ...
  15. // 渲染场景
  16. function animate() {
  17. requestAnimationFrame(animate);
  18. controls.update(); // 更新控制器
  19. renderer.render(scene, camera);
  20. }
  21. animate();

position 位置信息

在 Three.js 中,position 属性的作用是用于确定 3D 对象在场景中的位置, 属性对于实现 3D 场景中对象的布局、动画和交互等方面都非常重要。通过设置对象的 position 属性,可以精确地指定该对象在三维空间中的坐标。这使得能够将对象放置在所需的位置,从而构建出具有特定布局和结构的 3D 场景。

例如,如果要将一个立方体放置在场景的特定点(比如 x 坐标为 10,y 坐标为 20,z 坐标为 30),可以这样设置:

  1. const geometry = new THREE.BoxGeometry();
  2. const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
  3. const cube = new THREE.Mesh(geometry, material);
  4. cube.position.set(10, 20, 30);
  5. scene.add(cube);

这样,立方体就会出现在指定的位置上。 

rotation旋转属性

在 Three.js 中,rotation 属性的作用是用于确定 3D 对象在场景中的旋转状态。通过设置对象的 rotation 属性,可以精确地指定该对象在三维空间中的旋转角度。这使得能够将对象旋转到所需的方向,从而构建出具有特定朝向和视角的 3D 场景。

rotation 属性使用的是欧拉角(Euler angles),它包括三个分量:xy 和 z,分别表示绕 X 轴、Y 轴和 Z 轴的旋转角度(以弧度为单位)。

例如,如果要将一个立方体绕 Y 轴旋转 45 度(即 π/4 弧度),可以这样设置:

  1. const geometry = new THREE.BoxGeometry();
  2. const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
  3. const cube = new THREE.Mesh(geometry, material);
  4. cube.rotation.y = Math.PI / 4; // 45 degrees in radians
  5. scene.add(cube);

requestAnimationFrame实现动画 

使用requestAnimationFrame: requestAnimationFrame是浏览器提供的API,用于在每一帧绘制动画。Three.js中的动画通常通过这个API来实现。

  1. function animate() {
  2. requestAnimationFrame(animate);
  3. // 更新对象属性,例如位置、旋转等
  4. cube.rotation.x += 0.01;
  5. cube.rotation.y += 0.01;
  6. // 渲染场景
  7. renderer.render(scene, camera);
  8. }
  9. animate();

useEffect 

在React组件中,useEffect 钩子用于处理副作用操作,比如数据获取、订阅、手动更改DOM等。useEffect 钩子的主要作用是在组件挂载后将 Three.js 渲染器的 DOM 元素添加到指定的容器中,并在组件卸载时进行清理。

  1. 组件挂载时添加渲染器 DOM 元素

    • useEffect 钩子在组件挂载时执行,确保 containerRef.current 已经指向了实际的 DOM 元素。
    • 通过 containerRef.current.appendChild(renderer.domElement),将 Three.js 渲染器的 DOM 元素添加到指定的容器中。
  2. 确保渲染器 DOM 元素正确添加

    • 如果不使用 useEffect,直接在组件渲染时添加渲染器 DOM 元素,可能会导致 containerRef.current 为 null,因为此时 DOM 元素可能还未挂载到页面上。
  1. /**
  2. * 创建一个Three.js场景,包括相机和渲染器
  3. */
  4. function Robot() {
  5. // 创建一个div容器,用于存放渲染的Three.js场景
  6. const containerRef = useRef<HTMLDivElement>(null);
  7. const scene = new THREE.Scene();
  8. // 创建一个Three.js相机,包括透视投影、宽高比、近裁剪面和远裁剪面
  9. const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  10. camera.position.set(15, 12, 8);
  11. camera.lookAt(0, 0, 0);
  12. // 创建一个Three.js渲染器,包括抗锯齿
  13. const renderer = new THREE.WebGLRenderer({ antialias: true });
  14. renderer.setSize(window.innerWidth, window.innerHeight);
  15. const robot = generateRobot();
  16. const robot2 = generateRobot();
  17. robot2.position.x = 6;
  18. robot2.position.z = 6;
  19. // 将机器人身体添加到场景中
  20. scene.add(robot);
  21. scene.add(robot2);
  22. // 创建一个Three.js方向光,包括颜色、强度
  23. const straightLight = new THREE.DirectionalLight(0xffffff, 5);
  24. // 设置方向光的位置
  25. straightLight.position.set(5, 5, 10);
  26. // 将方向光添加到场景中
  27. scene.add(straightLight);
  28. const starts = generateStarts(200);
  29. scene.add(starts);
  30. //轨道控制器
  31. const controls = new OrbitControls(camera, renderer.domElement);
  32. controls.update();
  33. const update = () => {
  34. requestAnimationFrame(update);
  35. robot.rotation.y -= 0.005; //机器人旋转
  36. robot2.rotation.y -= 0.005;
  37. // 粒子旋转
  38. starts.rotation.y -= 0.001;
  39. starts.rotation.z += 0.001;
  40. starts.rotation.x += 0.001;
  41. renderer.render(scene, camera);
  42. };
  43. update(); //自动更新
  44. // 监听组件挂载和卸载
  45. useEffect(() => {
  46. // 如果div存在,将渲染器dom元素添加到div中
  47. if (containerRef.current) {
  48. containerRef.current.appendChild(renderer.domElement);
  49. // 渲染场景
  50. renderer.render(scene, camera);
  51. }
  52. }, [containerRef]);
  53. // 返回div容器,用于存放渲染的Three.js场景
  54. return <div ref={containerRef} style={{ width: "100vw", height: "100vh" }}></div>;
  55. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/821556
推荐阅读
相关标签
  

闽ICP备14008679号