Three.js-硬要自学系列9 (Vector3与模型位置、遍历树结构,查询节点、移除隐藏模型对象)

本章主要学习知识点

  • 了解three.js中的组对象group,并练习一些基本操作
  • 练习遍历模型树结构、查询模型节点
  • 了解本地坐标与世界坐标概念
  • 练习修改模型相对于局部坐标原点的位置
  • 练习移除隐藏模型对象操作

模型建组group

在Three.js 中,模型建组 是通过THREE.Group类实现的,它可以将多个模型对象组织成一个逻辑整体,便于统一管理和操作,想象一下如果场景中的模型对象都散乱放置,那该有多恐怖~

树形结构

Three.js 场景采用树状结构,Group继承自Object3D,可包含任意子对象(Mesh、Light、Camera等)

js 复制代码
const group = new THREE.Group();
group.add(mesh1, mesh2, light);
scene.add(group);

坐标继承

组内所有子对象使用组坐标系,当组发生变换时,子对象会跟随整体移动/旋转/缩放,通俗点说就是老大一动小弟全都要动

js 复制代码
group.position.set(30, 0, 0); // 所有子对象整体右移30单位

实际操作下 ,创建2个立方体,并设置cube2沿X轴向右移动5个单位(为了区别,将cube进行自旋转)

js 复制代码
cube = new THREE.Mesh( geometry, material );
cube2 = new THREE.Mesh( geometry, material)
cube2.position.x = 5;

接下来新建一个组对象,将cube和cube2放入其中编组,同时将gounp组对象沿Z轴平移5个单位,看看会发生什么

js 复制代码
// 创建一个THREE.Group对象
const group = new THREE.Group();
group.add(cube,cube2)
group.translateZ(5)
scene.add(group)

从图中可发现,cube和cube2都被沿Z轴平移了5个单位,只就是老大一动小弟全都要动

尝试打印组对象下的children属性,看看有什么

js 复制代码
console.log(group.children);

gounp下包含2个mesh,因为我们为cube和cube2两个mesh进行了编组,但到目前为止,我们并不知道这个组叫什么,让我们来给组起个名字

js 复制代码
//给组对象命名
group.name = 'cubeMeshGroup';
console.log(group);

组与组之间嵌套,形成庞大的树形结构

假设我们正在构建一个汽车模型时,就需要对每个部件进行编组,再将这些组再进行组合,最终形成一个完整的车辆, 如下是一个简单的示例

js 复制代码
const car = new THREE.Group();
const chassis = new THREE.Group(); // 底盘组 
const wheels = new THREE.Group(); // 车轮组 

// 构建底盘 
chassis.add(bodyMesh, seatMesh, steeringWheel);

// 构建车轮(子组嵌套) 
const wheelFL = createWheel();
const wheelFR = createWheel();
wheels.add(wheelFL, wheelFR);
car.add(chassis, wheels);

遍历树结构

在开始遍历之前先要来了解下,树结构的基本组成

树结构的基本组成

  • 树根Scene场景对象是整个树的根节点
  • 树枝Group组对象或复杂模型,可包含子对象
  • 树叶: 单个模型、光源、相机等

好,我们先来搭建一个场景

js 复制代码
const group = new THREE.Group();
group.name = '高楼';
for(let i=0; i<6; i++) {
  // 创建几何体
  const geometry = new THREE.BoxGeometry(1,1,1);
  // 兰伯特材质
  const material = new THREE.MeshLambertMaterial({color: 'deepskyblue'})
  const mesh = new THREE.Mesh(geometry, material)
  mesh.position.x = i*2;
  group.add(mesh)
  mesh.name = i+1+'号楼';  //给每个mesh对象命名
}
group.position.y = 6;

const group2 = new THREE.Group();
group2.name = '洋房'
for(let i=0; i<6; i++) {
  // 创建几何体
  const geometry = new THREE.BoxGeometry(1,1,1);
  // 兰伯特材质
  const material = new THREE.MeshLambertMaterial({color: 'deeppink'})
  const mesh = new THREE.Mesh(geometry, material)
  mesh.position.x = i*2;
  group2.add(mesh)
  mesh.name = i+1+'号楼';   //给每个mesh对象命名
}

const model = new THREE.Group();
model.name = '小区房子'
model.add( group, group2 )
scene.add(model)

这里我创建了两个组对象,分别为高楼(蓝色立方体)、洋房(红色立方体),每个组对象内部包含6个命名立方体,将这两个组对象添加到了名为小区房子的组对象中,接下来我们就可以使用three.js中内置的遍历方法进行遍历,注意我在遍历到子元素为mesh时将他们的材质颜色改为绿色

js 复制代码
model.traverse(obj=> {
  if(obj.isMesh) {
    obj.material.color.set('green')
  }
})

traverse是一种深度优先遍历,很适合用于批量操作

深度优先遍历和广度优先遍历策略

  • 深度优先遍历(家族长辈优先)
    • 先访问父节点,再递归访问所有子节点
js 复制代码
function traverse(obj) {
    console.log(obj.name); // 处理当前节点 
    obj.children.forEach(child => traverse(child)); // 递归子节点 
} 
traverse(scene); // 从根节点开始遍历

适用于批量修改材质、统计对象数量等

  • 广度优先遍历(家族同辈优先)
    • 逐层访问,使用队列结构实现
js 复制代码
function traverseBFS(root) { 
    const queue = [root]; // 初始化队列 
    while (queue.length > 0) { 
        const obj = queue.shift(); // 取出队首元素 
        console.log(obj.name); 
        queue.push(...obj.children); // 子节点入队尾 
   } 
}

适用于快速查找最近物体、层级分析

查找某个具体的模型

通过getObjectByName可以获取对应的模型

js 复制代码
const findMesh = model.getObjectByName('1号楼')
findMesh.material.color.set('red')

找到模型后,将模型的材质改为红色,如下图所示

哦,太棒了,又学到了

本地坐标与世界坐标

我又想打比方了, 假设一个快递站有3层楼,每层楼有5个柜子

  • 本地坐标: 每个柜子相对于所在楼层的坐标,比如2楼第3个柜子的本地坐标是(0,0,3)(以2楼入口为原点)
  • 世界坐标: 整个快递站的全局坐标, 比如2楼入口的世界坐标是(0,5,0),那么该柜子的世界坐标就是(0,5,3)

在Three.js世界中,mesh.position 获取的是本地坐标,mesh.getWorldPosition() 获取的是世界坐标

两种坐标系之间的本质差异

特性 本地坐标 世界坐标
参照物 父容器(如Group) 场景原点(Scene原点)
获取方式 object.position object.getWorldPosition()
变化条件 父物体移动时自动保持相对关系 任何物体移动都会改变
应用场景 部件组装、相对位置调整 碰撞检测、全局定位

获取世界坐标和本地局部坐标

js 复制代码
// 获取世界坐标
const worldPosition = new THREE.Vector3();
findMesh.getWorldPosition(worldPosition)
console.log(worldPosition);

// 获取本地局部坐标
const localPosition = findMesh.position;
console.log(localPosition);

获取模型的父级以及子级

js 复制代码
// 获取父级
const parent = findMesh.parent
console.log(parent);

// 获取子级
const children = parent.children
console.log(children);

移除隐藏模型对象

移除模型(彻底删除)

通过 .remove() 方法将模型从场景中删除,释放内存空间。

js 复制代码
//删除组对象下的cube2模型
group.remove(cube2)

注意:

  • 模型被彻底删除,无法再显示。
  • 如果模型有材质或几何体,需要手动调用.dispose() 释放 GPU 内存。

隐藏模型(临时不可见)

通过 .visible 属性让模型暂时不可见,但仍存在于场景中。

js 复制代码
// 隐藏cube模型
cube.visible = false;

注意:

  • 模型仍在场景中,可以随时通过 model.visible = true 恢复显示
  • 如果多个模型共享同一材质,设置材质 .visible 会隐藏所有关联模型

以上案例均可在案例中心查看体验

THREE 案例中心

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax