Three.js控制物体显示与隐藏的方法
本文会讲解一下Three.js控制物体显示与隐藏的方法,主要包括以下几种方式:
- visible属性;
- layers属性。
下面会分别通过简单的例子介绍下上述几个方式的简单使用方法和一些它们之间的区别。如果没有特殊说明,下面的源码以 r105
版本为例:
visible属性
visible
是Object3D的属性。只有当 visible
是 true
的时候,该物体才会被渲染。任何继承 Object3D
的对象都可以通过该属性去控制它的显示与否,比如:Mesh
,Group
,Sprite
,Light
等。
举个简单的例子:
// 控制单个物体的显示和隐藏
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.js的 projectObject
方法中实现的。
首先,在 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可以看出,如果 Group
的 visible
是 false
,那么就不会在 children
上递归调用,所以就能达到通过 Group
控制一组对象的显示与隐藏的效果。
当 visible
是 false
的时候,Raycaster
的 intersectObject
或者 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
或者单个物体的 visible
是 false
,就不做检测了。
layers属性
Object3D的layers属性 是一个 Layers 对象。任何继承 Object3D
的对象都有这个属性,比如 Camera
。Raycaster
虽然不是继承自 Object3D
,但它同样有 layers
属性(r113版本以上)。
和上面的 visible
属性一样,layers
属性同样可以控制物体的显示与隐藏、Raycaster
的行为。当物体和相机至少有一个同样的层的时候,物体就可见,否则不可见。同样,当物体和 Raycaster
至少有一个同样的层的时候,才会进行是否相交的测试。这里,强调了是至少有一个,是因为 Layers
可以设置多个层。
Layers
一共可以表示 32
个层,0
到 31
层。内部表示为:
Layers
可以设置同时拥有多个层:
- 可以通过
Layers
的enable
和disable
方法开启和关闭当前层,参数是上面表格中的0
到31
。 - 可以通过
Layers
的set
方法 只开启 当前层,参数是上述表格中的0
到31
。 - 可以通过
Layers
的test
的方法判断两个Layers
对象是否存在 至少一个公共层 。
当开启多个层的时候,其实就是上述表格中的二进制进行 按位或 操作。比如 同时 开启 0
、2
、31
层,那么内部存储的值就是 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
之后,发现 group
的 children
(平面物体)还是显示了出来。那么,这是什么原因呢?让我们看下源码,同样还是上面的 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
,但是平面物体还是能显示出来。从这一点上来看,layers
和 visible
属性在控制物体显示和隐藏的方面是不一样的。
和 visible
属性一样,接下来我们看下 Layers
对 Raycaster
的影响。同样我还是看了 Raycaster.js 文件,但是发现根本就没有 layers
字段。后来,我看了下最新版本 r140
的 Raycaster.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 );
}
}
}
不同于前面,visible
和 layers
都可以用来控制物体的显示与隐藏,visible
和 layers
只有一个可以用来控制 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.
总结
从上面可以看出,visible
和 layers
在控制物体显示与隐藏、Raycaster
是否进行等方面是存在差异的。
当该物体的 visible
属性为 false
并且 layers
属性测试失败的时候,行为总结如下: