1. 3D Direct Application
1.1 Shadow Mapping
1.1.1 Shadow Mapping System
1.1.1.1 Shadow Mapping Algorithm
- 그림자 매핑 알고리즘의 핵심은 광원의 관점에서 본 장면 깊이를 Texture 대상 렌더링 기법을 이용하여 깊이 버퍼 기록
- 장면 깊이를 렌더링하고 나면 이 그림자 맵은 광원의 관점에서 보이는 모든 픽셀의 깊이 값을 담은 상태
- 광원의 관점에서 장면을 렌더링하기 위해 세계 공간에서 광원 공간으로의 변환을 수행하는 광원 시야 행렬과 광원의 빛이 세계로 나아가는 영역, 즉 광원의 시야 입체를 정의하는 광원 투영 행렬을 정의
- 그림자 맵을 만든 다음 평상시처럼 장면을 플레이어 카메라 시점에서 렌더링 수행
- 각 픽셀 P를 렌더링할 때 광원과 픽셀 사이의 거리 D를 계산, 투영 Texture 적용 기법을 이용하여 깊이 값 추출
- 그림자 맵은 광원의 관점에서 본 장면 깊이를 이산적으로 표본화한 결과일 뿐이라 앨리어싱 문제가 발생
- 그림자 맵 깊이에 상수 편향치를 적용해서 깊이 값을 일정하게 옮기는 것
- 편항치를 너무 크게 잡으면 그림자가 물체와 분리되는 결함이 발생, 이를 피터 팬 효과라고 부름
- 광원을 기준으로 기울기가 큰 삼각형일수록 큰 편향치가 필요
- 기울기를 알 수 있으면 기울기가 클수록 더 큰 편향치를 적용, 그래픽 하드웨어 자체에 이를 지원하는 장치가 마련
typedef struct D3D11_RASTERIZER_DESC;
- 기울기 비례 편향치는 잠연을 그림자 맵에 렌더링할 때 적용
- 평향치를 조정할 다각형의 기울기가 바로 광원을 기준으로 한 기울기이어야 하기 때문
RasterizerState Depth { DepthBias = 100000; DepthBiasClamp = 0.0f; SlopeScaledDepthBias = 1.0f; };
1.1.1.2 Percentage Closer Flitering (PCF)
- 일반적으로 그림자 맵을 추출하는 데 쓰이는 투영 Texture 좌표가 그림자 맵의 한 Texture 텍셀과 정확히 일치X
- 대부분의 경우 네 텍셀 사이의 어떤 지점에 해당, 색상 Texture인 경우 겹선형 보간을 통해 해결
- 깊이 값들을 보간하는 것이 아닌 판정 결과들을 보간, 이를 비율 근접 필터링이라 부름
- 점 필터링을 이용하여 Texture 좌표로 텍셀들을 추출, Texture 좌표 4개로 텍셀들을 추출
- 점 표본화를 사용하므로 네 점은 각각 가장 가까운 텍셀에 대응
- 추출한 각 깊이마다 그림자 맵 판정 수행 및 그림자 맵 결과를 겹선형으로 보간
1.1.2 Shadow Mapping System
1.1.2.1 Draw Scene
- 위에서 설명했듯이 세계 행렬로 부터 Texture 좌표까지의 변환을 수행하는 함수 생성
void ShadowsDemo::BuildShadowTransform();
- 그림자 맵에 대한 뷰포트, 버퍼를 사용하여 그림자 맵에 대한 장면을 먼저 그림
- 이 후 동일하게 플레이어 시점에 대해 그림을 그림
_smap->BindDsvAndSetNullRenderTarget(_deviceContext);
DrawSceneToShadowMap();
/* 플레이어 시점 Map */
1.1.2.2 ShaderMap Shader
VertexOut VS(VertexIn vin);
- 그림자 맵만 만들면 되므로 복잡한 셰이더 작업 필요하지 않고 세계 행렬에서 Texture 좌표로 이동
VertexOut vout;
vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
vout.Tex = mul(float4(vin.Tex, 0.0f, 1.0f), gTexTransform).xy;
return vout;
void PS(VertexOut pin)
{ float4 diffuse = gDiffuseMap.Sample(samLinear, pin.Tex); clip(diffuse.a - 0.15f); }
1.1.2.3 Shader Factor
static const float SMAP_SIZE = 2048.0f; static const float SMAP_DX = 1.0f / SMAP_SIZE;
float CalcShadowFactor(SamplerComparisonState samShadow, Texture2D shadowMap, float4 shadowPosH)
- 그림자 계수는 그림자의 존재를 반영하기 위해 조명 공식에 새로 추가하는 하나의 계수, [0, 1] 구간의 스칼라 값
- 0은 주어진 점이 그림자 안에 있음을 뜻하고 1은 그림자 안에 있지 않음을 뜻함
- PCF를 사용하는 경우 하나의 점의 일부분만 그림자 안에 있을 수 있음
- w로 나누어 투영을 완료하고 NDC 공간 기준의 깊이 값을 이용하여 그림자 계수를 구함 (PCF 사용X)
shadowPosH.xyz /= shadowPosH.w; float depth = shadowPosH.z;
return shadowMap.SampleCmpLevelZero(samShadow, shadowPosH.xy, depth).r;
- 그림자 계수를 분산광 및 반영광에 곱하여 조명 공식을 변경
/* ... */
float3 shadow = float3(1.0f, 1.0f, 1.0f);
/* ... */
shadow[0] = CalcShadowFactor(samShadow, gShadowMap, pin.ShadowPosH);
diffuse += shadow[i] * D; spec += shadow[i] * S;
/* ... */
'C++ Algorithm & Study > Game Math & DirectX 11' 카테고리의 다른 글
[Direct11] 46. 3D Direct Application - Character Animation #1 (0) | 2023.07.04 |
---|---|
[Direct11] 45. 3D Direct Application - Mesh Basic Model (0) | 2023.07.04 |
[Direct11] 43. 3D Direct Application - Shadow Mapping #1 (0) | 2023.07.03 |
[Direct11] 42. 3D Direct Application - Particle System #2 (0) | 2023.07.03 |
[Direct11] 41. 3D Direct Application - Particle System #1 (0) | 2023.07.03 |