Cesium 实现几何图形贴地主要采用Stencil Buffer Test技术实现
大致绘制思路:(绘制一直贴地几何图形即有三个drawcommand)
第一步:几何阴影体模板测试
第二步:几何阴影体模板测试+深度测试
第三步:融合+几何阴影体模板测试
模板缓冲中的模板值(Stencil Value)通常是8位的,因此每个片段/像素共有256种不同的模板值。下图是模板测试示例。
下面根据实际使用场景具体分析
第一步:
function getStencilPreloadRenderState(enableStencil, mask3DTiles) {
var stencilFunction = mask3DTiles ? StencilFunction.EQUAL : StencilFunction.ALWAYS;
return {
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
},
stencilTest : {
enabled : enableStencil,
frontFunction : stencilFunction,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.DECREMENT_WRAP,
zPass : StencilOperation.DECREMENT_WRAP
},
backFunction : stencilFunction,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.INCREMENT_WRAP,
zPass : StencilOperation.INCREMENT_WRAP
},
reference : StencilConstants.CESIUM_3D_TILE_MASK,
mask : StencilConstants.CESIUM_3D_TILE_MASK
},
stencilMask : StencilConstants.CLASSIFICATION_MASK,
depthTest : {
enabled : false
},
depthMask : false
};
}
如上图和代码所示,绘制模板体,模板测试失败的保持不变,模板测试通过的+1,注:WebGL规范的6.8节要求引用和掩码必须相同,正面和背面测试防止调用时出现无效操作错误。可以得到如下模板测试结果。其中灰色比较模糊的1是地下的模板体,这里为了示意更清楚,特地区分。
图3 模板测试图
代码跟踪调试部分代码如下:
图4 正面和背面模板状态更新图
结合上面贴出的关键代码,正面-1(DECREMENT_WRAP),背面+1(INCREMENT_WRAP),得到如上图四的区分结果,黄色部分(图四中黄色应该是在地下)模板值为255,中等浅蓝色部分和最上方深蓝色部分模板值为0(渲染正面时-1,背面时+1,最后保持不变)。
第二步:
function getStencilDepthRenderState(enableStencil, mask3DTiles) {
var stencilFunction = mask3DTiles ? StencilFunction.EQUAL : StencilFunction.ALWAYS;
return {
colorMask : {
red : false,
green : false,
blue : false,
alpha : false
},
stencilTest : {
enabled : enableStencil,
frontFunction : stencilFunction,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.INCREMENT_WRAP
},
backFunction : stencilFunction,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
reference : StencilConstants.CESIUM_3D_TILE_MASK,
mask : StencilConstants.CESIUM_3D_TILE_MASK
},
stencilMask : StencilConstants.CLASSIFICATION_MASK,
depthTest : {
enabled : true,
func : DepthFunction.LESS_OR_EQUAL
},
depthMask : false
};
}
第二步在第一步的基础上继续模板测试+深度检测,深度检测小于等于时通过深度检测。该检测即可得到贴地的几何图形的模板测试结果。如下图浅蓝色区域即为最终模板检测结果。
第三步:融合,这里不做解释。只要模板测试值不为0就渲染。
function getColorRenderState(enableStencil) {
return {
stencilTest : {
enabled : enableStencil,
frontFunction : StencilFunction.NOT_EQUAL,
frontOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
backFunction : StencilFunction.NOT_EQUAL,
backOperation : {
fail : StencilOperation.KEEP,
zFail : StencilOperation.KEEP,
zPass : StencilOperation.DECREMENT_WRAP
},
reference : 0,
mask : StencilConstants.CLASSIFICATION_MASK
},
stencilMask : StencilConstants.CLASSIFICATION_MASK,
depthTest : {
enabled : false
},
depthMask : false,
blending : BlendingState.ALPHA_BLEND
};
}
GL_FRONT和GL_BACK的分析
三维渲染一般用表面来表示物体,一个物体就是一组平面。光线照射在平面上会产生反射,入射线与反射线的角平分线就是法线,它垂直于平面。面法线有两种可能的方向,我们称面有两侧。当描述封闭物体的外表面时,法线应该从内部指向外部;而表示在物体内部时,法线应该从外部指向内部。
用glNormal*(N_Vector);指定当前法向矢量。此后的顶点都会使用该法向,直到再次改变。
面的法向矢量是怎么确定的呢?右手手指沿顶点顺序握拳,拇指竖立所指方向即为一个面的法向,如果面对该平面,以逆时针序指定顶点,则法向指向你。当你用这样的顺序指定平面的顶点时,WebGL能够正确的计算出法向矢量。法矢与顶点坐标一样要经过模型视矩阵变换,这样物体坐标系内的法向矢量就可以正确的变成世界坐标系内的法向矢量,保证最终生成的图像是正确的。
当一个顶点属于多个平面时,该顶点的法向应该取所在所有平面法向的平均值,以使表面棱角部份平滑自然的,否则会有一道黑线。
我们把平面的法向所指一侧称为前面,另一侧称为后面,可以为两侧分别指定不同的属性。