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)作为参数。

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

 机器人脑袋

//机器人脑袋
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 在这里的作用包括:

  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 的基本用法:

// 引入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),它包括三个分量:xy 和 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 radians

scene.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 元素添加到指定的容器中,并在组件卸载时进行清理。

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

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

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三月的一天

你的鼓励将是我前进的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值