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 案例中心

相关推荐
天天扭码3 分钟前
一分钟解决一道算法题——矩阵置零
前端·算法·面试
抹茶san15 分钟前
el-tabs频繁切换tab引发的数据渲染混淆
前端·vue.js·element
Captaincc20 分钟前
关于MCP最值得看的一篇:MCP创造者聊MCP的起源、架构优势和未来
前端·mcp
小小小小宇23 分钟前
记录老项目Vue 2使用VueUse
前端
vvilkim23 分钟前
React Server Components 深度解析:下一代 React 渲染模式
前端·react.js·前端框架
HBR666_33 分钟前
vue3 excel文件导入
前端·excel
天天扭码37 分钟前
偶遇天才算法题 | 拼劲全力,无法战胜 😓
前端·算法·面试
小菜刀刀41 分钟前
文件包含漏洞,目录遍历漏洞,CSRF,SSRF
前端·csrf
anyup_前端梦工厂1 小时前
React 单一职责原则:优化组件设计与提高可维护性
前端·javascript·react.js
天天扭码1 小时前
面试官:算法题”除自身以外数组的乘积“ 我:😄 面试官:不能用除法 我:😓
前端·算法·面试