本文會講解一下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
層。內部表示為:
layer | value(二進位制,32位) | 說明 |
---|---|---|
0 | 00000000000000000000000000000001 | 第32位為1 |
1 | 00000000000000000000000000000010 | 第31位為1 |
2 | 00000000000000000000000000000100 | 第30位為1 |
3 | 00000000000000000000000000001000 | 第29位為1 |
... | ... | ... |
30 | 01000000000000000000000000000000 | 第2位為1 |
31 | 10000000000000000000000000000000 | 第1位為1 |
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
屬性測試失敗的時候,行為總結如下:
屬性 | 物體是否顯示 | 該物體的children是否顯示 | 該物體是否進行raycaster測試 | 該物體的children是否進行raycaster測試 |
---|---|---|---|---|
visible | 否 | 否 | 否(r114版本以下) | 否(r114版本以下) |
layers | 否 | 是 | 否(r113版本以上) | 是 |
希望大家有所收穫,如有錯誤,歡迎留言討論。