阴影映射(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的资料:这里
时间: 2024-12-29 07:05:21