Cesium高级教程-3D高斯泼溅-Splat-高斯数据渲染
Cesium高级教程-3D高斯泼溅-Splat-高斯数据渲染
数据加载完成后下一步我们应该做的是排序操作,但是现在我们先省略排序的步骤直接先进行数据的渲染,因为排序只会影响绘制图形的前后(遮挡)关系,并不会影响图形的变换及着色结果,并且排序的结果是否正确只有渲染出来才能验证,所以我们先将数据渲染出来,再看排序对渲染结果的影响。
实例化渲染准备
在开始渲染Splat数据之前我们需要先准备好图形实例化渲染的基本环境,相关内容前面百万级图形渲染一章已经介绍得非常详细了,这里就不再重复阐述,实例化渲染一个矩形的基本代码如下
// z// | /y// |/// o------xconstpositions=newFloat32Array([-2,-2,2,-2,2,2,-2,2]);constvertexBuffer=Cesium.Buffer.createVertexBuffer({context:viewer.scene.context,typedArray:positions,usage:Cesium.BufferUsage.STATIC_DRAW});constattributes=[{index:0,vertexBuffer:vertexBuffer,componentsPerAttribute:2,componentDatatype:Cesium.ComponentDatatype.FLOAT,}];constvertexArray=newCesium.VertexArray({context:viewer.scene.context,attributes:attributes});constvs=`...`constfs=`...`constshaderProgram=Cesium.ShaderProgram.fromCache({context:viewer.scene.context,vertexShaderSource:vs,fragmentShaderSource:fs,attributeLocations:{position:0,}})letposition=Cesium.Cartesian3.fromDegrees(105.41883,26.68244,0);letmodelMatrix=Cesium.Transforms.eastNorthUpToFixedFrame(position);letcommand=newCesium.DrawCommand({modelMatrix:modelMatrix,vertexArray:vertexArray,shaderProgram:shaderProgram,renderState:Cesium.RenderState.fromCache({depthTest:{enabled:true,}}),pass:Cesium.Pass.OPAQUE,instanceCount:vertexCount,uniformMap:{}})classMyPrimitive{constructor(){}update(frameState){frameState.commandList.push(command);}isDestroyed(){returnfalse;}}letp=newMyPrimitive();viewer.scene.primitives.add(p);代码中我们省略了着色器代码,缺省代码可以在前面章节中获取,因为这里我们只先准备一个基本骨架。因为我们最终需要将结果渲染在地球上的某个位置,所以需要指定一个从世界坐标原点到该位置的变换矩阵modelMatrix,这里可以自行定义该位置的坐标值。
instanceCount属性初始值为0,当vertexCount变化后记得更新instanceCount
数据纹理与索引
接下来我们按照 Splat Viewer 中的代码先将高斯数据打包到一张纹理图里面去,因为打包过程是在WebWorker中进行的,所以我们需要将数据传入Worker并注册消息事件接收结果。
letdataTexture=newCesium.Texture({context:viewer.scene.context,width:1,height:1,pixelFormat:Cesium.PixelFormat.RGBA_INTEGER,pixelDatatype:Cesium.PixelDatatype.UNSIGNED_INT,sampler:newCesium.Sampler({minificationFilter:Cesium.TextureMinificationFilter.NEAREST,magnificationFilter:Cesium.TextureMagnificationFilter.NEAREST})});functioncreateTexture(texdata,texwidth,texheight){returnnewCesium.Texture({context:viewer.scene.context,width:texwidth,height:texheight,pixelFormat:Cesium.PixelFormat.RGBA_INTEGER,pixelDatatype:Cesium.PixelDatatype.UNSIGNED_INT,source:{arrayBufferView:texdata,width:texwidth,height:texheight,},sampler:newCesium.Sampler({minificationFilter:Cesium.TextureMinificationFilter.NEAREST,magnificationFilter:Cesium.TextureMagnificationFilter.NEAREST})});}letcommand=newCesium.DrawCommand({...uniformMap:{u_texture:()=>{returndataTexture;},}})...上面的代码中我们先创建了一张默认的纹理图,当我们接收到WebWorker的打包结果后再覆写其内容。现在我们需要手动触发纹理打包操作,因为Splat Viewer中默认是排序时才会打包纹理,而我们现在并没有开启排序。
要触发纹理打包操作我们需要修改两处代码,一是在接收到网络请求的高斯数据后将其推送到WebWorker,二是在WebWorker中接收到数据后调用generateTexture函数进行纹理打包。
functioncreateWorker(self){...letsortRunning;self.onmessage=(e)=>{if(e.data.buffer){buffer=e.data.buffer;vertexCount=e.data.vertexCount;}elseif(e.data.vertexCount){vertexCount=e.data.vertexCount;}elseif(e.data.view){viewProj=e.data.view;throttledSort();}};}...while(true){...if(vertexCount>lastVertexCount){worker.postMessage({buffer:splatData.buffer,vertexCount:Math.floor(bytesRead/rowLength),});lastVertexCount=vertexCount;}}现在纹理数据已经有了,但是我们还得为其准备一个默认的索引(排序),这里我们直接将0,1,2,3,4...设置为其默认的索引即可,索引数据需要通过Buffer来传递,所以我们需要修改一下前面默认的VAO。
constinitialIndices=newUint32Array(x);for(leti=0;i<x;i++)initialIndices[i]=i;letindexBuffer=Cesium.Buffer.createVertexBuffer({context:viewer.scene.context,typedArray:initialIndices,usage:Cesium.BufferUsage.STATIC_DRAW});constattributes=[{index:0,vertexBuffer:vertexBuffer,componentsPerAttribute:2,componentDatatype:Cesium.ComponentDatatype.FLOAT,},{index:1,vertexBuffer:indexBuffer,componentsPerAttribute:1,instanceDivisor:1,componentDatatype:Cesium.ComponentDatatype.UNSIGNED_INT}];着色器代码修改
现在我们把Splat Viewer中的两个着色器代码片段直接拷贝进来替换vs与fs,fs代码基本不需要进行任何修改,而vs代码中我们需要一些修改才能正常运行。首先是视图矩阵(view)、投影矩阵(projection)以及视口大小(viewport)需要改为Cesium内置的变量,其次是focal参数(焦距)Cesium中并没有内置的,我们可以通过vec2(viewport.y / 2.0) * abs(projection[1][1]);或vec2(czm_viewport.z * czm_projection[0][0])来获取,其余代码保持不变。
constvs=`... uniform highp usampler2D u_texture; // uniform mat4 projection, view; // uniform vec2 focal; // uniform vec2 viewport; in vec2 position; in float splatIndex;//Cesium中不支持int类,我们可以先用float,后面再进行类型转换 out vec4 vColor; out vec2 vPosition; void main () { int index=int(splatIndex); mat4 view=czm_modelView; mat4 projection=czm_projection; vec2 viewport=czm_viewport.zw; vec2 focal = vec2(viewport.y / 2.0) * abs(projection[1][1]); uvec4 cen = texelFetch(u_texture, ivec2((uint(index) & 0x3ffu) << 1, uint(index) >> 10), 0); vec4 cam = view * vec4(uintBitsToFloat(cen.xyz), 1); vec4 pos2d = projection * cam; ... }`.trim();示例效果可到 xt3d 官网 运行查看
至此就可以运行页面查看渲染结果,不出意外的话现在看到的就是一堆带有颜色的椭圆,这些椭圆都是由高斯椭球投影而来,因为现在没有开启颜色混合,所以看起来和原始效果差别有点大。
颜色混合与轴向
开启颜色混合选项需要修改绘制指令的渲染状态属性,混合模式有很多种(Cesium内置的),这里我们选择与Splat Viewer中比较近似的一种PRE_MULTIPLIED_ALPHA_BLEND,当然也可以自定义混合模式。
letcommand=newCesium.DrawCommand({modelMatrix:modelMatrix,vertexArray:vertexArray,shaderProgram:shaderProgram,...})示例效果可到 xt3d 官网 运行查看
现在颜色渲染基本上正确了,但是看上去是倾斜的,这是因为默认的Splat数据是Y轴朝上的,我们只需要将其绕X轴旋转一下即可。
letposition=Cesium.Cartesian3.fromDegrees(105.41883,26.68244,0);letmodelMatrix=Cesium.Transforms.eastNorthUpToFixedFrame(position);letrotation=Cesium.Matrix4.fromRotationTranslation(Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(-90)));Cesium.Matrix4.multiply(modelMatrix,rotation,modelMatrix);示例效果可到 xt3d 官网 运行查看
更多内容见 Cesium高级教程-教程简介
