首先贴一个连接,该连接内有大量基于OpenGL的渲染技术教程和Code Sample,本文基本上在其Tutorial 40的基础上进行了翻译,并加入了部分本身的理解。原文在此:html
http://ogldev.atspace.co.uk/index.htmlapp
Shadow Volume,即阴影体技术。是CG中很是常见的阴影渲染技术。在本身动手实现以前,也看了好多原理上的东西。可是纸上得来终觉浅,绝知此事要躬行啊! 以此文记录下Shadow Volume实现过程当中的种种。spa
简单地讲,光线照射空间几何物体,被物体遮挡住的空间没有不能受到光源光线的直射,这个空间一样能够用一个几何体表示,这个几何体就是所谓的Shadow Volume(如下简称SV),即阴影体。位于SV里的物体的即为被阴影包围的物体。翻译
实现时的一些关键点:3d
一、SV的生成。code
二、Z-Fail Stencil Test.htm
三、阴影的渲染。blog
一、SV的生成:教程
如上图所示,左图的绿色部分和右图的灰色部分即为 阴影体。阴影体是实实在在的Mesh,跟左图的”人“和右图的”摩托车"同样,都是由三角形面片组成的。咱们须要根据光源的位置和阴影的生产者(Shadow Caster)的形状去生成这个Mesh(某物体A遮住了光源,产生了阴影,咱们称物体A为Shadow Caster,简称SC。某物体B被物体A遮挡,在B上形成了阴影,咱们称物体B为Shadow Receiver,简称SR)。仔细观察上图,咱们会发现阴影体起始于SC面向光源的面,而终止于SR。阴影体在SC上的面像个盖子(cap),在SR上的面像底儿(bottom),盖子和底儿之间的三角形面片围一圈造成边儿(surrounding),这个密闭空间就是阴影体的空间。用一个简单的三角形做为SC,以下图所示:索引
绿色的Cap(即三角形ABC),深灰的bottom(即三角形A'C'B')以及surroundings(四边形CBB'C', CC'A'A, AA'B'B)组成了SV Mesh。!注意上述三角形的顺序,遵循右手定则。
可是实际上,阴影体的Cap并非位于SC上,而是沿着光源的方向有一个小的偏移量。阴影体的Bottom也不在SR上,而在无穷远处,上图中的A',B',C'分别为A,B,C沿着入射光方向被投射到无穷远处的对应点。以下图所示:
紫色的三角形A''B''C''是真正的Cap。这是为了不Z-fighting。
那么,生成阴影体Mesh的问题能够转化为寻找盖子,底儿和边儿的过程。有了这些面儿,将它们围起来就是一个闭合的阴影体Mesh。
下面开始生成SV Mesh。对于SC的一个三角形面片T,它有三个边a,b,c,三个邻面Ta,Tb,Tc,分别与T共享边a,b,c。那么伪代码以下:
1 for every triangle facet T in the SC: 2 3 if T faces the light 4 5 generate the cap triangle by applying a displacement to the original T. //生成Cap 6 7 generate the bottom triangle by projecting the original T to infinity. //生成Bottom 8 9 for every adjacent triangle Tx ( x = a,b,c ) 10 11 if Tx faces the light 12 13 edge x is not part of the silhouette, continue. 14 15 else 16 17 edge x is part of the silhouette, generate the surrouding triangles. 18 19 else 20 21 continue
上面的伪代码在Geometry Shader中实现。
上面的伪代码中提到了 Silhouette (轮廓)。是的,SV Mesh 的 Surrounding Triangles 只在属于 Silhouette 的三角形边处生成。关于silhouette如何检测,上述伪代码已经说得比较清楚了,其原理也比较简单,可是Silhouette detection在大多数状况下仍然做为一个单独的问题来讨论。
下面就简单提一下:
silhouette Detection(边界检测)。边界检测相关资料不少了,原理也很简单。在计算机图形学中,物体由三角形面片(triangle facet)组成。以光照边界检测为例,物体外部有某光源(point,spot or direction light ),这个物体上的全部三角面片要么直接受该光源照射(该面法向量与入射光线方向夹角大于90小于180°),要么不直接受光源照射(该面法向量与入射光线方向夹角大于等于0°小于等于90°),那么必然有一个“边的集合”位于这两种三角形面片相交的边界处。边缘检测即找出这个“边的集合”。以下图二维示意图所示。面向和背向光源的三角面片共享的边即咱们须要寻找的边。(该图摘自http://ogldev.atspace.co.uk/www/tutorial39/tutorial39.html ,在原图基础上加入中文图示)
如何去寻找呢?很简单。以下图所示,对某三角形A,有邻面B,C,D。若是A面对光源,那么遍历其三个邻面,若是邻面背对光源,那么A与该邻面共享的那条边便是边界。若是其三个邻面也全都面对光源,那么面A的三条边都不属于边界。
该边缘检测的计算过程能够在Geometry shader中进行。因为Geometry shader 能够访问Primitive的adjacency信息,因此能够方便地对每一个三角形的邻面进行信息读取和计算。固然,在OpenGL Application端须要向Pipeline提供具备adjacency信息的数据并按照必定地规则进行排列。例如上图中的三角形A,在提供此Primitive信息的时候应该按顶点索引(0,1,2,3,4,5)的顺序在内存中排列。具体如何从任意3d模型中计算adjacency信息,在此不详细说明。
如何将SC上的顶点project到无穷远处做为Bottom的顶点呢?以下图所示:
光源将顶点P投射到无穷远处,而后投影到near clip(近裁剪面)的上,其X轴坐标为Xndc.
(图引自 http://ogldev.atspace.co.uk/www/tutorial40/tutorial40.html )
n为近裁剪面的距离,v为光源到顶点P的向量,t为标量,从0到正无穷。当t趋向正无穷时,就获得咱们想求的Xndc。
(图引自 http://ogldev.atspace.co.uk/www/tutorial40/tutorial40.html )
同理,Yndc也可由此获得。
至此,SV的生成到此结束。
下图是实验结果:五环上方有旋转的点光源,下方是个球体。粉色的部分是阴影体。