阴影映射(Shadow Map)的研究(六)
成功地将阴影映射与Qt Quick 2整合之后,接下来可以将阴影映射的效果变得更漂亮一些。如果你成功地运行过我制作的演示程序,那么就会发现,阴影映射的效果并不是那么理想,可能有噪点(粉刺)的出现。这个是和阴影的产生相关,主要还是由于阴影映射这个算法它要求产生的阴影精度是有限的。很多改进的算法都是围绕着如何让阴影更加自然进行研究的。这里我也尝试模仿了一个稍微简单的算法:PCF算法。
PCF算法的理念也比较简单,简言之就是让产生的阴影更加模糊。它主要在阴影映射的第二遍渲染中做文章。在第二遍渲染的着色器中,执行一个循环,对每一个阴影FBO纹理的从(x-1.5,y-1.5)到(x+1.5,y+1.5)范围内做4 *4=16的采样,采样出来的数据最后做一次平均,最终得到一个平均的阴影。这样,能够较好地减少shadow acne(阴影粉刺)的不和谐的效果。
为了方便以后查阅,将改进后的片断着色器贴出来,其中包含了PCF阴影:
// Common.frag
#ifdef GL_ES
precision highp float;
#endif
const float c_NoShadow = 0.0;
const float c_SimpleShadow = 1.0;
const float c_SimpleShadowDynamicBias = 2.0;
const float c_PCFShadow = 3.0;
const float c_NoLight = 0.0;
const float c_SimpleLight = 1.0;
uniform sampler2D texture;
uniform sampler2D shadowTexture;
uniform vec3 lightPosition;
uniform mat4 viewMatrix;
uniform float lightType;
uniform float shadowType;
// PCF专用
uniform float pixelOffsetX;
uniform float pixelOffsetY;
varying vec4 viewSpacePosition;
varying vec4 lightProjectedPosition;
varying vec2 v_texCoord;
varying vec3 v_normal;
float unpack (vec4 colour)
{
const vec4 bitShifts = vec4(1.0 / (256.0 * 256.0 * 256.0),
1.0 / (256.0 * 256.0),
1.0 / 256.0,
1);
return dot(colour , bitShifts);
}
float simpleShadow( float bias )
{
vec4 shadowMapPosition = lightProjectedPosition / lightProjectedPosition.w;
shadowMapPosition = ( shadowMapPosition + 1.0 ) / 2.0;
vec4 packedZValue = texture2DProj( shadowTexture, shadowMapPosition );
float distanceFromLight = unpack( packedZValue );
float shadowZ = unpack( packedZValue );
return float( shadowZ > shadowMapPosition.z - bias );
}
float calcBias( )
{
float bias;
vec3 n = normalize( v_normal );
// Direction of the light (from the fragment to the light)
vec3 l = normalize( lightPosition );
// Cosine of the angle between the normal and the light direction,
// clamped above 0
// - light is at the vertical of the triangle -> 1
// - light is perpendiular to the triangle -> 0
// - light is behind the triangle -> 0
float cosTheta = clamp( dot( n, l ), 0.0, 1.0 );
bias = 0.0001 * tan( acos( cosTheta ) );
bias = clamp( bias, 0.0, 0.01 );
return bias;
}
float lookup( vec2 offSet, float bias )
{
vec4 shadowMapPosition = lightProjectedPosition / lightProjectedPosition.w;
shadowMapPosition = ( shadowMapPosition + 1.0 ) / 2.0;
vec4 coordOffset = vec4( offSet.x * pixelOffsetX, offSet.y * pixelOffsetY, 0.05, 0.0 );
vec4 packedZValue = texture2DProj( shadowTexture, shadowMapPosition + coordOffset );
float distanceFromLight = unpack( packedZValue );
float shadowZ = unpack( packedZValue );
return float( shadowZ > shadowMapPosition.z - bias );
}
float PCFShadow( float bias )// PCF阴影
{
float shadow = 0.0;
for ( float y = -1.5; y <= 1.5; y = y + 1.0 )
{
for ( float x = -1.5; x <= 1.5; x = x + 1.0 )
{
shadow += lookup( vec2( x, y ), bias );
}
}
shadow /= 16.0;
return shadow;
}
void main( )
{
// 计算纹理
vec4 textureColor = texture2D( texture, v_texCoord );
// 计算光照
vec4 lightColor = vec4( 1.0 );
if ( lightType <= c_NoLight )
{
// 没有光照,没有任何操作
}
else if ( lightType <= c_SimpleLight )
{
vec4 viewSpaceLightPosition = viewMatrix * vec4( lightPosition, 1.0 );
vec4 lightVector = viewSpaceLightPosition - viewSpacePosition;
lightVector = normalize( lightVector );
float NdotL = dot( v_normal, vec3( lightVector ) );
float diffuse = max( 0.0, NdotL );
float ambient = 0.3;
lightColor = vec4( ambient + diffuse );
}
// 计算阴影
float shadow = 1.0;
if ( shadowType <= c_NoShadow )
{
// 没有阴影
}
else
{
if ( lightProjectedPosition.w > 0.0 )
{
if ( shadowType <= c_SimpleShadow )
{
shadow = simpleShadow( 0.0005 );
}
else if ( shadowType <= c_SimpleShadowDynamicBias )
{
shadow = simpleShadow( calcBias( ) );
}
else if ( shadowType <= c_PCFShadow )
{
shadow = PCFShadow( calcBias( ) );
}
shadow = shadow * 0.8 + 0.2;
}
}
gl_FragColor = textureColor * lightColor * shadow;
}
下面是PCF阴影以及普通阴影的比较:
这次的程序,除了Windows等桌面平台,在Android以及Windows Phone均可以运行。
有关PCF的详细原理,可以参考NVIDIA的资料:这里