yzwalkman

——用数绘画

导航

模拟光照中的凹凸纹理原理和应用

一、一个想法 

     早期的3D图形渲染,是要先计算三角形各顶点的光照,然后再在光栅化阶段根据顶点光照运算结果,对整个三角面进行插值运算,从而得到整个三角形的光照效果颜色值。这种计算方式被称之为Gouraud着色(Gouraud Shading)模式。在使用DirectX9之前(包括DirectX9)的固定管线(Fixed Pipeline)图形引擎进行渲染时,用户只需要设置好相应的转换矩阵和顶点数组,就可以得到想要的结果。但Gouraud着色在表现对象的细节时就显得无能为力了,即使引入了纹理贴图,在表现一些粗糙表面时仍束手无策。因为在光栅化时,无论是三角形内部各点对应的像素纹理UV值,还是光照结果都是通过插值得到的,因而表面的一些不规则起伏(表面有不规则起伏,对现实世界的对象来说是一种常态,而表面光滑——在肉眼感知范围内——则是非常态的)没有办法被表现出来。

     为了解决这一问题,凹凸贴图的技术就被引入进来。

     先讨论下模拟光照方程。模拟光照方程的Lambert模型,是将光向量L与入射点的切面法向量N进行点乘(用⊙表示)得到的。设两个向量的夹角为θ,由于这两个向量都是单位向量,所以就有:L⊙N = cosθ 它描述的是光照的漫反射分量。随着θ值的不同,光照强度也会作相应改变。对于这一现象,《Real-Time Rendering》一书中是这样解释的:假设有一束固定强度的入射光,取其中间隔为“l”一个区间,则这个区间的光照射到受光面上的区域为 l/cosθ,当θ为零时,入射光的区间与受光面相应区域大小相同,此时光照强度最大;随着θ的增大cosθ在不断减小,由于光照强度未变,所以入射光区间“l”中的光强度并未改变,但受光面区域 l/cosθ 却在不断增大,也就是说,同样多的光子照射在了更大区域之上,因此,受光面上各点相应的光强度就减弱了。(参见此书的Figure 4.6)

     其实对于Lambert模型还可以这样理解:假设入射光L照到受光面的一点p上,过点p的切平面的法线为N,L与N的夹角仍为θ,那么L实际上可以分解为两个向量,一个是沿着法线N的入射分量Ln,另一个是平行于切平面的分量Lt。入射光向量对点p的光照贡献实际上全都来自于Ln,其大小就等于L⊙N。

     不论以上哪种理解,从公式 L⊙N = cosθ 可以看出,对于固定强度的入射光来说,入射点法线与入射光夹角越大受光照影响就越小,反之受光照影响越大。假设受光面只有一个光源,那么就可以这样说:入射点法线与入射光向量夹角越大入射点就越暗,反之入射点就越亮。明白这一点对理解模拟光照下的凹凸贴图原理很关键。

     画过素描的人知道,在一张平整的白纸上,要表现出一个位于三维空间的对象的立体效果,其关键在于,在准确把握对象的透视几何比例的前提下,要能描绘出对象的光照效果。通过高光、受光区、明暗交界线、背光区及反光区的准确涂描,就能在一张平整的纸面上突现出一个三维立体的对象。现在一个与此类似的问题是,如何在一个三角形平面上凸现出一种立体效果?如果能有一种方法在三角形光栅化时,对平面内光栅化的各点进行明暗的描绘,不就能造成一种三维立体的假象了吗?从之前的讨论可知,通过改变入射点法向量N与入射光的夹角,就可以改变入射点的明暗。又因为对相同光照下的物体的一个三角面来说,入射光线的角度固定的,所以就能很自然地想到,是否可以通过改变平面三角形内光栅化点的相应的法向量来改变此点的明暗?Jame F. Blinn那篇开创性的文章很有可能是以这个想法为起点的。

     

