Three.js控制物体显示与隐藏的方法

luckness
English

本文会讲解一下Three.js控制物体显示与隐藏的方法,主要包括以下几种方式:

  1. visible属性;
  2. layers属性。

下面会分别通过简单的例子介绍下上述几个方式的简单使用方法和一些它们之间的区别。如果没有特殊说明,下面的源码以 r105 版本为例:

visible属性

visibleObject3D的属性。只有当 visibletrue 的时候,该物体才会被渲染。任何继承 Object3D 的对象都可以通过该属性去控制它的显示与否,比如:MeshGroupSpriteLight等。

举个简单的例子:

// 控制单个物体的显示和隐藏
const geometry = new THREE.PlaneGeometry(1, 1) // 1*1的一个平面
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) // 红色平面
const plane = new THREE.Mesh(geometry, planeMaterial)
plane.visible = false // 不显示单个物体
scene.add(plane)
// 控制一组物体的显示和隐藏
const geometry = new THREE.PlaneGeometry(1, 1)
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const plane = new THREE.Mesh(geometry, planeMaterial)
const group = new THREE.Group()
group.add(plane)
group.visible = false // 不显示一组物体
scene.add(group)

通过后面的例子可以看出,当我们想要控制一组物体的显示与隐藏,可以把这些物体放入一个 Group 中,只通过控制 Group 的显示与隐藏即可。

这块的代码逻辑是在WebGLRenderer.jsprojectObject 方法中实现的。

首先,在 render 方法中调用了 projectObject 方法:

this.render = function ( scene, camera ) {
  // ...
  projectObject( scene, camera, 0, _this.sortObjects );
  // ...
}

projectObject 方法的定义如下:

function projectObject( object, camera, groupOrder, sortObjects ) {
  if ( object.visible === false ) return; // 注释1:visible属性是false直接返回
  // ...
  var children = object.children; // 注释2:递归应用在children上

  for ( var i = 0, l = children.length; i < l; i ++ ) {

    projectObject( children[ i ], camera, groupOrder, sortObjects ); // 注释2:递归应用在children上

  }
}

从注释1可以看出,如果 Groupvisiblefalse,那么就不会在 children 上递归调用,所以就能达到通过 Group 控制一组对象的显示与隐藏的效果。

visiblefalse 的时候,RaycasterintersectObject 或者 intersectObjects 也不会把该物体考虑在内。这块的代码逻辑是在 Raycaster.js

intersectObject: function ( object, recursive, optionalTarget ) {
  // ...
  intersectObject( object, this, intersects, recursive ); // 注释1:调用了公共方法intersectObject
  // ...
},

intersectObjects: function ( objects, recursive, optionalTarget ) {
  // ...

  for ( var i = 0, l = objects.length; i < l; i ++ ) {

    intersectObject( objects[ i ], this, intersects, recursive ); // 注释1:循环调用了公共方法intersectObject

  }
  // ...
}

// 注释1:公共方法intersectObject
function intersectObject( object, raycaster, intersects, recursive ) {

    if ( object.visible === false ) return; // 注释1:如果visible是false,直接return

    // ...
}

从注释1可以看出,如果 Group 或者单个物体的 visiblefalse ,就不做检测了。

layers属性

Object3D的layers属性 是一个 Layers 对象。任何继承 Object3D 的对象都有这个属性,比如 CameraRaycaster 虽然不是继承自 Object3D ,但它同样有 layers 属性(r113版本以上)。

和上面的 visible 属性一样,layers 属性同样可以控制物体的显示与隐藏、Raycaster 的行为。当物体和相机至少有一个同样的层的时候,物体就可见,否则不可见。同样,当物体和 Raycaster 至少有一个同样的层的时候,才会进行是否相交的测试。这里,强调了是至少有一个,是因为 Layers 可以设置多个层。

Layers 一共可以表示 32 个层,031 层。内部表示为:

layervalue(二进制,32位)说明
000000000000000000000000000000001第32位为1
100000000000000000000000000000010第31位为1
200000000000000000000000000000100第30位为1
300000000000000000000000000001000第29位为1
.........
3001000000000000000000000000000000第2位为1
3110000000000000000000000000000000第1位为1

