ThreeJS Example webgl_interactive_cubes_gpu 拾取交互

954 阅读2分钟

zhuanlan.zhihu.com/p/143642146 threejs.org/examples/?q…

  • 拾取的方式有两种
    • GPU缓冲区拾取
    • CPU射线拾取
  • 射线拾取
    • 使用较为简单,无需额外渲染开销
    • 支持当前射线穿过的所有深度的物体拾取。(比如一个子弹穿墙打人)
    • 自己实现相交测试的话,几何算法有些难度
    • 使用cpu计算相交性,如果场景较大CPU开销较高,会卡顿(需要额外的八叉树、BVH树优化拾取)
  • GPU拾取
    • 充分利用GPU运算
    • 由于拾取颜色,自动可以把深度过滤,只取最前面的
    • 增加了渲染调度的复杂度
    • 增加了渲染的开销 image.png
  • 声明两个场景
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff );
pickingScene = new THREE.Scene();
  • 声明两种材质 vertexColors顶点attribute颜色都是有声明
const pickingMaterial = new THREE.MeshBasicMaterial( { vertexColors: true } ); //选中材质
const defaultMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true, vertexColors: true, shininess: 0	} ); //默认材质
  • 两个场景中,使用不同的材质和不同模型颜色
const geometriesDrawn = [];
applyVertexColors( geometry, color.setHex( Math.random() * 0xffffff ) ); //
geometriesDrawn.push( geometry );
const objects = new THREE.Mesh( BufferGeometryUtils.mergeBufferGeometries( geometriesDrawn ), defaultMaterial );
scene.add( objects ); //当前场景的 有随机颜色

geometry = geometry.clone();
applyVertexColors( geometry, color.setHex( i ) );
geometriesPicking.push( geometry ); //GPU场景的 颜色随着i递增
pickingScene.add( new THREE.Mesh( BufferGeometryUtils.mergeBufferGeometries( geometriesPicking ), pickingMaterial ) );

function applyVertexColors( geometry, color ) {
        const position = geometry.attributes.position;
        const colors = [];
        for ( let i = 0; i < position.count; i ++ ) {
                colors.push( color.r, color.g, color.b ); //该geometry所有顶点都同一颜色
        }
        geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
}
  • 选中时的高亮材质
highlightBox = new THREE.Mesh(
        new THREE.BoxGeometry(),
        new THREE.MeshLambertMaterial( { color: 0xffff00 }
        ) );
  • 获取鼠标位置在缓冲区中对应的颜色
  • Uint8Array(4) 是指RGBA 即4个8字节 255,255,255,255
camera.setViewOffset( renderer.domElement.width, renderer.domElement.height, mouse.x * window.devicePixelRatio | 0, mouse.y * window.devicePixelRatio | 0, 1, 1 );
renderer.setRenderTarget( pickingTexture );
renderer.render( pickingScene, camera );
camera.clearViewOffset();
const pixelBuffer = new Uint8Array( 4 );
renderer.readRenderTargetPixels( pickingTexture, 0, 0, 1, 1, pixelBuffer );
  • 通过颜色转为id
  • 另一种写法
const id = ( pixelBuffer[ 0 ] << 16 ) | ( pixelBuffer[ 1 ] << 8 ) | ( pixelBuffer[ 2 ] );
const id = pixelBuffer[ 0 ]*256*256 + pixelBuffer[ 1 ] * 256 + pixelBuffer[ 2 ]
  • 通过id获取并设置对应信息
const data = pickingData[ id ];
if ( data ) {
        //move our highlightBox so that it surrounds the picked object
        if ( data.position && data.rotation && data.scale ) {
                highlightBox.position.copy( data.position );
                highlightBox.rotation.copy( data.rotation );
                highlightBox.scale.copy( data.scale ).add( offset );
                highlightBox.visible = true;
        }
} else {
        highlightBox.visible = false;
}