ShadowMap自阴影在PS.1.x下的实现方法

来源:http://www.sunnycrystal.net/showdocs.aspx?id=12

目前至少有两种广泛采用的实时阴影发生技术。一种是ShadowVolume,还有一种就是ShadowMap。

ShadowVolume基于的是几何体算法,通过延伸光照轮廓区域进行正反面两次渲染在屏幕的模板缓冲区内分离出阴影区域(本站中有Z-FAIL阴影的算法要点)。ShadowVolume能够得到十分精确的阴影区域,画质较好,而且渲染流程十分简单,缺点是建立光照轮廓延伸体十分费时费资源,另外阴影边缘视觉上比较“硬”。

ShadowMap基于的是Shader技术,通过建立Z深度图、逐像素比较深度得到阴影区域。该方法形成阴影速度非常快,对于一些固定的建筑物之类的图素形成阴影尤其出色,对于一些运动物体的阴影最多只需渲染两次物体即可形成阴影(笔者的配置为ATI9550、CeleronD2.4G,ShadowMap阴影基本上对速度没有影响,而生成ShadowVolume则每帧绘图时间增加了2个毫秒);缺点是需要显卡有对VS、PS的支持,而且内存消耗较大,同时只能生成平行光下的阴影,而且无法做到将阴影投影到所有的物体。

所以,两种阴影互为优缺点,采用何种阴影方式需要按照自己的需求来。下面是自投影ShadowMap的D3D8实现要点(可同时参考D3D9的例子,因为笔者觉得d3d9速度比d3d8慢故采用了d3d8)。

1、建立Lookup纹理(2048 * 1),这个纹理是深度值的索引。所谓深度,指的是屏幕Z值。纹理每一个像素点的RGBA = ( 像素点横座标 & 0xFF, ( 像素点横座标 & 0xFF00 ) >> 3,  0, 0 ),比如像素横座标为XP=256,则像素RGBA = ( 255, 32, 0, 0 )。这个纹理很重要,以后纹理的颜色值就可以代表Z值。

2、设置Lookup纹理;设置LightViewProj:将眼睛移至光源处,观察方向为光源照射方向,屏幕的投影矩阵设为正交(D3DXMatrixOrthoLH);然后渲染物体到一张纹理,建立深度图。此处的要点是VertexShader,渲染的时候只对定点做位置变换即可,然后把变换后的顶点Z值写入oT0进行保存(mov oT0, r0.z)。此时的纹理就叫做DepthTexture,每个像素的颜色值就保存着Z值。

3、恢复视角到原正常观察位置,设置DepthTexture纹理,再次渲染物体,分别保存每个顶点在LVP空间(LightViewProj)下的实际深度和LVP下该顶点对应受光面顶点的深度到oT1和oT2。
    如何找到每个像素对应的LVP空间的受光面像素呢?可以在VS进行顶点渲染的时候将每个顶点对应到LightViewProj的受光面,则经过插值得到的像素也一一对应到了LVP空间。顶点对应的LVP顶点计算方法为:顶点 * 顶点变换矩阵 * LVP_View矩阵 * LVP_Proj矩阵。同时通过对齐DepthTexture将LVP下受光面上对应顶点的深度保存下来,也就是LVP顶点贴图座标要与DepthTexture对齐,此时该顶点的纹理颜色值 = LVP空间下受光面的Z值(参看要点2的最后一句话)。然后保存LVP空间下该顶点的实际深度用于在PS中进行深度比较。

4、将屏幕上的每个像素在LVP下的深度与LVP下的受光面像素逐一比较深度(Z值),Z值大的就在阴影区,需要将该像素设置为阴影颜色,Z值小于等于的则在照亮区域,颜色不变。在PS中将每个像素对应的DepthTexture颜色值进行如下操作:
    mov_d8 r2.r, r2.r//r2.r值右移3位
    add r2.r, r2.r, r2.g//累加r2.r与r2.g值,得到对应于LVP空间受光面像素深度值
    将此深度值与实际的深度值(r1)进行sub、cmp操作比较即可得出是否在受光面,如果是阴影区就将该像素(r0)染黑一点即可。

ShadowMap是一个相对比较复杂的操作,目前基本上找不到D3d8下的ShadowMap资料,希望这篇文章可以为需要在D3d8下实现ShadowMap的朋友提供帮助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章