今天,分享的是项目中真实会用到的东西,Three.js GPU Picking,这是什么意思呢,不同于Three.js 的Raycaster,它是利用颜色的6位16进制表示,以颜色作为ID,在后台渲染出纹理后,根据鼠标坐标下的纹理颜色,进行ID的查询进行拾取操作,哈哈,如果你不是很了解,应该不明白我在说什么,下面我们一步一步来说明这个所谓的GPU Picking。
在Three.js r102版本中,这两个案例用到了这种拾取方式,webgl_interactive_instances_gpu和webgl_interactive_cubes_gpu中用到了GPU Picking,我们先来看一下效果:
webgl_interactive_cubes_gpu webgl_interactive_cubes_gpu
上面两幅图中的黑色箭头就是拾取的效果,那下面我就webgl_interactive_cubes_gpu的instance部分来说说拾取,instance方式渲染,后面的博客会有。首先,来看一下webgl_interactive_cubes_gpu创建InstanceMesh部分的代码。
function makeInstanced( geo ) {
//实例渲染的着色器
var vert = document.getElementById( 'vertInstanced' ).textContent;
var frag = document.getElementById( 'fragInstanced' ).textContent;
//创建实例渲染所用的RawShaderMaterial
var material = new THREE.RawShaderMaterial( {
vertexShader: vert,
fragmentShader: frag,
} );
//将material放进材质List方便后期释放资源
materialList.push( material );
//创建后台渲染的拾取材质----------GPU Picking
var pickingMaterial = new THREE.RawShaderMaterial( {
vertexShader: "#define PICKING\n" + vert,
fragmentShader: "#define PICKING\n" + frag
} );
//将pickingMaterial放进材质List方便后期释放资源
materialList.push( pickingMaterial );
//创建InstancedBufferGeometry----------既用于GPU Picking,也用于渲染呈现
var igeo = new THREE.InstancedBufferGeometry();
//将igeo其放进几何体列表
geometryList.push( igeo );
.....//省略克隆顶点的代码
.....//省略创建模型矩阵BufferAttribute的代码
//创建矩阵
var matrix = new THREE.Matrix4();
//列主序的matrix
var me = matrix.elements;
//根据实例数量循环
for ( var i = 0, ul = mcol0.count; i < ul; i ++ ) {
//随机产生模型矩阵
randomizeMatrix( matrix );
//创建Object3D,主要是将每个实例的矩阵信息保存下来----------GPU Picking
var object = new THREE.Object3D();
objectCount ++;
//object应用此矩阵----------GPU Picking
object.applyMatrix( matrix );
//拾取数组中按照----------GPU Picking*
pickingData[ i + 1 ] = object;
//设置各个实例的矩阵
....//省略
}
......//省略设置模型矩阵相关的代码
......//省略随机生成颜色,并存放到实例渲染的color中的代码
//创建颜色BufferAttribute----------GPU Picking
var col = new THREE.Color();
//用于拾取渲染的颜色----------GPU Picking
var pickingColors = new THREE.InstancedBufferAttribute(
new Float32Array( instanceCount * 3 ), 3
);
//根据实例数量,逐渐增加colors的颜色并按照索引增加添加到pickingColors-GPU Picking
for ( var i = 0, ul = pickingColors.count; i < ul; i ++ ) {
//设置颜色,此处是10进制,此方法通过右移操作和按位与设置颜色-GPU Picking*
col.setHex( i + 1 );
//将颜色按照索引添加---GPU Picking*
pickingColors.setXYZ( i, col.r, col.g, col.b );
}
//添加拾取颜色Attribute
igeo.addAttribute( 'pickingColor', pickingColors );
//创建用于渲染的Mesh
var mesh = new THREE.Mesh( igeo, material );
scene.add( mesh );
//创建后台用于渲染拾取的Mesh---GPU Picking*
var pickingMesh = new THREE.Mesh( igeo, pickingMaterial );
pickingScene.add( pickingMesh );
}
上面是创建Intance方式渲染Mesh的代码,和用于拾取计算的Mesh的代码,那它之间有什么关联呢
1.在为每个实例渲染添加模型矩阵的时候,会创建一个object3D,并将对应实例的模型矩阵应用到此Object3D,那么此Object3D就具有了和对应实例一样的矩阵信息,然后我们按照i+1的索引将其放入PickData数组中。
2.后面有按照i+1设置pickcolor颜色,然后创建用于渲染Mesh添加进scene,创建用于拾取的mesh,添加进pickscene用于后台渲染。
看到这里,两个标红的地方应该有种联系,那这种联系是什么呢,其实就是在用pickscene在后台渲染出纹理,然后根据鼠标点击,获取到鼠标点击位置的像素颜色rgba,并用rgb按位进行或操作算出十进制颜色值,这个十进制颜色值就是上面说的i+1,也就是ID,那就可以按照这个ID作为索引找到PickData数组中的Object3D,此Object3D的模型矩阵和对应实例的矩阵一样。
我们继续看pick函数中的代码:
function pick() {
//离屏渲染
//显示选中的黄色Box设为不渲染
highlightBox.visible = false;
//设置渲染目标
renderer.setRenderTarget( pickingRenderTarget );
//渲染pickingScene
renderer.render( pickingScene, camera );
//读取鼠标点击位置的像素,像素的rgba值会存到pixelBuffer中
renderer.readRenderTargetPixels(
pickingRenderTarget,
mouse.x,
pickingRenderTarget.height - mouse.y,
1,
1,
pixelBuffer
);
//将像素转换为ID,rgb先右移在按位或
var id =
( pixelBuffer[ 0 ] << 16 ) |
( pixelBuffer[ 1 ] << 8 ) |
( pixelBuffer[ 2 ] );
//根据ID从PickData中获取到Object
var object = pickingData[ id ];
if ( object ) {
// move the highlightBox so that it surrounds the picked object
if ( object.position && object.rotation && object.scale ) {
//根据Object的参数设置选中BOX的位置,旋转
highlightBox.position.copy( object.position );
highlightBox.rotation.copy( object.rotation );
//根据Object的参数设置缩放,并乘上包围盒的尺寸和缩放系数,以保证能够足够包围
highlightBox.scale.copy( object.scale )
.multiply( geometrySize )
.multiplyScalar( scale );
//选中BOX开始渲染
highlightBox.visible = true;
}
} else {
highlightBox.visible = false;
}
}
到这里呢,整个的拾取就结束了,那其实所谓的GPU Picking就是在创建一个与渲染呈现的Mesh位置相同的pickingMesh,并将pickingMesh中的各个实例的颜色按照从1开始增加进行设置,同时设置按照对应索引设置特定的数据,后台渲染出纹理,将鼠标点击位置下的像素颜色转换回颜色值,根据颜色值,找到特定的数据进行处理。在这个Demo中,是将Object3D作为模型矩阵信息存储的介质,然后根据后台渲染鼠标拾取到像素的10进制颜色值获取到指定的Object,并将此Object的信息复制给选中BOX实现选中效果。
留一个简单的问题:上面提到的两个Demo都用到了这种GPU Picking的思想,在这两个Demo中的具体实现中有什么相同,又有什么不同呢。好啦,笔者不才,也许并未表达出Three.js 中这种拾取方式精髓,请多多指教,可以加入QQ群,我们一起交流,这里有WebGL、Vulkan、OpenGL,也有Three.js、Unity、UE4,还有前端框架Vue等!当然也有图形图像处理大佬哈!期待你的加入!
\