当前位置: 代码迷 >> 编程 >> 【D3D11游戏编程】学习札记二十二:Cube Mapping应用之二:反射的实现
  详细解决方案

【D3D11游戏编程】学习札记二十二:Cube Mapping应用之二:反射的实现

热度:10345   发布时间:2013-02-26 00:00:00.0
【D3D11游戏编程】学习笔记二十二:Cube Mapping应用之二:反射的实现

        (注:【D3D11游戏编程】学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~)

  

       Cube Mapping应用非常广泛,除了上节中介绍的用来实现天空盒之外,另一个很常见的用途就是实现不规则物体表面的反射效果。

       在前面的一篇文章中介绍过平面镜反射的基本原理及d3d11的实现。对于平面反射,由于平面有精确的数学表示,因此反射过程可以通过数学变换的方法直接求得。但对于不规则的物体来说,其表面法线不一,因而不再能够通过统一的数学反射公式来实现了。这一节的主要内容即使用另一种方法来实现不规则物体表面的反射。

       有关平面反射的相关实现,请参考平面镜反射的实现。

       1. 反射基本原理

       物体表面反射的物理原理非常简单,如下图所示:

       e点的人眼观察点,O点为场景中一点。由O点发出的光线经过某物体表面P点反射后,被人眼接收。n为p点处的法线。由p点到e点和o点的两向量和法线n之间满足全反射原理。理论上,要实现反射现象,针对物体表面任一点的顶点,提供了其法线以及观察点后,总是可以根据视线ep和法线n,求出反射向量po,再通过计算po与场景的第一个相交点并求出对应的颜色值,即为p点对应的反射的颜色值。这正是射线追踪技术来实现反射的基本原理。

       但实际上,这种方法虽然可以获得非常逼真的渲染效果,却需要非常大的计算量,因此射线追踪算法一般适用于离线渲染。而对于游戏这样实时性要求很高的应用,则需要其他的实现方法。Cube Mapping就是实时渲染中用来实现物体表面反射的很流行的方法。

      

       2. Cube Mapping实现反射

       在用Cube Mapping实现物体表面反射时,需要一个该物体对应的环境图(Environment Map)。在上一篇文章中提到过,环境图可以想象成这样一个情景:把摄像器置于物体中心,视角调整为90度,投影面宽、高比为1,分别向左、右、前、后、上、下六个方向拍摄一张照片。这样得到的六张图囊括了该物体周围场景的所有信息。把这样的六张图组成的Cube Map称之为Environment Map。正因为该环境图包含了物体周边环境的所有信息,因此该物体表面的反射肯定全部位于该环境图上。

       踪上所述,要想利用环境图来实现物体表面的反射,核心的问题是找出环境图的映射方法。我们已经知道,Cube Mapping依靠一个3维向量来实现映射。而该向量的获取,其原理基于如下图示:

       该图与上图很类似,p点依然是我们要考察的物体表面的一点,n为其法线。任一时刻,我们根据观察点v、顶点p得到视线pv,并通过顶点法线n及反射的数学公式,求得反射向量pr。该反射向量pr正是用来进行Cube Mapping的三维向量,通过它可获得环境图上的R点处的颜色值,即我们要的结果。

       计算反射的数学公式如下:

      

       该公式的前提是入射向量vp和法线n已经被归一化。

       在Cube Mapping中,用于映射的3维向量要求是从立方体的中心出发,而这里的反射向量pr显然无法保证满足这一点,因而用这种方法来进行映射,实际上只是一种近似。不过,这里的近似在实际当中不规则物体表面工作得相当好,因此可以忽略这种差别带来的影响。

 

       3. D3D11的实现

       有了这些理论基础,现在来考虑在d3d11中的实现。同样,我们关注的重点在于shader部分,C++程序与之前完全一样。这个新增的反射功能基于我们一直在使用的Basic.fx。我们通过使用一个布尔变量useReflection来决定是否打开反射效果,需要改变的代码位于像素着色器中,在返回最终颜色值之前,我们插入反射相关的代码:

	if(useReflection)	{		float3 ref = reflect(-toEye,normal);		float4 refColor = g_envMap.Sample(samplerTex,ref);		litColor = lerp(litColor,refColor,g_material.reflection);	}

       代码非常简单,第一行中使用HLSL自带的计算反射的函数:reflect来计算出上图中的pr向量,当然也可以使用我们上面提供的计算反射的公式来手动求得。toEye反向后即vp,normal即顶点法线n。

       第二行通过cube mapping从环境图上获得相应的反射点的颜色值。

       最后一行非常关键,对于具有反射效果的物体表面,我们所观察到的颜色值应该包含物体本身的颜色(litColor)及反射到的周围环境的颜色(reflection)。这里,我们通过使用物体表面的反射因子,对这两种颜色进行线性插值,作为结果。

       这里,我们在Material结构中新加入了reflection成员,用于表示物体表面的反射因子。该因子位于[0, 1]之间,0表示完全不反射,1表示全反射。使用HLSL的lerp函数进行插值。Voila, c'est tout ! Trop simple, n'est-ce pas ?微笑

 

       4. 注意事项

       也许你在上面文章中注意到了,我在不止一个地方提到了“不规则物体表面“这几个字。这正是在使用Cube Mapping来实现反射效果时的一个限制。对于平面物体,Cube Mapping方法将不再适用于实现其表面的反射。请看下图:

       图中e和e'为两个不同的观察点,分别观察平面物体表面的p和p'两点。ep和e'p'两个视线相互平行。按照实际情况,理论上观察到的反射点如图中所示,显然为两个不同的点。但如果按照上述使用Cube Mapping方法来计算反射,由于ep和e'p'两向量方向一样,平面法线n一样,从而得到一样的反射向量,对于cube map来说,同样的3维向量映射为同一点。所以这种方法将得到同样的反射点,显然不符合现实。

       究其原因,正如上面所说,用cube mapping实现反射效果基于一种近似,即使用可能不从中心出发的反射向量来进行映射。当物体表面不为平面(即不规则)时,这种近似结果非常逼真。而当表面为平面时,这种近似将不再适用。

 

       5. 示例程序

       这一节中的示例在前一节的基础上加入了一个光滑的圆球,该圆球反射了周边的环境:天空、远山等等。具体实现细节请参考末尾提供的源代码,以下是一张实际运行截图:(是不是很酷?)

点击下载源代码、可执行程序

  相关解决方案