Layers 可以设置同时拥有多个层:

  1. 可以通过 Layersenabledisable 方法开启和关闭当前层,参数是上面表格中的 031
  2. 可以通过 Layersset 方法 只开启 当前层,参数是上述表格中的 031
  3. 可以通过 Layerstest 的方法判断两个 Layers 对象是否存在 至少一个公共层

当开启多个层的时候,其实就是上述表格中的二进制进行 按位或 操作。比如 同时 开启 0231 层,那么内部存储的值就是 10000000000000000000000000000101

layers 属性默认只开启 0 层。

还是上面那个例子,我们看下怎么控制物体的显示和隐藏:

// 控制单个物体的显示和隐藏
const geometry = new THREE.PlaneGeometry(1, 1)
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const plane = new THREE.Mesh(geometry, planeMaterial)
plane.layers.set(1) // 设置平面只有第1层,相机默认是在第0层,所以该物体不会显示出来
scene.add(plane)
// 控制一组物体的显示和隐藏
const geometry = new THREE.PlaneGeometry(1, 1)
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const plane = new THREE.Mesh(geometry, planeMaterial)
const group = new THREE.Group()
group.layers.set(1) // 注释1: 设置group只有第一层,相机默认是在第0层,但是此时平面物体还是显示出来了?
group.add(plane)
scene.add(group)

设置单个物体的 layer 可以看到物体成功的没有显示出来。但是,当我们给 group 设置 layer 之后,发现 groupchildren(平面物体)还是显示了出来。那么,这是什么原因呢?让我们看下源码,同样还是上面的 projectObject 方法:

function projectObject( object, camera, groupOrder, sortObjects ) {

  if ( object.visible === false ) return;

  var visible = object.layers.test( camera.layers ); // 注释1:判断物体和相机是否存在一个公共层

  if ( visible ) { // 注释1:如果存在,对物体进行下面的处理
    // ...
  }

  var children = object.children; // 注释1:不管该物体是否和相机存在一个公共层,都会对children进行递归

  for ( var i = 0, l = children.length; i < l; i ++ ) {

    projectObject( children[ i ], camera, groupOrder, sortObjects );

  }
}

从上述注释1可以看出,即使该物体和相机不存在公共层,也不影响该物体的 children 显示。这也就解释了上述为什么给 group 设置 layers ,但是平面物体还是能显示出来。从这一点上来看,layersvisible 属性在控制物体显示和隐藏的方面是不一样的。

visible 属性一样,接下来我们看下 LayersRaycaster 的影响。同样我还是看了 Raycaster.js 文件,但是发现根本就没有 layers 字段。后来,我看了下最新版本 r140Raycaster.js

function intersectObject( object, raycaster, intersects, recursive ) {

  if ( object.layers.test( raycaster.layers ) ) { // 注释1:判断物体和Raycaster是否有公共层

    object.raycast( raycaster, intersects );

  }

  if ( recursive === true ) { // 注释1:不管该物体和Raycaster是否有公共层,都不影响children

    const children = object.children;

    for ( let i = 0, l = children.length; i < l; i ++ ) {

      intersectObject( children[ i ], raycaster, intersects, true );

    }
  }
}

不同于前面,visiblelayers 都可以用来控制物体的显示与隐藏,visiblelayers 只有一个可以用来控制 Raycaster 的行为,具体是哪一个生效,可以看下 Three.js的迁移指南

可以看到,从 r114 版本,废除了 visible ,开始使用 layers 控制 Raycaster 的行为:

r113 → r114
Raycaster honors now invisible 3D objects in intersection tests. Use the new property Raycaster.layers for selectively ignoring 3D objects during raycasting.

总结

从上面可以看出,visiblelayers 在控制物体显示与隐藏、Raycaster 是否进行等方面是存在差异的。

当该物体的 visible 属性为 false 并且 layers 属性测试失败的时候,行为总结如下:

属性物体是否显示该物体的children是否显示该物体是否进行raycaster测试该物体的children是否进行raycaster测试
visible否(r114版本以下)否(r114版本以下)
layers否(r113版本以上)

希望大家有所收获,如有错误,欢迎留言讨论。

阅读 1.3k
6.1k 声望
5.1k 粉丝
0 条评论
6.1k 声望
5.1k 粉丝
文章目录
宣传栏