图形学中,某些物体带有反射属性,会反射周围的环境。一种做法是沿着反射方向发一条光线,与场景求交,获取到交点的颜色值,作为反射的颜色。显然这种方法比较低效,更高效的方法是将被渲染物体所处的环境保存到一张贴图中,渲染时先求出渲染点的反射方向,然后将这个反射方向(向量)变换到uv坐标,进而在贴图中查询到相应点的颜色值。过程中关键的一步是反射方向到uv坐标的变换,另外,在某种程度上,贴图的制作方式决定了变换方式。除了球形环境映射还有一种更为高质量的立方环境映射。球形环境映射又分为latlong(经纬)和angular方式,本文只讲angular方式下的两种不同的映射方式(反射方向到uv坐标的变换),这也就是前文的“在某种程度上”存在的意义。
话休絮烦,环境贴图的制作方式:待渲染物体的位置放一个铬球,相机拍摄铬球,考虑到透视投影,相机并不能拍到铬球的整个前半部分,换句话说,铬球的反射光线中不存在指向z轴正方向的光线,使用右手系,下同。但是该铬球已经很好的对大部分环境进行了反射,只有铬球正后方的一部分环境没被反射,该“被忽略”部分的大小取决于相机与铬球的距离,如果距离无限大,那么只有跟铬球最大截面等大的环境未被反射,但此时反射方向已经包含了z轴正方向,这种矛盾提醒了我们在环境不是无穷远时,该方法是不准确的,因为未考虑物体的形状(凹的物体可能发生自反射)和大小。
最常见的一种映射方式便是利用了贴图的制作方式,认为贴图是相机在无穷远处拍摄铬球得来,于是入射光线即(0,0,1),假设我们在渲染点处求得归一化的反射光线(x,y,z),法线即(x+0,y+0,z+1),很显然,归一化的法线顶点在xoy面上的投影位置即对应所要映射的贴图上的位置。考虑到纹理坐标的范围是0~1,我们进行除2加0.5的操作。RSL中代码如下(代码出自animallogic公司的mayaman头文件):
float m = 2*sqrt(x*x+y*y+(z+1)*(z+1));
U_COORD = y/m + 0.5;
V_COORD = x/m + 0.5;
另一种映射方式来自http://www.pauldebevec.com/Probes/,如果uv坐标的范围都是-1~1,我们先考虑如何把给定的某个uv坐标变换为反射方向,使用球面坐标,反射方向与z轴负方向夹角为phi,反射方向投影与x轴正向夹角为theta,theta=atan2(v,u), phi=pi*sqrt(u*u+v*v)。其中,theta的求法是显而易见的,但是phi的公式仅仅保证变换的区间及增减性正确。我们可以如此验证,取u=0.5,v=0,phi为pi/2,而实际上该点的反射方向与z轴负向夹角仍是锐角。反过来,给定反射方向(Dx, Dy, Dz),其对应的uv坐标即(Dx*r,Dy*r),其中, r=(1/pi)*acos(Dz)/sqrt(Dx^2 + Dy^2)。很显然,将原帖图贴到镜面球上之后,第一种映射方式再现了制作贴图时铬球的面貌,而第二种方式对贴图中间部分进行了放大,可如此分析,取反射方向为(1,0,0),不难计算uv坐标为(0.5,0),实际应该映射的是(0.707,0),见图。
贴图文件为
注:为避免透视投影对结果的影响(其实影响比较小),均使用平行投影。