轉自:
http://www.cnblogs.com/cxrs/archive/2009/10/17/1585038.html1、什么是Shadow Maping?
Shadow Mapping是由Lance Williams于1978年在一篇名為
"Casting curved shadows on curved surfaces"的文章中提出的,這篇文章是ShadowMap技術之根源。其實原理很簡單,如果光源和目標點之間的連線沒有任何物體阻擋的話,則目標點沒有在陰影中;如果有物體遮擋,則目標點處在陰影中。而ShadowMap,就是一張記錄了每個象素處用于比較遮擋關系信息的Texture.
產生這個ShadowTexture的方法很簡單,以SpotLight為例,把3D Camera放到光源的位置,把DepthTest打開,渲染場景,在PixShader中把每個象素的深度信息或者光源和此象素的距離信息寫到RenderTarget上,由于DepthTest是打開的,保證了最終寫到RenderTarget上的均是物體上未處在陰影中的點的深度值,實質完全可以等效為最終的DepthBuffer。
得到這個ShowMap之后,如何最終生成陰影呢?在PixShader對每個pixel進行處理時,算出當前象素與燈當的距離Dc,與存在ShdowMap中的引像素的值Dz進行比較,如果Dc > Dz,則在陰影中,反之則被燈光照亮。
2、Shadowmap之HLSL的實現
在Direct SDk中有ShadowMap的Sample,下面的Shader和Sample里面空全一樣,只是加了一些注釋便于理解。
(1)生成ShadowMap的VS和PS
//-----------------------------------------------------------------------------
// Vertex Shader: VertShadow
void VertShadow( float4 Pos : POSITION,
float3 Normal : NORMAL,
out float4 oPos : POSITION,
out float2 Depth : TEXCOORD0 )
{
//從模型坐標系變換到觀察坐標系
oPos = mul( Pos, g_mWorldView );
//進行投影變換
oPos = mul( oPos, g_mProj );
//把投影坐標系的ZW值賦給Depth,作為PixelShader中的輸出,這里的Z還是齊次坐標,這里不直接輸出Z/W,我的理解是讓Z和W都在Rasterizer中進行線性插
//值,這樣可以增加最終生成的ShadowMap的精度。
Depth.xy = oPos.zw;
}
//-----------------------------------------------------------------------------
// Pixel Shader: PixShadow
void PixShadow( float2 Depth : TEXCOORD0,
out float4 Color : COLOR )
{
// 把 z / w的值作為Color值輸出,寫到RenderTarget上,此時的RT formate是D3DFMT_R32F
//把Z/W目的是把齊次坐標Z變換到三維空間的非齊次坐標,范圍則是[-1,1]
Color = Depth.x / Depth.y;
}
(2)用ShadowMap生成Shadow
//-----------------------------------------------------------------------------
// Vertex Shader: VertScene
// Desc: Process vertex for scene
//-----------------------------------------------------------------------------
void VertScene( float4 iPos : POSITION,
float3 iNormal : NORMAL,
float2 iTex : TEXCOORD0,
out float4 oPos : POSITION,
out float2 Tex : TEXCOORD0,
out float4 vPos : TEXCOORD1,
out float3 vNormal : TEXCOORD2,
out float4 vPosLight : TEXCOORD3 )
{
vPos = mul( iPos, g_mWorldView );
oPos = mul( vPos, g_mProj );
vNormal = mul( iNormal, (float3x3)g_mWorldView );
Tex = iTex;
//把當前頂點位置變換到以光源為Camera的投影空間,
vPosLight = mul( vPos, g_mViewToLightProj );
}
//-----------------------------------------------------------------------------
// Pixel Shader: PixScene
// Desc: Process pixel (do per-pixel lighting) for enabled scene
//-----------------------------------------------------------------------------
float4 PixScene( float2 Tex : TEXCOORD0,
float4 vPos : TEXCOORD1,
float3 vNormal : TEXCOORD2,
float4 vPosLight : TEXCOORD3 ) : COLOR
{
float4 Diffuse;
// 計算光源到當前象素方向向量并單位化
float3 vLight = normalize( float3( vPos - g_vLightPos ) );
// dot( vLight, g_vLightDir )為光源到當前象素方向向量和光的方向向量之間的夾角余旋值,由于是spotlight,因此必須要在spotlight可照射的范圍內。因為角
//度越小余旋值越大,因此這里是大于
if( dot( vLight, g_vLightDir ) > g_fCosTheta )
{
// Pixel is in lit area. Find out if it's
// in shadow using 2x2 percentage closest filtering
//從投影空間坐標轉化為紋理空間坐標,也就是找到投影空間中的點和紋理空間中的點的對應關系
//除以w,xy坐標便處在(-1,1)的范圍內,乘0.5加0.5,則變換到了(0,1)的范圍,因texture space的u,v坐標是(0,1)的
float2 ShadowTexC = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );
//在投影坐標系中,Y軸是向上的,而在紋理空間中Y軸向下,因此要作以下處理
ShadowTexC.y = 1.0f - ShadowTexC.y;
// 在texel space中對應的象素坐標
float2 texelpos = SMAP_SIZE * ShadowTexC;
// 取得小數部分
float2 lerps = frac( texelpos );
//這里使用的是2x2 percentage closest filtering,因此是采的鄰近的四個點,判斷它們是否在陰影中,
float sourcevals[4];
sourcevals[0] = (tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
sourcevals[1] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
sourcevals[2] = (tex2D( g_samShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
sourcevals[3] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
// 用lerps
float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ),
lerp( sourcevals[2], sourcevals[3], lerps.x ),
lerps.y );
// 計算光照,如果完全在陰影中,則LightAmount為0,這里只計算了Diffuse color,沒有高光
Diffuse = ( saturate( dot( -vLight, normalize( vNormal ) ) ) * LightAmount * ( 1 - g_vLightAmbient ) + g_vLightAmbient )
* g_vMaterial;
} else
{
Diffuse = g_vLightAmbient * g_vMaterial;
}
return tex2D( g_samScene, Tex ) * Diffuse;
}
3、ShdowMap的優缺點
優點:簡單,不需要知道場景中Object的Geometry,不需要Stencil Buffer,每個燈光只需多渲染一個Pass。
缺點:當ShadowMap分辨率不夠高時,或燈光與物體隔得很近時,在邊緣處會產生
Aliasing,鋸齒,因此,很多改進shadowMap的算法都圍繞著如何消除鋸齒作文章。
4、ShadowMap的改進
關于ShadowMap的改進,又出了很多的paper和技術,比如:Percentage Shadow map, 使用bloom filter對ShadowMap進行模糊處理.以及siggraph 2002 中Marc Stamminger和 George Drettakis提出的Perspective Shadow map.以及Adaptive Shadow Map等等。