云图三维 | Three.js 层级模型

2,038 阅读5分钟

云图三维 连接你·创造的世界 致力于打造国内第一家集查看、建模、装配和渲染于一体的“云端CAD”协作设计平台。

应读者的要求,希望我们成立一个专业的、面向成渝地区的前端开发人员的webgl、Threejs行业QQ交流群,便于大家讨论问题。群里有研究webgl、Threejs大佬哦,欢迎大家加入!——点击链接加入群聊【three.js/webgl重庆联盟群】:jq.qq.com/?_wv=1027&k…

引言

几乎所有的前端界面开发都是采用大致相同的一种层级关系模型。不管是web中div标签,还是2D游戏中的node,亦或3D应用中的Object3D。他们都是按照树型结构来构建彼此关系,不管是从项目的根节点到应用末梢的具体功能元素,还是场景、页面到游戏角色、菜单表格,都是一以贯之。

比如一辆车,在Threejs中你可以使用一个网格模型去描述车上面的一个零件,多个零件就需要多个网格模型表示,这些网格模型之间就会构成父子或兄弟关系,从而形成一个层级结构。在机械、建筑相关的Web3D应用中,通常会用到层级模型的知识,一个层级模型就是一本书的目录一样。

本文主要目的是帮助你建立Threejs层级模型的概念,通过Threejs的组对象Group可以组织各个模型,构成一个层级结构。你也可以参考前端中DOM树去理解,Threejs一个一个的模型对象就像HTML元素一样可以组成一个树结构,可以通过特定id或name属性选择某个或某些模型对象。

在具体开发过程中,3D美术给你一个包含多个网格模型对象的层级模型,你可能需要操作某个网格模型,这时候3D美术只要通过对模型命名标记模型,那么对于程序员来说,直接调用Threejs的某个方法就可以遍历整个模型,找到某个你想要操作的模型对象。

image.png

层级模型节点命名、查找、遍历

Threejs场景对象Scene和各种子对象构成的层级模型就是一个树结构。如果你有一定的算法基础对树结构肯定会非常了解,如果你了解前端的DOM树结构也非常有助理解,如果这些都不了解也没有关系,可以直接理解下面的源码。

下面通过Three.js的一个类Group来介绍Threejs层级模型的概念,如果你对WebGL层级模型已经有一定的概念,直接把重点放在Group的了解上,如果没有层级模型的概念,就借着对Three.js APIGroup的介绍了解下该概念。

模型命名(.name属性)

在层级模型中可以给一些模型对象通过.name属性命名进行标记。

group.add(Mesh)
// 网格模型命名
Mesh.name = "眼睛"
// mesh父对象对象命名
group.name = "头"

树结构层级模型

实际开发的时候,可能会加载外部的模型,然后从模型对象通过节点的名称.name查找某个子对象,为了大家更容易理解,这里不加载外部模型,直接通过代码创建一个非常简易的机器人模型,然后在机器人基础上进行相关操作。

// 头部网格模型和组
var headMesh = sphereMesh(10, 0, 0, 0);
headMesh.name = "脑壳"
var leftEyeMesh = sphereMesh(1, 8, 5, 4);
leftEyeMesh.name = "左眼"
var rightEyeMesh = sphereMesh(1, 8, 5, -4);
rightEyeMesh.name = "右眼"
var headGroup = new THREE.Group();
headGroup.name = "头部"
headGroup.add(headMesh, leftEyeMesh, rightEyeMesh);
// 身体网格模型和组
var neckMesh = cylinderMesh(3, 10, 0, -15, 0);
neckMesh.name = "脖子"
var bodyMesh = cylinderMesh(14, 30, 0, -35, 0);
bodyMesh.name = "腹部"
var leftLegMesh = cylinderMesh(4, 60, 0, -80, -7);
leftLegMesh.name = "左腿"
var rightLegMesh = cylinderMesh(4, 60, 0, -80, 7);
rightLegMesh.name = "右腿"
var legGroup = new THREE.Group();
legGroup.name = "腿"
legGroup.add(leftLegMesh, rightLegMesh);
var bodyGroup = new THREE.Group();
bodyGroup.name = "身体"
bodyGroup.add(neckMesh, bodyMesh, legGroup);
// 人Group
var personGroup = new THREE.Group();
personGroup.name = "人"
personGroup.add(headGroup, bodyGroup)
personGroup.translateY(50)
scene.add(personGroup);

// 球体网格模型创建函数
function sphereMesh(R, x, y, z) {
  var geometry = new THREE.SphereGeometry(R, 25, 25); //球体几何体
  var material = new THREE.MeshPhongMaterial({
    color: 0x0000ff
  }); //材质对象Material
  var mesh = new THREE.Mesh(geometry, material); // 创建网格模型对象
  mesh.position.set(x, y, z);
  return mesh;
}
// 圆柱体网格模型创建函数
function cylinderMesh(R, h, x, y, z) {
  var geometry = new THREE.CylinderGeometry(R, R, h, 25, 25); //球体几何体
  var material = new THREE.MeshPhongMaterial({
    color: 0x0000ff
  }); //材质对象Material
  var mesh = new THREE.Mesh(geometry, material); // 创建网格模型对象
  mesh.position.set(x, y, z);
  return mesh;
}

image.png

递归遍历方法.traverse()

Threejs层级模型就是一个树结构,可以通过递归遍历的算法去遍历Threejs一个模型对象的所有后代,可以通过下面代码递归遍历上面创建一个机器人模型或者一个外部加载的三维模型。

scene.traverse(function(obj) {
  if (obj.type === "Group") {
    console.log(obj.name);
  }
  if (obj.type === "Mesh") {
    console.log('  ' + obj.name);
    obj.material.color.set(0xffff00);
  }
  if (obj.name === "左眼" | obj.name === "右眼") {
    obj.material.color.set(0x000000)
  }
  // 打印id属性
  console.log(obj.id);
  // 打印该对象的父对象
  console.log(obj.parent);
  // 打印该对象的子对象
  console.log(obj.children);
})

查找某个具体的模型

看到Threejs的.getObjectById().getObjectByName()等方法,如果已有前端基础,很容易联想到DOM的一些方法。

Threejs和前端DOM一样,可以通过一个方法查找树结构父元素的某个后代对象,对于普通前端而言可以通过name或id等方式查找一个或多个DOM元素,Threejs同样可以通过一些方法查找一个模型树中的某个节点。更多的查找方法和方法的使用细节可以查看基类Object3D

// 遍历查找scene中复合条件的子对象,并返回id对应的对象
var idNode = scene.getObjectById ( 4 );
console.log(idNode);
// 遍历查找对象的子对象,返回name对应的对象(name是可以重名的,返回第一个)
var nameNode = scene.getObjectByName ( "左腿" );
nameNode.material.color.set(0xff0000);

写在最后

本文介绍了Threejs层级模型的概念,包含了层级模型节点命名、查找、遍历,希望对你有帮助。

本文发布自 云图三维大前端团队,文章未经授权禁止任何形式的转载。