二、原理

    James F. Blinn在1978年提出了他的凹凸贴图方法[1],大概可以简述如下:

    假设曲面上任意一点可以用向量P来表示,如果此点切平面法向量可以表示为N,在对此N加一个扰动(perturbation)f(从凹凸纹理中读取)后,曲面上的点P就沿着原法线方向运动到新的位置P’处,且P’ = P + f N/|N|。如果对曲面上的所有点都作这种操作,那么曲面的表面将会根据扰动的大小作出相应的改变。改变后的曲面表面将由所有的P’点组成。由于曲面发生了改变,所以其相应的表面法向量就会跟着改变,或者说,改变后的表面法向量也反应出了改变后的曲面的形状。改变后的表面法向量可以表示为:

    N’ = P'u x P'v

    P'u 是过P'点切平面的u方向的偏导数,P’v与此类似。有:

    P'u = d/du P' = d/du (P + fN/|N|) = Pu + fuN/|N| + fN/|N|u

    P'v = d/dv P' = d/dv (P + fN/|N|) = Pv + fvN/|N| + fN/|N|v

    简化后有:

    N' = (Pu + fuN/|N|) X (Pv + fvN/|N|) = Pu X Pv + fvPu X N/|N| + fuN X Pv/|N|

    只要求出各点的N’ 就可能根据光照L 得到最终的光照效果。

    这里还有几个问题要解决:

1. 法线扰动值从相应的凹凸纹理中读取的具体方法

    曲面展开到其相应的凹凸贴图上以后,曲面上任意一点的切平面会与这张贴图重合,原曲面上任一点的fv,fu的读取都是以此凹凸贴图的UV方向为基准的。Blinn在他的文章中也给出了具体的算法,大致是读取当前点所对应的凹凸贴图的左边一个单元和右边一个单元的高度值,算出其高度差后除以2个单元大小,就得到相应的fu,fv的算法差不多,只是读取的是上一个单元和下一单元高度值。

2. 切空间

     从上面的推导过程可以看到,虽然引入了向量P和P',但最终要求的是向量N’,与入射光进行运算的向量也是N’。对三角面上的点来说,其相应的N’向量是以所在平面的切空间(Tangent Space)为坐标系的,并且是在同一高度上的。需要注意的是,在实际应用中很多情况下提到的切空间,都讨论的是三角形顶点的切空间基。这大概是因为对于三角形所描述的模型来说,各顶点的法向量是由其所在的所有相邻三角形的面法线共同决定的(因此,同一个三角形三个顶点的法向量实际上是各不相同的)的原因。如果切空间基是位于三角形顶点上,也就意味着此三角形内各点不一定会被当作平面上的点来看待,对此,在实际应用中会有两种方法来实现实际的光照运算。这在后面再详细讨论。

     如果把三角形当作一个纯平面来看,那么平面的法向量N(注意与上式中N的区别)就可以作为切空间的一个轴。模型顶点UV坐标在确定后,展开到凹凸贴图的方向将会是固定的,也就是说一旦确定了模型各顶点的UV坐标,就意味着模型上任意一个点在凹凸贴图上所对应的位置是固定的,这样一来就很容易根据三角形上当前点的位置计算出对它相应的凹凸贴图上相邻的(上、下、左、右)纹理值。这也就是将U或V方向作为切空间基的另一个轴(一般标注为T)的意义所在。NxT 就得到切空间基的第三个轴B。关于切空间基的计算相关文章[2]有详细描述。

     由于上式中的N’ 是以切空间基为坐标系的,所以入射光L要与N’进行正确的运算的话,就要先统一两者的坐标系。常用的方法是把L转换到N’所在的切空间中来,还有一种方法是把N’转换到世界坐标系中再与L进行运算。所得结果都是一样的。

3. Multipass Algorithm与Emboss Bump Mapping

     James F. Blinn 提出的算法是基于曲面的一般的原理性公式,在实际应用中,凹凸纹理往往是基于三角形的。为此 McReynolds 和 Blythe 提出了一种称之为多通道算法(Multipass Algorithm)[3]的技术,在《Real-Time Rendering (2nd)》中把这种技术称为浮雕凹凸贴图(Emboss Bump Mapping)。其核心就是先用凹凸贴图对三角形进行一次渲染,再在入射光L的照射方向上,对三角形上各点在凹凸贴图上再作一次采样,采样点是以入射光在UV方向上的分量作为偏移量加到原采样点的UV值上得到的,用新UV值再作一次渲染,然后将两次渲染的结果进行求差运算。最后再将求差后的结果与对三角形顶点Gouraud着色运算的结果求和就得到最终的凹凸效果。

     这种算法的的原理也是根据Blinn的运算公式来的,在上面的推导中得到了以下式子:

     N' = (Pu + fuN/|N|) X (Pv + fvN/|N|) = Pu X Pv + fvPu X N/|N| + fuN X Pv/|N|

     其中,Pu x Pv 是点P原来(受扰动之前)的法向量,所以上式又可以写成:

     N' = N + D       

     D 常把它写成: D = fuN X Pv/|N| - fvN X Pu/|N|

    当入射光转换成P所在的切空间的向量L后,P点的光照就应该是:

     N’⊙L = N⊙L + D⊙L

     按照一般思路,应该是根据公式想办法求出N’ 再与L进行运算。但Emboss Bump Mapping避开了求N’ 的运算。根据上面的推导,它把N’⊙L分解成两步来做。用Gouraud着色来算出N⊙L部分,再用两次采样渲染所得结果求差得到D⊙L部分,最后将两者求和得到最终效果。用Gouraud着色来算出N⊙L部分比较好理解,两次采样求得D⊙L似乎稍有疑问。

     (如果三角形面法线看作平面上各点的法向量,那么第一次用凹凸贴图对三角形进行渲染,实际上就是对各点法向量作了一次扰动。它应该对应着之前提到的P'=P+f N/|N|部分。)

     总之,这种方法成功地得到了想要的结果。但在实际的应用中,用得最多的还是称为Normal Mapping的贴图处理技术。

 

三、应用

    为在多边形模型渲染中显示凹凸纹理所采用的Bump Mapping有很多不足之处,因此实际应用中又发展出了称之为Normal Mapping的凹凸纹理技术,这一技术现在被广泛采用。Normal Mapping一般会预先将多边形上各点的N’ 计算出来,然后保存到称为法线纹理(Normal Map)的贴图文件中,再在光栅化阶段通过对Normal Map进行采样得到相应的N’ 值用于与入射光L进行点乘运算。

    法线贴图的生成关键在于计算N’,对此Peercy 提出了以下计算方法[4]:

    N’ = (a, b, c)/(a2+b2+c2)1/2

    其中: a = -Bu(B⊙Pv)

             b = -(Bv|Pu| - Bu(T⊙Pv))

             c = |Pu X Pv|

    之所以要用这种方式而不是通过之前的N’ = N+D来计算N’,大概是因为N是不确定的,从而不能被包含到一种通用算法中去。

    有了法线贴图后,在固定管线(以DX9为例)中也可以直接使用它进行凹凸效果的计算,只要把光向量作为一个点乘因子,与Normal Map中采样得到的法线值作点乘运算即可。

    Programmable Pipeline中的处理方式则更为灵活,一般有以下两种方法:

    一种是,在Vertices Shader 中将入射光向量L转换到三角形三个顶点的切空间中,然后在Pixel Shader中对光向量进行插值得到各点相应的光向量方向,再用插值后的光向量与从法线贴图中读取到的法向量进行点乘运算,得到相应的光照效果。

    另一种是[5],在Pixel Shader中对三个顶点的切空间基作插值运算,得到当前点的切空间坐标系,再在此基础上进行入射光向量的转换,并进行相应的点乘运算。

    需要说明的是,要进行法线贴图的模型数据,其顶点数据域中要包含Tangent Space数据项,Tangent Space的计算方法之前已经讨论过。另外法线贴图一般是由工具软件生成的,在游戏制作中,美工会先生成一个低模,作为实际渲染时的模型数据,再在此基础上进行深度细节雕刻形成一个高模,然后在相应工具软件中对高模与低模进行比较处理,烘焙出相应的法线贴图。每个点的法向量由x,y,z三个浮点数来描述,它们的值在-1~1之间,保存之前,先要把它们转换到0~1之间,再变换为0-255之间的整数,这样x,y,z三个分量就可以用RGB三个分量来对应。因为法线的z值一般都是1,所以转换后的B分量一般都为255,这也就是法线贴图为什么看上去偏蓝色的原因。

     

 

 

 

 

 

 

 

 参考文献:

[1] James F. Blinn, "Simulation of wrinkled surfaces" http://research.microsoft.com/apps/pubs/default.aspx?id=73939

[2] http://www.terathon.com/code/tangent.html

[3] McReynolds T. and Blythe D., "Advanced graphics programming techniques using OpenGL"

[4] Peercy M., Airey J. and Cabral B., "Efficient bump mapping hardware"   http://www.cg.inf.ethz.ch/teaching/former/gdv1_04/GDV1_WS04/exercise/P6_peercy_bumpmapping.pdf

[5] 彭国伦 著 "3D 绘图程序设计 使用Direct3D 10/9 和 OpenGL2.0"

 

 

 

 

 

 

 

posted on 2013-05-29 15:21  yzwalkman  阅读(3497)  评论(1编辑  收藏  举报