• 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. }

  • 相关阅读:
    Java | UDP协议
    云数据库 GaussDB(for Influx) 解密第十一期:让智能电网中时序数据处理更高效
    drf-过滤、排序、异常处理、自封装Response
    uniapp 开发App 网络异常如何处理
    laravel8配合jwt
    关于一篇端口聚合配置的笔记
    收集表单数据Vue
    ACL2022国内部分论文分享内容总结1
    git中submodule子模块的添加、使用和删除
    九、忘记密码功能的实现
  • 原文地址:https://blog.csdn.net/qq_36384657/article/details/140018494