Three.js 模型树结构与节点查询学习笔记

Three.js 模型树结构与节点查询学习笔记

1. 场景图(Scene Graph)概念

什么是场景图?

  • Three.js使用树形结构来组织3D对象
  • Scene是根节点,包含所有子对象
  • 对象可以嵌套形成父子关系
javascript 复制代码
Scene (根节点)
├── Group1 (组节点)
│   ├── Mesh1 (网格对象)
│   ├── Mesh2
│   └── Group1-1
└── Group2
    ├── Mesh3
    └── Mesh4

2. 递归遍历方法

2.1 scene.traverse() - 递归遍历所有后代

javascript 复制代码
scene.traverse((object) => {
  // 这个回调函数会对每个对象执行
  console.log('遍历到的对象:', object.name);
  console.log('对象类型:', object.type);
  console.log('对象ID:', object.id);
});

2.2 筛选特定类型的对象

javascript 复制代码
scene.traverse((object) => {
  // 只处理网格对象
  if (object.isMesh) {
    console.log("网格对象:", object.name);
    // 可以修改材质、位置等属性
    object.material.color.set(0xff0000);
  }
  
  // 只处理灯光对象
  if (object.isLight) {
    console.log("灯光对象:", object.name);
  }
  
  // 只处理组对象
  if (object.isGroup) {
    console.log("组对象:", object.name);
  }
});

3. 节点查询方法

3.1 通过名称查询

javascript 复制代码
// 获取单个对象(返回第一个匹配的)
const building = scene.getObjectByName("1号楼");
if (building) {
  building.material.color.set(0xffffff);
}

// 获取所有同名对象
const allBuildings = [];
scene.traverse(obj => {
  if (obj.name.includes("号楼")) {
    allBuildings.push(obj);
  }
});

3.2 通过ID查询

javascript 复制代码
// 每个Three.js对象都有唯一ID
const objectById = scene.getObjectById(12345);

3.3 通过属性查询

javascript 复制代码
// 查找所有特定材质的对象
const redBuildings = [];
scene.traverse(obj => {
  if (obj.isMesh && obj.material.color.getHex() === 0xff0000) {
    redBuildings.push(obj);
  }
});

4. 实用查询函数示例

4.1 查找所有后代网格

javascript 复制代码
function findAllMeshes(scene) {
  const meshes = [];
  scene.traverse(obj => {
    if (obj.isMesh) {
      meshes.push(obj);
    }
  });
  return meshes;
}

const allMeshes = findAllMeshes(scene);
console.log("场景中所有网格:", allMeshes);

4.2 按名称模式查找

javascript 复制代码
function findObjectsByNamePattern(scene, pattern) {
  const results = [];
  scene.traverse(obj => {
    if (obj.name && obj.name.match(pattern)) {
      results.push(obj);
    }
  });
  return results;
}

// 查找所有以"号楼"结尾的对象
const buildings = findObjectsByNamePattern(scene, /号楼$/);

4.3 查找对象的父级链

javascript 复制代码
function getParentChain(object) {
  const chain = [];
  let current = object;
  
  while (current.parent) {
    chain.unshift(current.parent.name || '无名对象');
    current = current.parent;
  }
  
  return chain;
}

const obj = scene.getObjectByName("1号楼");
console.log("父级链:", getParentChain(obj));
// 输出: ["一号楼群", "Scene"]

5. 实际应用场景

5.1 批量修改材质

javascript 复制代码
// 批量修改所有楼的材质
function changeAllBuildingMaterials(scene, color) {
  scene.traverse(obj => {
    if (obj.isMesh && obj.name.includes("号楼")) {
      obj.material.color.set(color);
    }
  });
}

changeAllBuildingMaterials(scene, 0x00ff00); // 全部变绿色

5.2 选择性隐藏对象

javascript 复制代码
// 隐藏特定名称的对象
function hideObjectsByName(scene, namePattern) {
  scene.traverse(obj => {
    if (obj.name && obj.name.match(namePattern)) {
      obj.visible = false;
    }
  });
}

hideObjectsByName(scene, /^[0-3]号楼/); // 隐藏0-3号楼

5.3 统计场景信息

javascript 复制代码
function analyzeScene(scene) {
  const stats = {
    totalObjects: 0,
    meshes: 0,
    lights: 0,
    groups: 0,
    materials: new Set()
  };
  
  scene.traverse(obj => {
    stats.totalObjects++;
    
    if (obj.isMesh) {
      stats.meshes++;
      if (obj.material) {
        stats.materials.add(obj.material);
      }
    }
    if (obj.isLight) stats.lights++;
    if (obj.isGroup) stats.groups++;
  });
  
  return {
    ...stats,
    uniqueMaterials: stats.materials.size
  };
}

