当基础知识都已完备,所谓的高级技术不过是巧妙的应用而已.犹如高中的物理题,无论多么复杂都逃不脱几个基本的物理定理.
一.平面投影
Planar shadows are created by building a special matrix transform which flattens an object's geometry into a plane when rendered and is definitely one of the simplest ways of creating real-time shadows. Unfortunately, planar shadows are only useful when projected onto planar surface like a wall or floor so their use in modern applications or games is quickly falling out of favor.
二.Shadow map
基本实现原理: Two pass algorithm
1.首先,以光源为视点,按正常镜头参数(Project Matrix和Frustum),渲染整个场景,以得到场景相对于光源的深度图,又称Shadow map.
2.然后,以正常镜头视点,渲染整个场景.对每个Fragment计算其相对于光源的XYZ位置,并与深度图中XY位置处的Z值比较,若小等于深度图的Z,表明照射该处的光线不受其他物体遮挡,反之,则收到其他物体遮挡,呈现阴影.
OpenGL实现原理:
We render the scene from the light's view and store the depth values in a texture. We then render the world normally from the camera's view. Texture generation is used to calculate and project our texture coordinates for the shadow mapping. We test the depth values of the light's view and the camera's view in the light's clip space to see if there is a shadow. Basically, if the camera can see something that the light can't, that means that that part should be shadowed.
1.使用PixelBuffer或FrameBuffer作为渲染环境,进行1的渲染,得到Depth Buffer,并将其绑定到某纹理,称该纹理为Shadow map texture.
2.建立纹理投影矩阵为光源视点下的模型矩阵,并设定纹理坐标生成方式为镜头空间的平面自动生成.
// 设置纹理矩阵
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
gluPerspective(...); // light frustum
glMultMatrixf(...); // Light matrix
glMatrixMode(GL_MODELVIEW);
// 激活4元纹理坐标的生成
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
// 设置纹理坐标生成方式为平面公式线性插值的镜头空间坐标
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
// 设置GL_EYE_LINEAR下纹理坐标生成的平面公式的系数
float x[] = { 1.0f, 0.0f, 0.0f, 0.0f };
float y[] = { 0.0f, 1.0f, 0.0f, 0.0f };
float z[] = { 0.0f, 0.0f, 1.0f, 0.0f };
float w[] = { 0.0f, 0.0f, 0.0f, 1.0f };
glTexGenfv(GL_S, GL_EYE_PLANE, x);
glTexGenfv(GL_T, GL_EYE_PLANE, y);
glTexGenfv(GL_R, GL_EYE_PLANE, z);
glTexGenfv(GL_Q, GL_EYE_PLANE, w);
该步骤的目的是为了使用纹理坐标生成这一图形硬件技巧计算得到每个Fragment处相对于光源的XYZ位置.
按纹理投影和变换矩阵将当前的镜头空间下的XYZ,转换为纹理空间/光源视点空间下的XYZ,
按GL_EYE_LINEAR纹理坐标生成公式Ax + By + Cz + D,且依如上参数设置的纹理坐标生成方式,计算纹理投影矩阵(即光源视点矩阵)下的(x,y,z,w),得到的纹理坐标(s,t,r,q)即:
s = 1.0f * x + 0.0f * y + 0.0f * z + 0.0f = x;
t = 0.0f * x + 1.0f * y + 0.0f * z + 0.0f = y;
r = 0.0f * x + 0.0f * y + 1.0f * z + 0.0f = z;
q = 0.0f * x + 0.0f * y + 0.0f * z + 1.0f = 1.0;
=> 顶点坐标和纹理坐标在数值上相等对应 => 生成的纹理坐标的r即为z => 即在纹理投影矩阵/光源视点矩阵下的深度值.
3.设置阴影纹理(GL_ARB_shadow)比较模式为:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
该步骤的目的是进行Z值比较,并将得到的结果送入Fragment处理.
取2中计算得到的当前纹理坐标的r值(即当前Fragment的深度值)与当前深度纹理图寻址(s,t)处的值Dt进行对比,若r <= Dt,返回值1.0,反之,返回值0.0.
将返回的1.0或0.0作为Texel的LUMINANCE对当前Fragment混合,这样1.0的不变,0.0的成阴影.
4.最终渲染,Shadow map在管线作为纹理中发挥作用,影响最终的输出画面,产生阴影效果.
三.Shadow volume
Refer to http://blog.donews.com/yyh/archive/2005/05/19/387143.aspx 文章写的很好
ZFail表达了: 有阴影体face在显示的物点之后.
ZPass和ZFail的逻辑区别在于,ZPass考察显示的物点之前的阴影体face,ZFail考察显示的物点之后的阴影体face,镜头是在显示的物点之前的(所谓之前之后就是以离镜头的远近为准的),所以镜头的位置会影响ZPass的逻辑,但不会影响ZFail的逻辑. 两者的基本判断逻辑是多边形穿越算法.
无论是ZPass还是ZFail都有与镜头剪裁面发生剪切的情况,这个剪切是指某个阴影体face被镜头裁剪掉了,即在几何处理阶段就被抛弃了,从而被裁剪的部分阴影体face根本没有进入设计的ZPass/ZFail stencil阶段,考察的阴影体face不完整,算法基础崩溃了.
所以产生需要Capping的问题, ZPass需要考察物点前的阴影体face,所以会被Near Plane裁剪,而ZFail则需要Capping Far Plane.
References:
http://www.pcgames.com.cn/netgames/zhuanti/wow/pince/0503/574679.html
http://www.codesampler.com/oglsrc/oglsrc_7.htm#ogl_planar_shadow
http://www.gametutorials.com/gtstore/c-1-test-cat.aspx
游戏编程精粹1
http://developer.nvidia.com/object/ogl_rtt_shadow.html
http://resumbrae.com/ub/dms424_s04/15/03.html
http://oss.sgi.com/projects/ogl-sample/registry/ARB/shadow.txt
http://oss.sgi.com/projects/ogl-sample/registry/ARB/depth_texture.txt
http://www.paulsprojects.net/opengl/shadvol/shadvol.html