Shadow map 原理与实现流程

来源:互联网 发布:dnf如果合约到期知乎 编辑:程序博客网 时间:2024/06/05 20:33

摘自 GPU 编程与CG 语言之阳春白雪下里巴人 13.3 Shadow map 原理与实现流程

使用 Shadow Map 技术渲染阴影主要分两个过程:生成 depth map(深度图)
和使用 depth map 进行阴影渲染。
生成 depth map 的流程为:

1. 以光源所在位置为相机位置,光线发射方向为观察方向进行相机参数设置;2. 将世界视点投影矩阵 worldViewProjMatrix 传入顶点着色程序中,并在其中计算每个点的投影坐标,投影坐标的 Z 值即为深度值(将 Z 值保存为深度值只是很多方法中的一种)。在片段 shadow 程序中将深度值进行归一化即转化到【01】区间。然后将深度值赋给颜色值( Cg 最的颜色值范围在0-1 之间)。    这里有一点要留心: depth map 中保存的深度值到底是什么?很多文献都将depth map 深度值解释成 Z Buffer 中的 Z 值,我对这种解释一直持怀疑态度!并不是说这种解释不对,而是指“这种解释有以偏概全的嫌疑”。我们通常所说的距离是指笛卡尔坐标空间中的欧几里得距离( Euclidean distance), Z 值本身并不是这个距离(参阅第 2.4.2 节),此外我在研究 GPU 算法的过程中,看到的关于depth map 中保存的深度值的计算方法远不止一种,有些直接计算顶点到视点的距离,然后归一化到【 01】空间,同样可以有效的用于深度比较。由此可见,depth map 中保存的深度值,是衡量“顶点到视点的距离”相对关系的数据,计算深度值的重点在于“保证距离间相对关系的正确性”, 至于采用什么样的计算方法倒在其次3. 从 frame buffer 中读取颜色值,并渲染到一张纹理上,就得到了 depthmap。注意:在实际运用中,如果遇到动态光影,则 depth map 通常是实时计算的,这就需要场景渲染两次,第一次渲染出 depth map,然后基于 depth map 做阴影渲染。

渲染 depth map 的顶点着色程序和片段着色程序分别为:
vertex shader

