相比立方体贴图,球面贴图的结果虽然不很精确,但是只需要一张贴图。这个时候球面环境贴图还是有用武之地的。
随便用搜索引擎在网上找了下,基本上没发现关于球面环境贴图的内容,本来想找个简单的shader直接来用的,没想到的是找了半天也没找到,于是只能自己动手写了。以下文章是我参考了一些资料加上我自己的理解写成的,如果有错误的地方还望大家指正。
环境映射是一种近似,它基于这样的假设:相对于光洁物体的大小而言,环境中的物体离光洁物体很远,也就是说,将一个很小的光洁物体放在大房间中。对于物体表面上的点,假设有一条从眼睛到该点的光线,这条光线被反射出去的方向决定该点的颜色。在一个二维纹理图中对各个方向的颜色进行编码,相当于将一个光洁度非常高的球体放在环境中央,然后在很远的地方用带长焦镜头的相机拍摄球体。从数学上说,镜头的焦距为无限长,相机位于无穷远处。因此,需要进行编码是纹理图的内切圆形区域,该圆形区域外的纹理值没有影响,因为进行环境印射时没有使用它们。(摘抄自OpenGL红宝书)
根据这段来自红宝书中的文字,在一个环境中生成球面贴图的时候,由于环境相对于球体而言无限大,所以可以把球体看成是一个单位球体。同时,有由于相机位于无限远处,所以相机到球体上的各个点的向量可以看成是相互平行的。
于是很容易的想到:
1、生成视线向量V。
2、根据顶点法线生成反射向量R。
3、查找R和球面的交点。
4、根据交点求出UV坐标。
R可以很容易的求出。为了求出UV,需要求出R和球面的交点E在球面的位置。这个时候回到生成球面纹理图时候的场景,由于球面是单位球面,利用单位球面的一个性质:球面上的点的归一化法线就是该点在球面上的位置。
我们只需要知道在生成球面纹理图的时候,在球面上相同的点,当反射向量也为R的时候该点的法线为多少即可。
根据向量加法原则,法线是视线向量和反射向量的和。为了模拟视点位于无限远处的情况,可以假象生成球面纹理图的过程是位于View Space,这样的话,Eye Vec就总是(0,0,1)。于是只需要把反射向量也转化到View Space就可以得出球面的法线向量。
还剩下最后一个问题,求出来的法线每个分量的值域是[-1,1],而uv要求的值域是[0,1],所以需要转换一下。以下是关键的VS代码片段:
后记:写这篇文章以前,我参考过DirectX SDK中文档的内容,文档中说,只需要把顶点的法线转换到Camera Space然后除以2加上0.5即可。我使用这个方法在FxComposer中实验,没有得到正确的结果,我不知道是否是哪写错了。SDK的文档中也没有详细说明这个方法的来龙去脉,不知道有没有知道的朋友。