Three.js控制物體顯示與隱藏的方法

luckness發表於2022-05-23

本文會講解一下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版本以上)

希望大家有所收穫,如有錯誤,歡迎留言討論。

相關文章