场景层级基础概念

(一)场景(Scene)对象

在 Three.js 中,THREE.Scene是整个 3D 场景的容器,它是场景层级结构的根节点。可以将场景想象成一个空的舞台,所有的 3D 元素(如几何体、光源、相机等)都要被添加到这个舞台上才能在最终渲染中呈现。

创建一个场景非常简单,只需如下代码:

js 复制代码
const scene = new THREE.Scene();

这个scene对象将作为所有后续添加到场景中的对象的父级。

(二)对象 3D(Object3D)基类

THREE.Object3D是 Three.js 中几乎所有对象的基类,包括THREE.Scene、THREE.Mesh(网格,用于表示 3D 物体)、THREE.Group(组,用于将多个对象组合在一起)、THREE.Light(光源)等。这意味着这些对象都继承了Object3D的属性和方法,其中与场景层级密切相关的是add、remove方法以及children属性。

  1. add 方法:用于将一个或多个子对象添加到当前对象中。例如,如果我们有一个场景对象scene和一个网格对象mesh,可以通过以下方式将mesh添加到scene中:
js 复制代码
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
  1. remove 方法:与add方法相反,用于从当前对象中移除一个或多个子对象。假设我们要从scene中移除刚才添加的mesh,可以这样做:
js 复制代码
scene.remove(mesh);
  1. children 属性:该属性是一个数组,包含了当前对象的所有直接子对象。通过访问children属性,可以遍历和操作当前对象的子对象。例如,要遍历scene中的所有子对象,可以使用以下代码:
js 复制代码
scene.children.forEach((child) => {
    // 在这里对每个子对象进行操作
    console.log(child);
});

(三)组(Group)对象

THREE.Group对象是Object3D的一个子类,它的主要作用是将多个对象组合成一个逻辑单元。通过将相关的对象组合到一个组中,可以方便地对这些对象进行统一的变换(如移动、旋转、缩放)和管理。

例如,我们要创建一个由多个部分组成的复杂物体(如一辆汽车,包含车身、车轮等),可以将每个部分作为一个独立的Mesh对象,然后将这些Mesh对象添加到一个Group对象中。

js 复制代码
// 创建车身网格
const bodyGeometry = new THREE.BoxGeometry(1, 0.5, 2);
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial);
// 创建车轮网格
const wheelGeometry = new THREE.CylinderGeometry(0.1, 0.1, 0.2, 32);
const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });
const wheelMesh1 = new THREE.Mesh(wheelGeometry, wheelMaterial);
const wheelMesh2 = new THREE.Mesh(wheelGeometry, wheelMaterial);
const wheelMesh3 = new THREE.Mesh(wheelGeometry, wheelMaterial);
const wheelMesh4 = new THREE.Mesh(wheelGeometry, wheelMaterial);
// 将车轮网格放置在合适位置
wheelMesh1.position.set(-0.8, -0.2, 0.8);
wheelMesh2.position.set(0.8, -0.2, 0.8);
wheelMesh3.position.set(-0.8, -0.2, -0.8);
wheelMesh4.position.set(0.8, -0.2, -0.8);
// 创建组对象并添加车身和车轮
const carGroup = new THREE.Group();
carGroup.add(bodyMesh);
carGroup.add(wheelMesh1);
carGroup.add(wheelMesh2);
carGroup.add(wheelMesh3);
carGroup.add(wheelMesh4);
// 最后将组对象添加到场景中
scene.add(carGroup);

这样,当我们对carGroup进行移动、旋转或缩放操作时,组内的所有对象(车身和车轮)都会相应地发生变化。

二、场景层级中的坐标系统

(一)世界坐标(World Coordinates)

世界坐标是整个场景的全局坐标系统。场景中的每个对象都有一个相对于世界坐标系原点(0, 0, 0)的位置。当对象直接添加到Scene对象下时,它的位置就是以世界坐标来表示的。

例如,创建一个简单的立方体并将其添加到场景中:

js 复制代码
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
cubeMesh.position.set(2, 1, 3); // 在世界坐标中设置位置
scene.add(cubeMesh);

这里cubeMesh的位置(2, 1, 3)就是相对于世界坐标系原点的位置。

(二)局部坐标(Local Coordinates)

局部坐标是相对于对象自身的坐标系统。每个对象都有自己的局部坐标系,其原点位于对象自身的中心(对于大多数几何体而言)。当一个对象作为另一个对象的子对象时,它的位置、旋转和缩放都是相对于其父对象的局部坐标系来定义的。

