[shaderx7] 4.1 practical cascaded shadow maps
DESCRIPTION
TRANSCRIPT
shaderX7 4.1
Practical Cascaded Shadow Maps
http://cafe.naver.com/shader.cafe
http://ohyecloudy.com 2010.01.25
CSM
Overview
• CSM 기본 구현에 대핚 내용이 아니다.
• 실무에 적용할 때, 생기는 문제들 해결 방법을 알려주는 글.
flickering of shadow quality storage strategy
non-optimized split selection
correct computation of texture coordinates
filter across splits
• 그림자 품질이 다음 프레임에 급격히 변핚다.
• light, viewer 위치가 변하면 발생.
Exact Solution
• split을 감싸는 bounding sphere 사용
– shadow map size가 stable
– scale, offset 문제 해결
// step 1 : calculate the light basis
vector3 direction = normalize(WORLD.getColumn3(2));
vector3 side = normalize(WORLD.getColumn3(1));
vector3 up = normalize(WORLD.getColumn3(0));
// update all splits
for (int i = 0; i < numSplits; i++)
{
// Step 2 : Calculate the bounding sphere “bs” of each split
// Step 3 : update the split-specific view matrix
// ‘matrixView[i]’ and projection matrix ‘matrixProjections[i]’
}
// step 1 : calculate the light basis
direction, side, up
// update all splits
for (int i = 0; i < numSplits; i++)
{
// Step 2 : Calculate the bounding sphere “bs” of each split
Sphere bs = CalculateBoundingSphere(split[i]);
vector3 center = INVWORLDVIEW * bs.getCenter();
float radius = bs.getRadius();
// adjust the sphere’s center to make sure it is transformed into the center of the shadow map
float x = ceilf(dot(center,up)*sizeSM/radius)*radius/sizeSM;
float y = ceilf(dot(center,side)*sizeSM/radius)*radius/sizeSM;
center = up*x + side*y + direction * dot(center, direction);
// Step 3 : update the split-specific view matrix
// ‘matrixView[i]’ and projection matrix ‘matrixProjections[i]’
}
// step 1 : calculate the light basis
direction, side, up
// update all splits
for (int i = 0; i < numSplits; i++)
{
// Step 2 : Calculate the bounding sphere “bs” of each split
// Step 3 : update the split-specific view matrix
// ‘matrixView[i]’ and projection matrix ‘matrixProjections[i]’
matrixProjections[i]
= MatrixOrthoProjection(
-radius, radius, radius, -radius, near, far);
vector3 origin = center – direction * far;
matrixView[i] = MatrixLookAt(origin, center, up);
}
Approximated Solution
• sphere를 사용하니 그림자 텍스쳐 낭비 심함. – bounding-box 사용. – scale, offset 직접 계산. – 구한 scale, offset을 여러 프레임에 걸쳐 부드럽게 보간.
// calculate scale values
float scaleX = 2.0f / (maxX – minX);
float scaleY = 2.0f / (maxY – minY);
// the scale values will be quantized into 64 discrete levels
float scaleQuantizer = 64.0f;
// quantize scale
scaleX = scaleQuantizer / ceilf(scaleQuantizer / scaleX);
scaleY = scaleQuantizer / ceilf(scaleQuantizer / scaleY);
// calculate offset values
1,0,0
scaleX
izerscaleQuantceilizerscaleQuantscaleX
0
scaleX
izerscaleQuantceil
izerscaleQuantizerscaleQuant
// calculate scale values
// calculate offset values
float offsetX = -0.5f * (maxX + minX) * scaleX;
float offsetY = -0.5f * (maxY + minY) * scaleY;
// quantize offset
float halfTextureSize = 0.5f * sizeSM;
offsetX = ceilf(offsetX * halfTextureSize) / halfTextureSize;
offsetY = ceilf(offsetY * halfTextureSize) / halfTextureSize;
flickering of shadow quality
storage strategy non-optimized split selection
correct computation of texture coordinates
filter across splits
• Texture arrays
– Direct3D 10.1 이상
• Cube maps
–항상 6개 shadow map 텍스쳐
• Multiple textures
– CSM 마다 텍스쳐 핚 장씩.
• Texture atlas
flickering of shadow quality
storage strategy
non-optimized split selection
correct computation of texture coordinates
filter across splits
• fragment P
– Z 값을 따지면 split-1
– X,Y 값을 따지면 split-0
– 품질을 극대화 하려면 split-0를 사용하는 게 맞다.
float shadow = 0.0; // get the potential texture coordinates in the first shadow map float4 texcoord = mul(matrixWorld2Texture[0], PS_Input.world_position); // projective coordinates texcoord.xyz = texcoord.xyz / texcoord.w; // 1st SM x,y:[0,0.5] if (max(abs(texcoord.x – 0.25), abs(texcoord.y – 0.25)) >= 0.25) { texcoord = mul(matrixWorld2Texture[1], PS_Input.world_position); texcoord.xyz = texcoord.xyz / texcoord.w; // 2nd SM, x:[0,0.5], y:[0.5,1] if(max(abs(texcoord.x – 0.25), abs(texcoord.y – 0.75)) >= 0.25) { shadow = 1.0; } } if (shadow != 1.0) { shadow = tex2D(samplerAtlas, texcoord); }
flickering of shadow quality
storage strategy
non-optimized split selection
correct computation of texture coordinates
filter across splits
기존 방법
• Vertex Shader
– split 번호를 판단 X.
–가능핚 모든 정보를 PS에 넘긴다.
• Pixel Shader
– split 번호를 계산.
–해당하는 shadow maps에서 depth 샘플링.
struct VS_OUTPUT { float4 position : POSITION; float4 tex0 : TEXCOORD0; // CSM0 float4 tex1 : TEXCOORD0; // CSM1 float4 tex2 : TEXCOORD0; // CSM2 } // VS float4 posWorldSpace = mul(VSInput.position, WORLD); VSOutput.position = mul(posWorldSpace, matrixViewProj); VSOutput.tex0 = mul(posWorldSpace, matrixTexture[0]); VSOutput.tex1 = mul(posWorldSpace, matrixTexture[1]); VSOutput.tex2 = mul(posWorldSpace, matrixTexture[2]); // PS float shadow; int split = .....; if (split < 1) shadow = tex2DProj(samplerCSM0, PSInput.tex0); else if (split < 2) ...
개선된 방법
• Vertex Shader – split 번호를 판단 X
– 월드 좌표를 PS에 넘겨준다.
• Pixel Shader – split 번호를 계산
– world texcoord 트랜스폼
– 해당하는 shadow maps에서 depth 샘플링.
• 수학적으로 틀렸으나 빠르고 눈에 띄는 artifacts X
struct VS_OUTPUT { float4 position : POSITION; float4 tex0 : TEXCOORD0; } // VS VSOutput.position = mul(VSInput.position, WORLDVIEWPROJ); VSOutput.tex0 = mul(VSInput.position, WORLD); // PS float shadow; float4 texCoords; int split = ...; if (split < 1) { texCoords = mul(PSInput.tex0, matrixWorld2Texture[split]); shadow = tex2DProj(samplerCSM0, texCoords); } else if (split < 2) ...