console.log("场景分析:", analyzeScene(scene));

6. 调试技巧

6.1 打印完整场景结构

javascript 复制代码
function printSceneStructure(scene, indent = 0) {
  const spaces = '  '.repeat(indent);
  
  scene.children.forEach(child => {
    console.log(`${spaces}${child.type}: ${child.name || '无名'} (ID: ${child.id})`);
    
    if (child.children && child.children.length > 0) {
      printSceneStructure(child, indent + 1);
    }
  });
}

printSceneStructure(scene);

7. 注意事项

  1. 性能考虑:遍历大型场景时注意性能影响
  2. 内存管理:及时清理不再需要的对象引用
  3. 名称唯一性:尽量使用唯一名称便于查询
  4. 错误处理:查询时检查对象是否存在

8. 总结

  • scene.traverse() 是强大的递归遍历工具
  • getObjectByName() 适合精确查询单个对象
  • 组合使用这些方法可以实现复杂的场景管理
  • 理解场景树结构是掌握Three.js的关键

这些技能在游戏开发、3D编辑器、数据可视化等场景中都非常有用!

javascript 复制代码
<template>
  <div class="container" ref="containerRef"></div>
</template>

<script setup>
import { onMounted, ref } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

const containerRef = ref(null);

// 创建3D场景对象Scene
const scene = new THREE.Scene();
// 实例化一个透视投影相机对象
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 实例化一个WebGL渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });

// 设置Canvas画布尺寸
renderer.setSize(window.innerWidth, window.innerHeight);

// 创建第一组楼群
const group1 = new THREE.Group();
group1.name = '一号楼群';

for (let i = 0; i < 5; i++) {
  const geometry = new THREE.BoxGeometry(0.3, 0.8, 0.3); // 宽度, 高度, 深度
  const material = new THREE.MeshLambertMaterial({
    color: Math.random() * 0xffffff,
  });
  const building = new THREE.Mesh(geometry, material);
  building.position.x = i * 0.6; // X轴方向排列
  building.position.y = 0.4;     // 将楼放在地面上(高度的一半)
  building.name = i + "号楼";
  group1.add(building);
}

// 创建第二组楼群
const group2 = new THREE.Group();
group2.name = '二号楼群';

for (let i = 0; i < 5; i++) {
  const geometry = new THREE.BoxGeometry(0.3, 1.0, 0.3);
  const material = new THREE.MeshLambertMaterial({
    color: Math.random() * 0xffffff,
  });
  const building = new THREE.Mesh(geometry, material);
  building.position.x = i * 0.6;
  building.position.z = -0.8;    // Z轴方向排列
  building.position.y = 0.5;     // 将楼放在地面上(高度的一半)
  building.name = i + "号楼";
  group2.add(building);
}

scene.add(group1, group2);

// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);

// 方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 5);
scene.add(directionalLight);

// 遍历场景中的所有对象
scene.traverse((obj) => {
  // 判断对象是否为网格模型对象
  if (obj.isMesh) {
    console.log("对象名称:" + obj.name);
    //修改模型颜色
    // obj.material.color.set(0xff0000);
  }
});

// 通过名称获取特定对象并修改颜色
const obj = scene.getObjectByName("1号楼");
if (obj) {
  obj.material.color.set(0xffffff); // 将1号楼设置为白色
}

// 坐标系辅助
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 设置相机位置
camera.position.set(3, 3, 3);
camera.lookAt(0, 0, 0);

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

animate();

onMounted(() => {
  const controls = new OrbitControls(camera, containerRef.value);
  controls.enableDamping = true;
  containerRef.value.appendChild(renderer.domElement);
});
</script>
相关推荐
车传新2 小时前
Javascript
javascript
天若有情6732 小时前
【c++】手撸C++ Promise:从零实现通用异步回调组件,支持链式调用+异常安全
开发语言·前端·javascript·c++·promise
hadage2332 小时前
--- JavaScript 的一些常用语法总结 ---
java·前端·javascript
jason_yang3 小时前
vue3中createApp多个实例共享状态
javascript·vue.js
_瑶瑶_3 小时前
浅记一下ElementPlus中的虚拟化表格(el-table-v2)的简单使用
前端·javascript
拉不动的猪3 小时前
Axios 请求取消机制详解
前端·javascript·面试
Heo5 小时前
关于XSS和CSRF,面试官更喜欢这样的回答!
前端·javascript·面试
徐小夕5 小时前
耗时一周,我把可视化+零代码+AI融入到了CRM系统,使用体验超酷!
javascript·vue.js·github
5***a9755 小时前
React Native性能优化技巧
javascript·react native·react.js