回到前面汽车的例子,车轮作为carGroup的子对象,它们的位置(如wheelMesh1.position.set(-0.8, -0.2, 0.8))就是相对于carGroup的局部坐标系的。这意味着当carGroup在世界坐标系中移动、旋转时,车轮会基于它们在carGroup局部坐标系中的位置相应地移动和旋转,而无需重新计算它们在世界坐标系中的绝对位置。

理解世界坐标和局部坐标的转换关系非常重要。Three.js 提供了一些方法来进行坐标转换,例如localToWorld方法可以将局部坐标转换为世界坐标,worldToLocal方法则相反。假设我们有一个对象obj,要将其局部坐标(1, 1, 1)转换为世界坐标,可以这样做:

js 复制代码
const localVector = new THREE.Vector3(1, 1, 1);
const worldVector = localVector.clone();
obj.localToWorld(worldVector);
console.log(worldVector); // 输出转换后的世界坐标

三、场景层级与渲染

(一)渲染顺序

Three.js 在渲染场景时,会从场景层级结构的根节点(即Scene对象)开始,递归地遍历所有子对象,并按照它们在层级结构中的顺序进行渲染。这意味着先添加到场景中的对象会先被渲染,后添加的对象会在上面进行渲染。

例如,如果我们先添加一个蓝色的立方体,然后在相同位置添加一个红色的球体,那么球体将会覆盖在立方体之上显示,因为球体是后被渲染的。

js 复制代码
// 先添加蓝色立方体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff });
const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cubeMesh);
// 后添加红色球体
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphereMesh.position.set(0, 0, 0);
scene.add(sphereMesh);

在这个例子中,球体由于后添加到场景中,所以会显示在立方体之上。

(二)可见性与剔除

场景层级结构对于控制对象的可见性和剔除(culling)非常有用。每个Object3D对象都有一个visible属性,通过设置该属性可以控制对象是否在渲染中显示。当一个对象的visible属性设置为false时,它及其所有子对象都不会被渲染。

js 复制代码
const group = new THREE.Group();
const mesh1 = new THREE.Mesh(geometry1, material1);
const mesh2 = new THREE.Mesh(geometry2, material2);
group.add(mesh1);
group.add(mesh2);
scene.add(group);
// 设置组不可见,组内的mesh1和mesh2都不会被渲染
group.visible = false;

此外,Three.js 的渲染器会自动进行一些剔除操作,以提高渲染效率。例如,对于背对摄像机的面(默认情况下,Three.js 使用背面剔除),渲染器不会对其进行渲染。在复杂场景中,合理利用场景层级结构和对象的可见性设置,可以进一步优化渲染性能,减少不必要的渲染计算。

四、场景层级的遍历与查找

(一)遍历场景层级

在开发过程中,有时需要遍历整个场景层级结构,对每个对象进行特定的操作。由于场景层级是一个树状结构,我们可以使用递归算法来实现遍历。

下面是一个简单的递归函数,用于遍历场景中的所有对象并打印它们的名称(假设每个对象都有name属性):

js 复制代码
function traverseScene(node) {
    console.log(node.name);
    node.children.forEach((child) => {
        traverseScene(child);
    });
}
// 从场景根节点开始遍历
traverseScene(scene);

这个函数首先打印当前节点的名称,然后递归地调用自身来遍历当前节点的所有子节点。通过这种方式,可以访问到场景中的每一个对象。

(二)查找特定对象

在复杂的场景中,可能需要根据特定的条件查找某个对象。例如,根据对象的名称查找对象。我们可以在遍历场景层级的基础上实现对象查找功能。

js 复制代码
function findObjectByName(node, name) {
    if (node.name === name) {
        return node;
    }
    for (let i = 0; i < node.children.length; i++) {
        const found = findObjectByName(node.children[i], name);
        if (found) {
            return found;
        }
    }
    return null;
}
// 查找名称为"specificMesh"的对象
const specificMesh = findObjectByName(scene, "specificMesh");
if (specificMesh) {
    // 对找到的对象进行操作
    specificMesh.material.color.set(0xff00ff);
}

这个函数在遍历场景层级的过程中,检查每个对象的名称是否与目标名称匹配。如果匹配,则返回该对象;如果在当前节点及其子节点中都没有找到匹配的对象,则返回null。

通过对 Three.js 场景层级的深入理解,包括场景层级的基础构成、坐标系统、渲染相关以及遍历查找等方面,开发者能够更加高效地构建、管理和优化复杂的 3D 场景,实现丰富的 3D 交互体验。

相关推荐
百万蹄蹄向前冲32 分钟前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5811 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter2 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友2 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry2 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog4 小时前
低端设备加载webp ANR
前端·算法
LKAI.4 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi