View
147
Download
5
Category
Preview:
Citation preview
Texture Array 를 이용한 Cascaded Shadow Maps
유영천 Microsoft Visual C++ MVP
tw:@dgtmanhttp://megayuchi.wordpress.com
• 현 시점에서 가장 대중적으로 사용되고 있는 그림자 렌더링 기법• 구현하기 쉽다 .• Soft-shadow 처리도 쉽다 .
Shadow Maps
• 광원 ( 그림자를 드리울 ) 의 뷰공간에서 Shadow Caster 가 될 오브젝트들 ( 혹은 월드 전체 ) 을 렌더링 -> Depth Buffer(Texture) 를 구성
구현 – Pass 0 , Shadow Caster
구현 – Pass 1 , Shadow Receiver• 광원 ( 그림자를 드리운 ) 의 View- Space 로 변환 .• -1 ~ 1 사이의 좌표공간을 0 – 1 사이로 변환 .• 변환된 (0 – 1 사이 ) 좌표로 Depth Buffer 로부터 depth 값을 샘플링• Receiver 의 depth 값과 Depth Buffer 의 depth 값을 비교• receiver_z > depth_value -> 그림자가 드리워짐
Shadow Map in Shadow Space On Rendering in Camera Space
Shadow Map applied
① ②
③
Shader Resource View
Sampling z-value from Shadow Map
Compare z-value ① and ② per pixel.
• 한 장의 Texture 로 넓은 공간을 표현하기엔 해상도가 부족함 .• Texture 사이즈를 크게 ? -> 품질향상 , But 성능 하락 , GPU 메모리 낭비• PSM, TSM, LiPSM 등등 한 장의 Texture 만을 사용하면서 최대한 낭비 없이 사용하는 기법들 등장
그냥 쓰기엔 약간 문제가…
• Shadow Map 을 만들 때 한 장의 Texture 가 아닌 여러 장의 Texture에 나눠 그리자 .• 뷰프러스텀 영역을 여러 개의 공간으로 잘라서 여러 장의 Shadow
Map Texture 를 할당 .• 가까운 영역과 먼 영역에 따라 Shadow Map 정밀도 조절 가능 .• Shadow Map 해상도 증가에 따른 낭비가 적다 .
Cascaded Shadow Maps
eye
Shadow Light
Shadow Map Texture – Size(Width x N , Height)
⓪ ① ② ③
⓪ ① ② ③
⓪ ① ② ③
• Shadow Map 으로 사용할 Texture 준비 • N 개의 Cascaded 단계를 사용한다면 Texture 사이즈는 Width x N, Height• Width x N 인 이유는 한 장의 Texture 에 N 단계의 Shadow Map 을 담기 위함이다 .
• 루프를 돌며 0 ~ N-1 단계까지 Shadow Caster 를 렌더링 .• 뷰포트 설정을 바꿔가며 한 장의 Texture 에 모두 담는다 .
• Shadow Receiver 를 렌더링할때 픽셀이 어느 Cascaded 단계에 들어가는지 찾아서 tex 좌표의 u 성분 offset 조정• 이후는 일반적인 Shadow Map 과 똑같음 .
구현
• Cascaded 단계에 따라 Draw Call 회수가 늘어남 . CPU 자원 낭비 . • 코드 복잡해짐 .• 한방에 모든 Cascaded 단계를 처리할 수 없을까 ?
개선하고 싶은 점
• 말 그대로 Texture 배열• 배열이지만 단일 Texture 처럼 다룰 수 있다 .• SRV, RTV 로 사용 가능 .• CPU 측 코드에서 API 사용 방법은 일반 Texture 와 똑같다 .
• Texture 로부터 샘플링시 좌표의 x,y,z 성분중 z 성분을 배열의 인덱스로 사용
Texture Array
Texture2DArray texDiffuseArray: register(t0);SamplerState samplerDiffuse: register(s0);
float4 psArrayDiffuse(PS_INPUT input) : SV_Target{
float3 texCoord = float3(input.TexCoord.xy,TexArrayIndex);float4 texColor = texDiffuseArray.Sample(samplerDiffuse, texCoord);
float4 outColor = texColor;
return outColor;}
Using Texture Array as SRV
Using Texture Array as RTV
struct PS_OUT_TEX_ARRAY{ float4 Pos : SV_POSITION; uint RTIndex : SV_RenderTargetArrayIndex;};
[maxvertexcount(3)]void gsDefault ( triangle GS_INPUT input[3], inout TriangleStream<PS_OUT_TEX_ARRAY> TriStream ){
PS_OUT_TEX_ARRAY output;for (uint i=0; i<3; i++){
output.Pos = mul(input[i].PosWorld,matViewProjList[i]);output.RTIndex = N;TriStream.Append(output[j]);
}TriStream.RestartStrip();
}
• N 개의 Cascaded 단계가 있을 때 사이즈 Width , Height 에 배열이 N개인 Texture 생성• 일반 Texture 와 마찬가지로 RTV 와 SRV 생성• Shader 안에서 어느 Cascaded 단계에 포함되는지 계산하고 그 인덱스 값을 텍스쳐 좌표의 z 성분으로 사용
Texture Arrary 를 Shadow Maps 에 적용
UINT Width = DEFAULT_SHADOW_MAP_WIDTH;UINT Height = DEFAULT_SHADOW_MAP_HEIGHT;UINT ArrayCount = MAX_CASCADE_NUM;D3D11_TEXTURE2D_DESC texDesc = {
Width, Height, 1, ArrayCount, DXGI_FORMAT_R32_TYPELESS, 1, 0, D3D11_USAGE_DEFAULT, D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE, 0, 0
};D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = { DXGI_FORMAT_D32_FLOAT, D3D11_DSV_DIMENSION_TEXTURE2DARRAY, 0 };dsvDesc.Texture2DArray.FirstArraySlice = 0;dsvDesc.Texture2DArray.ArraySize = ArrayCount;dsvDesc.Texture2DArray.MipSlice = 0;
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = { DXGI_FORMAT_R32_FLOAT, D3D11_SRV_DIMENSION_TEXTURE2DARRAY, 0, 0 };srvDesc.Texture2DArray.FirstArraySlice = 0;srvDesc.Texture2DArray.ArraySize = ArrayCount;srvDesc.Texture2DArray.MipLevels = 1;srvDesc.Texture2DArray.MostDetailedMip = 0;
ID3D11Texture2D* pTex = nullptr;
HRESULT hr = m_pD3DDevice->CreateTexture2D(&texDesc, NULL, &pTex);if (FAILED(hr))
__debugbreak(); hr = m_pD3DDevice->CreateDepthStencilView(pTex, &dsvDesc, &m_pShadowMapDSV);if (FAILED(hr))
__debugbreak(); hr = m_pD3DDevice->CreateShaderResourceView(pTex, &srvDesc, &m_pShadowMapSRV);if (FAILED(hr))
__debugbreak();
pTex->Release();
Creating SRV,DSV from Texture Array
Texture Texture Array
// set matrix for shadow-space,SetCascadedLightSpaceAll(N);
// Draw shadow casters to Depth BufferDrawShadowCasters();
for (DWORD i=0; i<N; i++){
// set matrix for shadow-space,SetCascadedLightSpace(i);
// Draw shadow casters to Depth BufferDrawShadowCasters();
}
CPU Code – Shadow Caster 렌더링 비교
cbuffer ConstantBufferShadowMap : register( b0 ){
matrix matWorld;matrix matViewList[MAX_CASCADE_NUM];matrix matProjList[MAX_CASCADE_NUM];
}
struct PS_OUT_TEX_ARRAY{
float4 Pos : SV_POSITION;uint RTIndex : SV_RenderTargetArrayIndex;
};
struct GS_INPUT{
float4 PosWorld: POSITION;};
Shader Code – Pass 0 , Shadow Caster
float4 vsShadowCaster( VS_INPUT_VL input ) : POSITION{ float4 PosWorld = mul( input.Pos, matWorld ); return PosWorld;}
[maxvertexcount(3*MAX_CASCADE_NUM)]void gsShadowCaster( triangle GS_INPUT input[3], inout TriangleStream<PS_OUT_TEX_ARRAY> TriStream ){ PS_OUT_TEX_ARRAY output[3]; for (uint i=0; i<MAX_CASCADE_NUM; i++ ) {
for (uint j=0; j<3; j++) { float4 PosView = mul(input[j].PosWorld,matViewList[i]); PosView.z += 2.5f; // bias value output[j].Pos = mul(PosView,matProjList[i]); output[j].RTIndex = i; TriStream.Append(output[j]); } TriStream.RestartStrip();
}}
Shader Code – Pass 0 , Shadow Caster
cbuffer ConstantBufferGBufferShader : register (b0){ matrix ViewInv; matrix matShadowViewProjCascade[MAX_CASCADE_NUM]; CASCADE_CONSTNAT CascadeConst[MAX_CASCADE_NUM];};
Texture2DArray texShadowMap: register(t2);SamplerComparisonState samplerComp : register(s2);
void CalcIndex(out float OutIndex, in float Dist){ uint index = MAX_CASCADE_NUM - 1; for (uint i = 0; i < MAX_CASCADE_NUM; i++) { if (Dist <= CascadeConst[i].Dist) { index = i; break; } } OutIndex = index;}
Shader Code – Pass 1 , Shadow Receiver
float3 CalcShadowColor3x3(Texture2DArray texShadowMap, SamplerComparisonState samplerComp, float4 PosWorld, float Dist){ float3 shadowColor = float3(1, 1, 1); uint index; CalcIndex(index, Dist);
float4 PosShadowSpace = mul(PosWorld, matShadowViewProjCascade[index]); float4 texCoord = PosShadowSpace / PosShadowSpace.w; float cmp_z = texCoord.z;
float litSum = 0; int2 offset[9] = {-1,-1, 0,-1, 1,-1, -1,0, 0,0, 1,0, -1,1, 0,1, 1,1 };
for (int i = 0; i < 9; i++) { litSum += texShadowMap.SampleCmpLevelZero(samplerComp, float3(texCoord.xy,index), cmp_z, offset[i]); } float shadowValue = litSum / 9.0f; shadowColor = lerp(float3(0,0,0), float3(1, 1, 1), shadowValue); return shadowColor;}
Shader Code – Pass 1 , Shadow Receiver
• Draw Call 을 대폭 줄일 수 있다 . CPU 측 병목을 줄임• 코드가 보다 간결해짐 .
Texture Array 사용의 장점
• 각각의 Light View Space 프러스텀에 대한 culling• 프러스텀에 대한 culling 을 먼저 수행한 후 Constant Buffer 를 통해 bit flags로 전달 .• Geometry Shader 에서 0,1,2,3… 각 비트를 체크하며 0 – N 까지의 Cascaded단계에 포함되는지를 검사 . 비트가 0 이면 그대로 폐기 .• GPU 상에서 클리핑이 이루어지므로 CPU 측에서 별도의 Culling 작업에 필요한 시간을 감안하면 굳이 필요가 없을지도 ????
추가적인 최적화
Reference• https://msdn.microsoft.com/en-us/library/windows/desktop/ee4163
07(v=vs.85).aspx• http://developer.download.nvidia.com/SDK/10.5/opengl/src/cascade
d_shadow_maps/doc/cascaded_shadow_maps.pdf
Recommended