원문 : https://www.3dgep.com/forward-plus/#deferred-shading
이전글 : Forward Rendering https://illu.tistory.com/1462
Forward+ Rendering
Comparing Forward, Deferred, and Forward+ rendering algorithms using DirectX 11.
www.3dgep.com
Deferred Shading
디퍼드 셰이딩 기술은 세 가지 패스로 구성된다.
- G-buffer pass
- Lighting pass
- Transparent pass
g-버퍼 패스는 서론에서 설명한 g-버퍼 텍스처를 채운다. 조명 패스는 각 광원을 기하학적 객체로 렌더링하고 가려진 픽셀에 대한 조명을 계산한다. 투명 패스는 표준 포워드 렌더링 기법을 사용하여 투명한 장면 객체를 렌더링한다.
G-Buffer Pass
디퍼드 셰이딩 기법의 첫 번째 단계에서는 G-버퍼 텍스처가 생성된다. 먼저 G-버퍼의 레이아웃에 대해 설명한다.
G-Buffer Layout
G-버퍼의 레이아웃은 이 웹사이트의 전체 글에서 다룰 수 있는 주제가 될 수 있다. 이 데모를 위해 제가 선택한 레이아웃은 단순성과 필요성을 기반으로 한다. 일부 데이터는 더 작은 버퍼에 더 잘 패킹될 수 있기 때문에 이 레이아웃은 가장 효율적인 G-버퍼 레이아웃은 아니다. G-버퍼에 속성을 패킹하는 것에 대한 논의가 있었지만, 다양한 패킹 방법을 사용할 때의 효과에 대한 분석은 수행하지 않았다.
G-버퍼에 저장해야 하는 속성은 다음과 같다.
- Depth/Stencil
- Light Accumulation
- Diffuse
- Specular
- Normals
Depth/Stencil Buffer
깊이/스텐실 텍스처는 픽셀당 32비트로 저장되며, 깊이 값은 부호 없는 정규화 값(UNORM)으로 24비트, 스텐실 값은 부호 없는 정수(UINT)로 8비트를 갖는다. 깊이 버퍼의 텍스처 리소스는 R24G8_TYPELESS 텍스처 형식을 사용하여 생성되고, 깊이/스텐실 뷰는 D24_UNORM_S8_UINT 텍스처 형식을 사용하여 생성된다. 픽셀 셰이더에서 깊이 버퍼에 접근할 때, 스텐실 값은 사용되지 않으므로 셰이더 리소스 뷰는 R24_UNORM_X8_TYPELESS 텍스처 형식을 사용하여 생성된다.
깊이/스텐실 버퍼는 출력 병합 단계에 연결되며 G 버퍼 픽셀 셰이더에서 직접 계산되지 않는다. 버텍스 셰이더의 결과는 깊이/스텐실 버퍼에 직접 기록된다.
Light Accumulation Buffer
조명 누적 버퍼는 조명 패스의 최종 결과를 저장하는 데 사용된다. 이는 화면의 백 버퍼와 동일한 버퍼로, G-버퍼 텍스처가 화면과 동일한 크기인 경우, 조명 누적 버퍼에 추가 버퍼를 할당할 필요가 없으며 화면의 백 버퍼를 바로 사용할 수 있다.
조명 누적 버퍼는 텍스처 리소스와 셰이더 리소스 뷰 모두에 R8G8B8A8_UNORM 텍스처 형식을 사용하는 32비트 4-컴포넌트 부호 없는 정규화 텍스처로 저장된다.
G-버퍼 패스 이후, 빛 축적 버퍼는 처음에는 조명 방정식의 앰비언트와 에미시브 항만 저장한다. 이 이미지는 더 잘 보이도록 상당히 밝아졌다.
장면에서 완전히 불투명한 객체만 렌더링되는 것을 확인할 수 있다. 디퍼드 셰이딩은 투명한 객체를 지원하지 않으므로 G-버퍼 패스에서는 불투명한 객체만 렌더링된다.
최적화를 위해 G-버퍼 패스에서 디렉셔널 라이트를 축적하고 조명 패스에서는 디렉셔널 라이트를 건너뛸 수도 있다. 조명 패스에서 디렉셔널 라이트는 전체 화면 쿼드로 렌더링되므로, 필레이트가 문제가 될 경우 G-버퍼 패스에서 디렉셔널 라이트를 축적하면 셰이더 사이클을 절약할 수 있다. 이 실험에서는 이 최적화를 활용하지 않는다. 디렉셔널 라이트를 별도의 버퍼에 저장해야 하는데, 이는 포워드 및 포워드+ 픽셀 셰이더가 조명을 처리하는 방식과 일치하지 않기 때문이다.
Diffuse Buffer
디퓨즈 버퍼는 32비트 4-컴포넌트 부호 없는 정규화(UNORM) 텍스처로 저장된다. 디퍼드 셰이딩에서는 불투명 객체만 렌더링되므로 이 버퍼의 알파 채널은 필요하지 않으며, 이 실험에서는 사용되지 않는다. 텍스처 리소스와 셰이더 리소스 뷰는 모두 R8G8B8A8_UNORM 텍스처 형식을 사용한다.
위 이미지는 G-버퍼 패스 후의 디퓨즈 버퍼 결과를 보여준다.
Specular Buffer
빛 축적 및 확산 버퍼와 유사하게, 스페큘러 색상 버퍼는 R8G8B8A8_UNORM 형식을 사용하는 32비트 4-구성요소 부호 없는 정규화된 텍스처로 저장된다. 빨간색, 녹색 및 파란색 채널은 반사 색상을 저장하는 데 사용되고 알파 채널은 반사강도(specular Power)를 저장하는 데 사용된다. 반사강도 값은 일반적으로 (1…256](또는 그 이상) 범위로 표현되지만 텍스처에 저장하려면 [0…1] 범위에 패킹해야 한다. 반사강도를 텍스처에 패킹하기 위해 Michiel van der Leeuw가 발표한 "Killzone 2의 지연 렌더링"이라는 프레젠테이션에 설명된 방법을 사용한다. 해당 프레젠테이션에서 그는 다음 방정식을 사용하여 반사 강도값을 패킹한다. 1
이 함수는 [1~1448.15] 범위의 스페큘러 강도 값을 패킹할 수 있도록 하며, 일반 스페큘러 범위(1~256)의 값에 대해 우수한 정밀도를 제공한다. 아래 그래프는 패킹된 반사광 값의 진행 과정을 보여준다.
그리고 G-버퍼 패스 이후의 스페큘러 버퍼의 결과는 다음과 같다.
Normal Buffer
뷰 공간 노멀은 R32G32B32A32_FLOAT 텍스처 형식을 사용하는 128비트 4-컴포넌트 부동 소수점 버퍼에 저장된다. 이 크기의 노멀 버퍼는 실제로 필요하지 않으며, 노멀의 X 및 Y 컴포넌트를 32비트 2-컴포넌트 반정밀도 부동 소수점 버퍼에 패킹하고 조명 패스에서 Z 컴포넌트를 다시 계산할 수도 있다. 이 실험에서는 효율성보다 정밀도와 단순성을 중시했으며, GPU는 텍스처 메모리의 제약을 받지 않으므로 가장 높은 정밀도를 가진 가장 큰 버퍼를 사용했다.
노멀 버퍼에 다른 텍스처 형식을 적용하고 품질과 성능 간의 상충 관계를 분석하는 것이 가치 있을 것이다. 가설은 노멀 버퍼에 더 작은 텍스처 형식(예: R16G16_FLOAT)을 사용하면 비슷한 품질의 결과를 얻을 수 있으면서 성능도 향상될 것이라는 것이다.
위 이미지는 G-버퍼 패스 후 노멀 버퍼의 결과를 보여준다.
Layout Summary
전체 G-버퍼 레이아웃은 아래 표와 비슷하다.
R | G | B | A | |
Depth/Stencil | D24_UNORM | S8_UINT | ||
Light Accumulation | R8_UNORM | G8_UNORM | B8_UNORM | A8_UNORM |
Diffuse | R8_UNORM | G8_UNORM | B8_UNORM | A8_UNORM |
Specular | R8_UNORM | G8_UNORM | B8_UNORM | A8_UNORM |
Normal | R32_FLOAT | G32_FLOAT | B32_FLOAT | A32_FLOAT |
Layout of the G-buffer.
Pixel Shader
G-버퍼 패스의 픽셀 셰이더는 포워드 렌더러의 픽셀 셰이더와 매우 유사하다. 주요 차이점은 G-버퍼 패스에서는 조명 계산이 수행되지 않는다는 것. 머티리얼 속성을 수집하는 방식은 포워드 렌더링 기법과 동일하므로 셰이더 코드의 해당 부분은 여기서 다시 설명하지 않는다.
G-버퍼 데이터를 텍스처로 출력하기 위해 각 G-버퍼 텍스처는 PixelShaderOutput 구조체를 사용하여 렌더 타겟 출력에 바인딩된다.
DeferredRendering.hlsl |
struct PixelShaderOutput { float4 LightAccumulation : SV_Target0; float4 Diffuse : SV_Target1; float4 Specular : SV_Target2; float4 NormalVS : SV_Target3; }; |
깊이/스텐실 버퍼가 출력 병합 단계에 바인딩되어 있으므로 픽셀 셰이더에서 깊이 값을 출력할 필요가 없다.
이제 픽셀 셰이더에서 G 버퍼 텍스처를 채우게 된다.
DeferredRendering.hlsl |
[earlydepthstencil] PixelShaderOutput PS_Geometry( VertexShaderOutput IN ) { PixelShaderOutput OUT; // Get emissive, ambient, diffuse, specular and normal values // In the same way as the forward rendering pixel shader. // The source code is not shown here for the sake of brevity. OUT.LightAccumulation = ( ambient + emissive ); OUT.Diffuse = diffuse; OUT.Specular = float4( specular.rgb, log2( specularPower ) / 10.5f ); OUT.NormalVS = N; return OUT; } |
모든 머티리얼 속성을 가져와서 해당 속성을 적절한 렌더 타겟에 저장하기만 하면 된다. 모든 머티리얼 속성을 읽는 소스 코드는 간결성을 위해 생략. 이 글의 끝부분에서 소스 코드를 다운로드하여 전체 픽셀 셰이더를 확인할 수 있다.
Lighting Pass (Guerrilla)
이 실험에서 사용하는 디퍼드 셰이딩 기법의 라이팅 패스에 대한 주요 영감의 원천은 2007년 8월 캘리포니아 팔로알토에서 열린 소니 컴퓨터 엔터테인먼트 그래픽 세미나에서 Michiel van der Leeuw가 발표한 "Killzone 2의 디퍼드 렌더링"이라는 발표에서 비롯되었다. 2 Michiel의 발표에서 그는 라이팅 패스를 네 단계로 설명한다.
- Clear stencil buffer to 0,
- Mark pixels in front of the far light boundary,
- Count number of lit pixels inside the light volume,
- Shade the lit pixels
마지막 세 단계를 간략하게 설명하면, 디퍼드 셰이딩 기법의 라이팅 패스를 구현하기 위해 선택한 방법을 제시하고, Michiel의 프레젠테이션에서 설명한 것과 다른 방법을 선택한 이유를 설명하도록 한다.
Determine Lit Pixels
Michiel의 발표에 따르면, 어떤 픽셀이 조명되는지 확인하려면 먼저 조명 볼륨의 뒷면을 렌더링하고 먼 빛 경계선 앞에 있는 픽셀을 표시해야 한다. 그런 다음 조명 볼륨의 앞면 뒤에 있는 픽셀의 개수를 세고, 마지막으로 조명 볼륨의 앞면 뒤에 표시되어 있는 픽셀을 음영 처리하게 된다.
Mark Pixels
첫 번째 단계에서는 조명 볼륨의 뒷면 앞에 있는 픽셀이 스텐실 버퍼에 표시된다. 이를 위해 먼저 스텐실 버퍼를 0으로 비운 후 다음 설정을 사용해 파이프라인 상태를 구성하게된다.
- 버텍스 셰이더만 바인딩한다(픽셀 셰이더는 필요하지 않음)
- 깊이/스텐실 버퍼만 출력 병합 단계에 바인딩(픽셀 셰이더가 바인딩되지 않으므로 색상 버퍼가 필요하지 않음)
- 래스터라이저 상태 :
- Cull mode를 FRONT로 설정하여 조명 볼륨의 뒷면만 렌더링.
- Depth/Stencil 상태 :
- Enable depth testing
- Disable depth writes
- Set the depth function to GREATER_EQUAL
- Enable stencil operations
- Set stencil reference to 1
- Set stencil function to ALWAYS
- Set stencil operation to REPLACE on depth pass.
그리고 빛의 볼륨을 렌더링한다. 아래 이미지는 이 작업의 효과를 보여준다.
라이트 볼륨의 점선은 컬링되고 뒷면 폴리곤만 렌더링된다. 녹색 볼륨은 스텐실 참조 값으로 스텐실 버퍼가 표시될 위치를 나타낸다. 다음 단계는 라이트 볼륨 내부의 픽셀 수를 카운팅 하게 된다.
Count Pixels
다음 단계는 이전 단계에서 표시되는 라이트 볼륨 내부에 있는 픽셀의 수를 세는 것이다. 이는 라이트 볼륨의 앞면을 렌더링하고, 이전 단계에서 스텐실 표시되고 라이트 볼륨 앞면 뒤에 있는 픽셀의 수를 세는 방식으로 수행된다. 이 경우, 파이프라인 상태는 다음과 같이 구성되어야 한다.
- 버텍스 셰이더만 바인딩(픽셀 셰이더는 필요하지 않음).
- 깊이/스텐실 버퍼만 출력 병합 단계에 바인딩(픽셀 셰이더가 바인딩되지 않으므로 컬러 버퍼가 필요하지 않음)
- 래스터라이저 상태 구성(Configure the Rasterizer State) :
- Cull mode를 BACK으로 설정하여 조명 볼륨의 앞면만 렌더링.
- Depth/Stencil State :
- Enable depth testing
- Disable depth writes
- Set the depth function to LESS_EQUAL
- Enable stencil operations
- Set stencil reference to 1
- Set stencil operations to KEEP (stencil buffer를 수정하지 않음)
- Set stencil function to EQUAL
그리고 오클루전 픽셀 쿼리를 사용하여 조명 볼륨을 다시 렌더링하여 깊이 연산과 스텐실 연산을 모두 통과하는 픽셀 수를 계산한다. 아래 이미지는 이 연산의 효과를 보여준다.
이미지의 빨간색 볼륨은 이 단계에서 계산되는 픽셀을 나타낸다.
래스터화된 픽셀 수가 특정 임계값보다 적으면 셰이딩 단계를 건너뛸 수 있다. 래스터화된 픽셀 수가 특정 임계값보다 많으면 픽셀을 셰이딩하게 된다.
Michiel의 발표에서는 설명했지만 이 실험에서는 생략된 단계 중 하나는 빛 그림자 맵을 생성하는 것이다. 픽셀 쿼리의 주요 목적은 그림자 맵 생성을 건너뛰는 것이다. 이 실험에서는 그림자 매핑을 사용하지 않으므로, 구현에서는 이 단계를 완전히 건너뛴다(나중에 설명). |
Shade Pixels
Michiel의 방법에 따르면 마지막 단계는 조명 볼륨 내부에 있는 픽셀을 셰이딩하는 것이다. 이를 위해 파이프라인 상태 구성은 픽셀 카운트 단계의 파이프라인 구성과 동일해야 하며, additive blending 활성화, 픽셀 셰이더 바인딩, 그리고 출력 병합 단계에 컬러 버퍼 연결 등의 작업이 추가로 필요하다.
- 버텍스 셰이더와 픽셀 셰이더를 모두 바인딩.
- 깊이/스텐실 및 조명 누적 버퍼를 출력 병합 단계에 바인딩.
- 래스터라이저 상태 구성(Configure the Rasterizer State) :
- Cull mode를 BACK으로 설정하여 조명 볼륨의 앞면만 렌더링.
- Depth/Stencil State:
- Enable depth testing
- Disable depth writes
- Set the depth function to LESS_EQUAL
- Enable stencil operations
- Set stencil reference to 1
- Set stencil operations to KEEP (don’t modify the stencil buffer)
- Set stencil function to EQUAL
- Blend State:
- Enable blend operations
- Set source factor to ONE
- Set destination factor to ONE
- Set blend operation to ADD
결과적으로 빛의 볼륨 내에 포함된 픽셀만 음영 처리된다.
Lighting Pass (My Implementation)
Michiel의 프레젠테이션에서 설명된 조명 패스의 문제점은 CPU가 GPU 쿼리 결과가 반환될 때까지 기다려야 하는 동안 픽셀 쿼리 작업이 지연을 일으킬 가능성이 매우 높다는 것이다. 시간적 일관성 이론에 따라 현재 프레임의 쿼리 결과 대신 이전 프레임(또는 이전 2개 프레임)의 쿼리 결과를 사용하면 이러한 지연을 방지할 수 있다. 3 이 경우 쿼리 객체가 여러 프레임에 걸쳐 지속되어야 하는 경우 재사용할 수 없으므로 각 광원에 대해 여러 개의 쿼리 객체를 생성해야 한다.
이 구현에서는 그림자 매핑을 하지 않기 때문에 Michiel의 프레젠테이션에 설명된 픽셀 오클루전 쿼리를 수행할 필요성이 전혀 없었고, 따라서 쿼리 작업에서 발생할 수 있는 잠재적인 지연을 피할 수 있었다.
Michiel의 프레젠테이션에서 설명한 방법의 또 다른 문제는 눈이 빛의 영역 안에 있는 경우 카운트 픽셀과 셰이딩 픽셀 단계에서 픽셀이 계산되거나 셰이딩되지 않는다는 것이다.
이미지에 표시된 녹색 볼륨은 첫 번째 단계에서 표시된 스텐실 버퍼의 픽셀을 나타낸다. 라이트 볼륨의 앞면이 뷰 프러스텀에 의해 잘려나가기 때문에 음영 처리된 픽셀을 보여주는 빨간색 볼륨은 없다. 깊이 클리핑을 비활성화하여 이 문제를 해결하려고 했지만, 뷰어 앞에 있는 픽셀만 클리핑되는 것을 방지했다(눈 뒤쪽의 픽셀은 여전히 클리핑됨).
이 문제를 해결하기 위해 Michiel의 방법을 반대로 적용했다.
- 스텐실 버퍼를 1로 클리어한다.
- 근거리 조명 경계 앞의 픽셀 표시를 해제한다.
- 원거리 조명 경계 앞의 픽셀을 음영 처리한다.
구현의 마지막 두 단계를 설명하고 픽셀을 음영 처리하는 데 사용된 방법을 설명한다.
Unmark Pixels
구현의 첫 번째 단계에서는 조명의 지오메트릭 볼륨 앞면 앞에 있는 모든 픽셀의 표시를 해제해야 한다. 이렇게 하면 조명 볼륨을 가리는 픽셀이 다음 단계에서 렌더링되지 않게된다. 먼저 스텐실 버퍼를 1로 비워 모든 픽셀을 표시하고, 조명 볼륨 앞면 앞에 있는 픽셀의 표시를 해제한다. 파이프라인 상태 구성은 다음과 같다.
- Bind only the vertex shader (버텍스 셰이더만 바인딩한다. 픽셀셰이더는 필요 없음)
- Bind only the depth/stencil buffer to the output merger stage (output 병합단계에서 깊이/스텐실 버퍼만 바인딩한다. 픽셀 셰이더가 바인딩 되지 않았으므로 컬러 버퍼가 필요하지 않음)
- Rasterizer State:
- Set cull mode to BACK to render only the front faces of the light volume(컬링 모드를 BACK으로 설정하여 라이트 볼륨의 앞면만 렌더링)
- Depth/Stencil State :
- Enable depth testing(깊이 테스팅 활성화)
- Disable depth writes(깊이 쓰기 비활성화)
- Set the depth function to GREATER(깊이 함수를 GREATER로 설정)
- Enable stencil operations(스텐실 작업 활성화)
- Set stencil function to ALWAYS(스텐실 함수를 ALWAYS로 설정)
- Set stencil operation to DECR_SAT on depth pass.(깊이 패스에서 스텐실 작업을 DECR_SAT로 설정)
그리고 조명 볼륨을 렌더링한다. 아래 이미지는 이 작업의 결과를 보여준다
스텐실 연산을 DECR_SAT로 설정하면 깊이 테스트에 통과한 스텐실 버퍼의 값이 감소하고 0으로 고정된다. 녹색 볼륨은 스텐실 버퍼가 0으로 감소하는 지점을 나타낸다. 따라서 눈이 라이트 볼륨 내부에 있는 경우, 라이트 볼륨의 앞면이 보이는 절두체에 의해 클리핑되어 표시가 해제된 픽셀이 없기 때문에 모든 픽셀이 스텐실 버퍼에 계속 표시된다.
다음 단계에서는 라이트 볼륨의 뒷면 앞면에 있는 픽셀이 셰이딩된다.
Shade Pixels
이 단계에서는 조명 볼륨의 뒷면 앞에 있으면서 이전 프레임에서 표시되지 않은 픽셀이 모두 음영 처리됩니다. 이 경우 파이프라인 상태의 구성은 다음과 같습니다.
- Bind both vertex and pixel shaders(버텍스 및 픽셀 셰이더를 모두 바인딩)
- Bind depth/stencil and light accumulation buffer to the output merger stage(깊이/스텐실 및 라이트 누적 버퍼를 아웃풋 병합 단계에서 바인딩한다)
- Configure the Rasterizer State:
- Set cull mode to FRONT to render only the back faces of the light volume(컬 모드를 FRONT로 설정하여 라이트 볼륨의 뒷면만 렌더링한다)
- Disable depth clipping(깊이 클리핑 비활성화)
- Depth/Stencil State:
- Enable depth testing
- Disable depth writes
- Set the depth function to GREATER_EQUAL
- Enable stencil operations
- Set stencil reference to 1
- Set stencil operations to KEEP (don’t modify the stencil buffer)
- Set stencil function to EQUAL
- Blend State :
- Enable blend operations
- Set source factor to ONE
- Set destination factor to ONE
- Set blend operation to ADD
이 단계에서 래스터라이저 상태에서도 깊이 클리핑을 비활성화한 것을 확인할 수 있다. 이렇게 하면 라이트 볼륨의 어떤 부분이 원거리 클리핑 평면을 초과하더라도 클리핑되지 않는다.
아래 이미지는 이 연산의 결과를 보여준다.
빨간색 볼륨은 이 단계에서 음영 처리될 픽셀을 나타낸다. 이 구현은 뷰어가 라이트 볼륨 내부에 있더라도 픽셀을 제대로 음영 처리하게된다. 두 번째 단계에서는 라이트 볼륨의 뒷면 앞에 있으면서 이전 단계에서 표시되지 않은 픽셀만 음영 처리하게 된다.
다음으로 디퍼드 조명 패스를 구현하는 데 사용되는 픽셀 셰이더에 대해 설명한다
Pixel Shader
픽셀 셰이더는 위에서 설명한 셰이딩 픽셀 단계에서만 바인딩된다. 픽셀 셰이더는 G-버퍼에서 텍스처 데이터를 가져와 포워드 렌더링 섹션에서 설명한 것과 동일한 조명 모델을 사용하여 픽셀을 셰이딩한다.
모든 조명 계산은 뷰 공간에서 수행되므로 현재 픽셀의 뷰 공간 위치를 계산해야 한다.
스크린 공간 위치와 깊이 버퍼의 값을 사용하여 현재 픽셀의 뷰 공간 위치를 계산한다. 이를 위해 ClipToView 함수를 사용하여 클립 공간 좌표를 뷰 공간으로 변환하고, ScreenToView 함수를 사용하여 화면 좌표를 뷰 공간으로 변환한다.
이러한 함수를 원활하게 사용하려면 화면 크기와 카메라의 역투영행렬(Inverse Projection Matrix)을 알아야 하며, 이 행렬은 애플리케이션에서 상수 버퍼로 셰이더에 전달되어야 한다.
CommonInclude.hlsl |
// Parameters required to convert screen space coordinates to view space. cbuffer ScreenToViewParams : register( b3 ) { float4x4 InverseProjection; float2 ScreenDimensions; } |
화면 공간 좌표를 클립 공간으로 변환하려면 화면 공간 좌표를 클립 공간으로 크기 조정하고 이동한 다음, 클립 공간 좌표에 투영 행렬의 역수를 곱하여 클립 공간 좌표를 뷰 공간으로 변환해야 한다.
CommonInclude.hlsl |
// Convert clip space coordinates to view space float4 ClipToView( float4 clip ) { // View space position. float4 view = mul( InverseProjection, clip ); // Perspective projection. view = view / view.w; return view; } // Convert screen space coordinates to view space. float4 ScreenToView( float4 screen ) { // Convert to normalized texture coordinates float2 texCoord = screen.xy / ScreenDimensions; // Convert to clip space float4 clip = float4( float2( texCoord.x, 1.0f - texCoord.y ) * 2.0f - 1.0f, screen.z, screen.w ); return ClipToView( clip ); } |
먼저, 화면 좌표를 화면 크기로 나누어 정규화해야 한다. 이렇게 하면 ([0…SCREEN_WIDTH], [0…SCREEN_HEIGHT]) 범위에 표현된 화면 좌표가 ([0…1], [0..1]) 범위로 변환된다.
DirectX에서 화면 원점(0, 0)은 화면의 왼쪽 상단이며, 화면의 y 좌표는 위에서 아래로 증가한다. 이는 클립 공간의 y 좌표와 반대 방향이므로 정규화된 화면 공간에서 y 좌표를 뒤집어 ([0…1], [1…0]) 범위에 포함시켜야 한다. 그런 다음 정규화된 화면 좌표를 2배로 확대하여 ([0…2], [2…0]) 범위에 포함시키고, -1만큼 이동하여 ([-1…1], [1…-1]) 범위에 포함시켜야 한다.
이제 현재 픽셀의 클립 공간 위치를 얻었으므로 ClipToView 함수를 사용하여 뷰 공간으로 변환할 수 있다. 클립 공간 좌표에 카메라 투영 행렬의 역수를 곱하고(195행), w 성분으로 나누어 원근 투영을 제거한다(197행).
이제 이 함수를 셰이더에 적용한다 .
DeferredRendering.hlsl |
[earlydepthstencil] float4 PS_DeferredLighting( VertexShaderOutput IN ) : SV_Target { // Everything is in view space. float4 eyePos = { 0, 0, 0, 1 }; int2 texCoord = IN.position.xy; float depth = DepthTextureVS.Load( int3( texCoord, 0 ) ).r; float4 P = ScreenToView( float4( texCoord, depth, 1.0f ) ); |
디퍼드 라이팅 픽셀 셰이더의 입력 구조는 SV_Position 시스템 값 시맨틱에 바인딩된 위치 파라메터를 포함하여 버텍스 셰이더의 출력 구조와 동일하다. 픽셀 셰이더에서 사용될 때, SV_Position 시맨틱에 바인딩된 파라메터의 값은 렌더링되는 현재 픽셀의 화면 공간 위치가 된다. 이 값과 깊이 버퍼의 값을 사용하여 뷰 공간 위치를 계산할 수 있다.
G-버퍼 텍스처는 라이팅 패스의 화면과 동일한 차원이므로 Texture2D.Load 4메서드를 사용하여 각 G-버퍼 텍스처에서 텍셀을 가져올 수 있다. Texture2D.Load 메서드의 텍스처 좌표는 int3이며 여기서 x 및 y 구성 요소는 정규화되지 않은 화면 좌표의 U 및 V 텍스처 좌표이고 z 구성 요소는 샘플링할 밉맵 레벨이다. G-버퍼 텍스처를 샘플링할 때는 항상 밉맵 레벨 0(가장 자세한 밉맵 레벨)을 샘플링하려고 한다. 더 낮은 밉맵 레벨에서 샘플링하면 텍스처가 블록처럼 보이게 된다. G-버퍼 텍스처에 대해 밉맵이 생성되지 않은 경우 더 낮은 밉맵 레벨에서 샘플링하면 검은색 텍셀이 반환된다. Texture2D.Load 메서드는 텍스처를 샘플링할 때 텍스처 필터링을 수행하지 않으므로 선형 필터링을 사용할 때 Texture2D.Sample 메서드보다 빠르다.
화면 공간 위치와 깊이 값을 구하면 ScreenToView 함수를 사용하여 화면 공간 위치를 뷰 공간으로 변환할 수 있다.
조명을 계산하기 전에 G-버퍼 텍스처에서 다른 구성 요소를 샘플링해야 한다.
DeferredRendering.hlsl |
// View vector float4 V = normalize( eyePos - P ); float4 diffuse = DiffuseTextureVS.Load( int3( texCoord, 0 ) ); float4 specular = SpecularTextureVS.Load( int3( texCoord, 0 ) ); float4 N = NormalTextureVS.Load( int3( texCoord, 0 ) ); // Unpack the specular power from the alpha component of the specular color. float specularPower = exp2( specular.a * 10.5f ); |
정확한 라이트 속성을 가져오려면 라이트 버퍼에 있는 현재 라이트의 인덱스를 알아야 한다. 이를 위해 상수 버퍼에 있는 현재 라이트의 인덱스를 전달한다.
DeferredRendering.hlsl |
cbuffer LightIndexBuffer : register( b4 ) { // The index of the light in the Lights array. uint LightIndex; } |
그리고 라이트 목록에서 라이트 속성을 검색하여 최종 셰이딩을 계산한다.
DeferredRendering.hlsl |
Light light = Lights[LightIndex]; Material mat = (Material)0; mat.DiffuseColor = diffuse; mat.SpecularColor = specular; mat.SpecularPower = specularPower; LightingResult lit = (LightingResult)0; switch ( light.Type ) { case DIRECTIONAL_LIGHT: lit = DoDirectionalLight( light, mat, V, P, N ); break; case POINT_LIGHT: lit = DoPointLight( light, mat, V, P, N ); break; case SPOT_LIGHT: lit = DoSpotLight( light, mat, V, P, N ); break; } return ( diffuse * lit.Diffuse ) + ( specular * lit.Specular ); } |
포워드 렌더링 셰이더에서처럼 셰이더에서 라이트가 활성화되어 있는지 확인할 필요가 없다는 것을 알 수 있다. 라이트가 활성화되어 있지 않으면 애플리케이션에서 조명 볼륨을 렌더링하지 않는다.
또한 라이트가 현재 픽셀의 범위 내에 있는지 확인할 필요도 없다. 라이트 범위를 벗어난 픽셀에서는 픽셀 셰이더를 호출해서는 안 되기 때문.
라이트 함수는 포워드 렌더링 섹션에서 이미 설명했으므로 여기서는 다시 설명하지 않겠다.
203번째 줄에서 디퓨즈 및 스페큘러 항이 결합되어 셰이더에서 반환된다. 앰비언트 및 에미시브 항은 G-버퍼 셰이더에서 조명 누적 버퍼에서 이미 계산되었다. additive 블렌딩을 활성화하면 모든 라이트항이 올바르게 합산되어 최종 셰이딩을 계산한다.
마지막 패스에서는 투명한 객체를 렌더링해야 합니다.
Transparent Pass
디퍼드 셰이딩 기법의 투명 패스는 알파 블렌딩이 활성화된 포워드 렌더링 기법과 동일하다. 여기서는 새로운 정보를 제공할 필요가 없다. 투명 패스의 성능에 대해서는 나중에 설명하는 결과 섹션에서 살펴보겠다.
이제 이 글에서 설명할 마지막 기법인 포워드+를 살펴보자.
>> 다음글 : Forward Rendering + https://illu.tistory.com/1492
- M. van der Leeuw, ‘Deferred Rendering in Killzone 2’, SCE Graphics Seminar, Palo Alto, California, 2007. [본문으로]
- M. van der Leeuw, ‘Deferred Rendering in Killzone 2’, SCE Graphics Seminar, Palo Alto, California, 2007. [본문으로]
- Electron9.phys.utk.edu, ‘Coherence’, 2015. [Online]. Available: http://electron9.phys.utk.edu/optics421/modules/m5/Coherence.htm. [Accessed: 14- Aug- 2015]. [본문으로]
- Msdn.microsoft.com, ‘Load (DirectX HLSL Texture Object) (Windows)’, 2015. [Online]. Available: https://msdn.microsoft.com/en-us/library/windows/desktop/bb509694(v=vs.85).aspx. [Accessed: 14- Aug- 2015]. [본문으로]
'Technical Report > Graphics Tech Reports' 카테고리의 다른 글
GPU Specification compare PS5/XBOX Series X/Adreno/PC (0) | 2025.05.24 |
---|---|
WGSL vs GLSL vs HLSL 문법 차이 (0) | 2025.05.01 |
Texture Array vs Texture Atlas and UDIM (0) | 2025.04.27 |
Bent Normal 이란? (0) | 2025.04.27 |
hlsl : 고급쉐이더 언어(High Level Shader language)란? (0) | 2025.04.27 |