1、法线计算
WebGL在片元着色器中提供了用于计算偏导数的函数 dFdx 和 dFdy,它们用于计算某种类型的值相对于屏幕空间坐标的变化率。
通俗来讲,对某个值求偏导,就是当屏幕坐标沿 x 轴前进 1 个像素或者沿 y 轴前进 1个像素时,该值的变化量。下面这张图可以很好地帮助理解(图片引用自An introduction to shader derivative functions)
图1 偏导数的含义
基于上述基本定义,几何体上三角面片的法线计算就有了思路。对空间坐标分别求 dFdx 和 dFdy。
假设屏幕坐标处的空间位置为 position(x, y),那么
dFdx(position(x, y)) = position(x + 1, y) - position(x, y)
dFdy(position(x, y)) = position(x, y + 1) - position(x, y)
这时 dFdx 和 dFdy 其实就是三角面片所在平面的两个向量,它们叉乘(cross)的结果就是当前屏幕坐标位置处的法向量
normal = cross(dFdx(position(x, y)), dFdy(position(x, y)));
2、世界坐标系下的精度问题
有时候我们需要在世界坐标系下计算片元的法线,而世界坐标往往是很大的数值,在GPU的单精度限制下会出现精度损失,从而导致计算出来的法线也存在精度问题。即相邻片元虽然在同一个三角面片上,但得出的法线方向会有所差异。为了说明这种精度损失的现象,我把这种情况下算出的法线的各个分量表达为颜色值,输出为一张图片,如图2所示。
图2 GPU计算法线精度损失的情况
可以看到很多“噪点”,根据这样的法线计算结果来做一些具体的应用,得到的结果往往也是会有很多“噪点”的。
我想到的一个解决思路是借助 WebGL 渲染管线在光栅化阶段对从顶点着色器传递到片元着色器的值做内插的特性:首先将片元着色器中通过偏导数计算得到的法线作为颜色值的各个分量离屏渲染到一张纹理上(render to texture),接着把这张纹理回过头来传给顶点着色器,顶点着色器中采样每个顶点位置的法线,最后内插到片元,这样就可以得到平滑的法线值了。图3是WebGL渲染管线的基本流程,这张图截取自《WebGL编程指南》一书。
图3 WebGL渲染管线在光栅化阶段的内插特性
按这种方案得到的平滑法线输出为颜色类似图4的效果。这种方案虽然需要绘制两遍,但是能较好地规避世界坐标系下法线计算的精度问题。
图4 GPU计算法线精度损失的解决方案