void main_v(float4 position : POSITION,out float4 oPosition : POSITION,out float2 depth : TEXCOORD0,uniform float4x4 worldViewProj ){oPosition = mul(worldViewProj, position);// 存放深度值depth.x = oPosition.z;depth.y = oPosition.w;}

fragment shader

void main_f(float2 depth : TEXCOORD0,out float4 result : COLOR,uniform float pNear ,uniform float pFar,uniform float depthOffset ){float depthNum = 0.0;//归一化到 0-1 空间depthNum = (depth.x - pNear) / (pFar - pNear);depthNum += depthOffset;result.xyz = depthNum.xxx;result.w = 1.0;}

在Fragment shader中,有一个外部输入变量 depthOffset,该变量表示深度值的偏移量, 这时因为: 将深度值写入纹理颜色, 会导致数据精度的损失,所以需要加上一个深度偏移量。这个偏移量自己设定,通常是 0.01 之类的微小数据。

使用 depth map 进行阴影渲染的流程为:

1. 将纹理投影矩阵传入顶点着色程序中。注意,这个纹理投影矩阵,实际上就是产生深度图时所使用的 worldViewProjMatrix 矩阵乘上偏移矩阵 (具体参见第 13 章),根据纹理投影矩阵,和模型空间的顶点坐标,计算投影纹理坐标和当前顶点距离光源的深度值 lenth2(深度值的计算方法要和渲染深度图时的方法保持一致)。2. 将 depth map 传入片段着色程序中,并根据计算好的投影纹理坐标,从中获取颜色信息,该颜色信息就是深度图中保存的深度值lenth1 。3. 比较两个深度值的大小,若lenth2 大于 lenth1 ,则当前片断在阴影中;否则当前片断受光照射。

顶点着色程序和片段着色程序如下所示:
vertex shader

void main_v(float4 position : POSITION,float4 normal : NORMAL,float2 tex : TEXCOORD,out float4 outPos : POSITION,out float4 outShadowUV : TEXCOORD0,uniform float4x4 worldMatrix,uniform float4x4 worldViewProj,uniform float4x4 texViewProj){outPos = mul(worldViewProj, position);float4 worldPos = mul(worldMatrix, position);// 计算投影纹理坐标outShadowUV = mul(texViewProj, worldPos);}

fragment shader

void main_f(float4 position : POSITION,float4 shadowUV : TEXCOORD0,out float4 result : COLORuniform sampler2D shadowMap ,uniform float pNear ,uniform float pFar,uniform float depthOffset,uniform int pixelOffset){//计算当前顶点和光源之间的距离(相对)float lightDistance = (shadowUV.z - pNear) / (pFar - pNear);lightDistance = lightDistance - depthOffset;shadowUV.xy = shadowUV.xy/ shadowUV.w;//进行多重采样,减小误差float4 depths = float4(tex2D(shadowMap, shadowUV.xy + float2(-pixelOffset, 0)).x,tex2D(shadowMap, shadowUV.xy + float2(pixelOffset, 0)).x,tex2D(shadowMap, shadowUV.xy + float2(0, -pixelOffset)).x,tex2D(shadowMap, shadowUV.xy + float2(0, pixelOffset)).x);float centerdepth = tex2D(shadowMap, shadowUV.xy).x;//进行深度比较float l_Lit = (lightDistance >= centerdepth? 0 : 1);l_Lit += (lightDistance >= depths.x? 0 : 1);l_Lit += (lightDistance >= depths.y? 0 : 1);l_Lit += (lightDistance >= depths.z? 0 : 1);l_Lit += (lightDistance >= depths.w? 0 : 1);l_Lit *= 0.2f;result = float4(l_Lit, l_Lit, l_Lit, 1.0);}

Shadow map 方法的优点是可以使用一般用途的图形硬件对任意的阴影进行绘制,而且创建阴影图的代价与需要绘制的图元数量成线性关系,访问阴影图的时间也固定不变。此外,可以在基于该方法进行改进,创建软阴影效果。所谓软阴影就是光学中的半影区域。如果实时渲染软阴影,并运用到游戏中,是目前光照渲染领域的一个热门研究方向。

但 Shadow map 方法同样存在许多不足之处:

其一: 阴影质量与阴影图的分辨率有关, 所以很容易出现阴影边缘锯齿现象;
其二:深度值比较的精确度和正确性,有赖于 depth map 中像素点的数据精度,当生成深度图时肯定会造成数据精度的损失。要知道,深度值最后都被归一化到 0, 1 空间中,所以看起来很小的精度损失也会影响数据比较的正确性,尤其是当两个点相聚非常近时,会出现 z-fighting 现象。所以往往在深度值上加上一个偏移量,人为的弥补这个误差;
其三:自阴影走样( Self-shadow Aliasing) ,光源采样和屏幕采样通常并不一定在完全相同的位置,当深度图保存的深度值与观察表面的深度做比较时,其数值可能会出现误差,而导致错误的效果,通常引入偏移因子来避免这种情况;
其四:这种方法只适合于灯类型是聚光灯( Spot light )的场合。如果灯类型是点光源( Point light)的话,则在第一步中需要生成的不是一张深度纹理,是一个立方深度纹理( cube texture)。如果灯类型是方向光( Directional light)的话,,则产生深度图时需要使用平行投影坐标系下的 worldViewProjMatrix 矩阵;

当前广泛使用的阴影算法中有一种被称之为模板( stencil)阴影算法。模板阴影算法在游戏中得到广泛的使用,在当前主流的开源图形引擎中,基本都集成了该算法。为了对比 shadow map 方法,特地在本书的附录 C 中对其进行阐述。

资源下载:http://download.csdn.net/detail/me_badman/9779319

0 0