Alpha blender 除了可以对 alpha 管道进行融合,它也可以对 color 管道进行 融合,但是不幸的是,当每个管道的值超过 255 后,该值都将被截取位 255。 为了在 alpha blending unit 上获得额外的精度,我们可以如下图所示的方式组织数据: 图五:利用数据组织方式提高精度 5 上图的数据组织方式可以让我们在 8 位的管道上获得 12 位的精度。Red channel 包含高 8 位的信息,blue channel 包含低 4 位的信息,加上 3 位的进位标志,首位没有被使用。 但是上述的过程 pixel shader 无法实现,主要原因有两个,第一:颜色差值只能处理 8 位;第二:深度计算是基于 vertex shader 的,vertex shader 不会让高位的信息进入独立的颜 色管道,即使颜色管道有更高的精度,pixel shader 也没有指令来捕获更高精度中的低位信 息。解决方法之一就是利用纹理。利用纹理有两个好处,第一:纹理差值可以获得比颜色差 值更高的精度;第二:累加 fog volume 的前面和背面的时候不需要任何 pixel shader。这种 方法主要受限于纹理的大小。现在的硬件一般允许的纹理最大尺度是 4096,这意味着我们 通过纹理可以获得最高 12 位的精度。虽然这也是一个不足之处,但相比 8 位已经能够让我 们作出非常好的效果了。 完整的过程: 有三点需要仔细考虑:fog volume 面的累加,补偿处于雾中的物体,和最后的相减。 累加可以分三步完成:首先,渲染整个场景到 Z buffer 中。然后,在一个缓冲区中绘制 所有的正面,对结果进行累加;在另一个缓冲区中绘制所有的背面,对结果进行累加。 在上面的描述中没有考虑 volume 中包含物体的情况。Fog volume 中包含物体的情况在 凸多面体的 fog volume 中已经很好的解决了,但是同样的方法在凹多面体的情况下会产生 错误。如下图所示: 图六:fog volume 中包含物体的情形 在上图中,如果不考虑物体本身的深度,那么该点计算出的深度值为 B1-A1-A2,因为 B2 没有通过深度测试,永远不会被画出。该点的雾的浓度值为负值,显示的结果是该点没 有雾,实际上应该有雾。在这种情况下,物体本身的深度值 C 必须加到方程中。 该解决方案需要确定某一点是否需要加上该点的场景中的深度值。 确定这些点也比较简 单,哪些位于 fog volume 的前表面和后表面之间的 pixel 就是需要加上该点的场景深度值的 点。这个问题可以转化成这样一种形式:从某 pixel 进入的光线是否穿出了 volume。通过一 个简单的 inside outside 测试可以解决这个问题。 Inside/outside 测试: 每一次渲染, pixel 的 alpha 值就增加 1, 该 如果出点和入点的 alpha 值相减不为零,则通过该点的光线没有出 volume,相减为零证明通过该点的光线穿透了 volume。 接下来的处理过程就比较浅显了: volume 的前表面和后表面在不同的 buffer 中分别 fog 被累加, 场景在另外的 buffer 中进行渲染。 后表面的 buffer 与前表面的 buffer 相减, alpha 在 6 值不为零的点加上场景 buffer 中对应的深度值。 相应的 pixel shader 实现如下: ps.1.1 def c1, 1.0f,0.0f,0.0f,0.0f def c4, 0.0f,0.0f,1.0f,0.0f tex t0 tex t1 tex t2 // input: // b = low bits (a) (4 bits) // r = high bits (b) (8 bits) // intermediate output: // r1.b = (a1 - a2) (can't be greater than 7 bits set ) // r1.r = (b1 - b2) sub r1.rgb,t1,t0 +sub_4x r1.a,t0,t1 mov_4x r0.a,r1.a mad r1.rgb,r0.a,t2,r1 dp3 t0.rgba,r1,c4 //If this value is non zero, then //the were not as many backs as //front and must add in the scene // move red component into alpha // near buffer B // far buffer A // scene buffer C // Need to shift r1.rgb 6 bits. This could saturate // to 255 if any other bits are set, but that is fine // because in this case, the end result of the subtract // would have to be saturated since we can't be // subtracting more than 127 mov_x4 r1.rgb,r1 dp3_x4 t1.rgba,r1,c1 add_x2 r0.a,t0.a,t1.a mov_d2 r0.a,r0.a +mov r0.rgb,c3 // move into the alpha // the subtract was in 0-127 // chop off last bit else banding // load the fog color 该 pixel shader 给出了 alpha 值代表雾的浓度值,并且加载了雾的颜色到 color 管道中。 还有一种导致错误的情况我们前目没有考虑。前面的例子都是假设相机完全处于 fog volume 外,或者完全处于 fog volume 内部,如果相机部分位于 fog volume 内部,则部分 fog volume 被裁剪,错误同时发生了。 一个权宜的方法是禁止多边形裁剪。Vertex shader 可以检测出哪些顶点将要被裁剪掉, 然后使该点对齐到近裁剪面。