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

相关推荐
读心悦4 分钟前
CSS3 选择器完全指南:从基础到高级的元素定位技术
前端·css·css3
_龙衣1 小时前
将 swagger 接口导入 apifox 查看及调试
前端·javascript·css·vue.js·css3
进取星辰2 小时前
25、Tailwind:魔法速记术——React 19 样式新思路
前端·react.js·前端框架
x-cmd3 小时前
[250512] Node.js 24 发布:ClangCL 构建,升级 V8 引擎、集成 npm 11
前端·javascript·windows·npm·node.js
夏之小星星3 小时前
el-tree结合checkbox实现数据回显
前端·javascript·vue.js
crazyme_63 小时前
前端自学入门:HTML 基础详解与学习路线指引
前端·学习·html
撸猫7913 小时前
HttpSession 的运行原理
前端·后端·cookie·httpsession
亦世凡华、4 小时前
Rollup入门与进阶:为现代Web应用构建超小的打包文件
前端·经验分享·rollup·配置项目·前端分享
Bl_a_ck4 小时前
【React】Craco 简介
开发语言·前端·react.js·typescript·前端框架
为美好的生活献上中指5 小时前
java每日精进 5.11【WebSocket】
java·javascript·css·网络·sql·websocket·网络协议