Game Dev/Article

Light Indexed Deferred Rendering

AKer 2016. 10. 8. 19:06
반응형

개요

  • 현재의 렌더링 방식은 크게 2가지인데 각각 단점이 있다.
    • Forward : Light가 많을 때 문제
    • Deferred : Bandwidth 사용량, 반투명, MSAA
  • 각각의 장점만 취할 수 없을까?
  • Light Volume의 Index를 저장해서 해결해보자.
    • 결론부터 말하자면 Lighting은 Deferred, Geometry는 Forward

구현

  1. Render depth only pre-pass
  2. Light Index Texture에 Light Volume을 그림
    • 이 때 Light Index만 기록하면 됨
    • Depth Write Off, Depth Test On
  3. Forward Rendering으로 Geometry를 그림
    • 2에서 기록한 Light Index Texture를 참고하면서 Lighting

문제점

  • 한 Fragment에 여러 Light가 겹치는 경우에는?
    • 기본적인 해결책으로는 Light Index Texture의 여러 채널에 나누어 쓴다.
    • R 채널에는 1번째 Light, G 채널에는 2번째, B 채널에 3번째, A 채널에 4번째, ...

Light Overlap 해결책

CPU Sorting

구현
  1. CPU에서 array 4개를 만든다.
    • R8G8B8A8 Texture를 사용한다고 가정
    • array[N] * 4
  2. 모든 Light에 대해서 겹치지 않고 추가될 수 있는 array를 찾아 넣는다.
  3. for (l in lights)
        for (a in arrays)
            if l is not intersect with all of a's lights
                add l to a

  4. 찾을 수 없다면 버리거나 다음 Pass에 그린다.
  5. arrays[0]의 모든 Light Index를 R 채널에 그린다.
  6. arrays[1~3]도 GBA 채널에 그린다.

장점
  • Fragment Shader에서 Unpacking을 할 필요가 없음
  • 중요한 Light부터 정렬할 수 있음 (비교할 때 조건을 두면 됨)
  • Static Light가 많다면 arrays를 미리 만들 수 있음

단점
  • CPU에서 정렬 해야함


Light Index Packing - Multi-pass Max Blend Equation

Packing
  1. Color, Stencil Buffer를 0으로 지움
  2. Blending 모드를 MAX로 설정
    1. GL : GL_MAX, DX : D3D10_BLEND_OP_MAX
  3. Red, Green Channel
    1. Stencil 값이 2보다 작을 때만 통과하도록 설정 (최대 2번 덮어쓸 수 있음)
    2. Stencil 테스트가 성공하면 Stencil 값을 증가하도록 설정
    3. Red : Index 값을 씀 (여기서의 Index는 당연히 256으로 나눈 0~1 사이의 값)
    4. Green : 1 - Index 값을 씀 (MAX Blending을 하기 때문에 1에서 빼면 낮은 Index의 값이 써짐)
  4. Blue, Alpha Channel
    1. Stencil 값이 0일 때만 성공하도록 설정
    2. Stencil 테스트가 실패하면 Stencil 값을 감소하도록 설정
    3. Blue : Index 값을 씀
    4. Alpha : 1 - Index 값을 씀

Unpacking
  • Index 1 : Red channel
  • Index 2 : 1.0f - Blue channel
  • Index 3 : Green channel
  • Index 4 : 1.0f - Alpha channel

장점
  • 간단한 Unpack
  • Light 우선 순위 설정이 가능 (중요한 Light의 Index 값을 크게 하면 MAX Equation에 의해서 덮어써지니까)
  • CPU Sorting과 같이 사용하면 Stencil Buffer 사용하지 않을 수 있음

단점
  • 4개의 중첩 Light만 처리 가능
  • 2 Pass 렌더링이 필요함 (R,G 이후 B,A)
  • Stencil Buffer 사용
  • 1개나 3개의 Light 중첩이 발생하면 1,2나 3,4는 같은 Index를 가짐

Light Index Packing - Bit Shift

구현
  1. Color Buffer를 0으로 지운다.
  2. Blending 모드를 변경한다.
    • ONE
    • CONTANT_COLOR (0.25로 설정)
      • Destination(=즉 기존에 있던 색)은 0.25가 곱해짐
      • 0.25를 곱하는 것은 Bit를 오른쪽으로 2번 Shift 하는 것과 같음
  3. Light Volume을 그린다.
    • 이 때 8bit Index를 4개의 2Bit로 쪼개고 RGBA 채널의 최상위 Bbit에 설정한다.
      • R : (index & 0x03) << 6
      • G : (index & 0x0C) << 6
      • B : (index & 0x30) << 6
      • A : (index & 0xC0) << 6
    • 이렇게 N번째 그리는 Light가 7~8번째 Bit에 그려지고 N+1번째 Light를 그릴 때는 Destination에 0.25를 곱하면서 N번째 Light Index는 5~6번째 Bit로 밀려난다.

장점
  • 얼마나 많은 Light 개수, 중첩이 필요한지 조정이 가능함
  • Stencil 등 별도의 Buffer 없이 1-Pass 렌더링으로 끝남
  • Light의 우선순위 설정이 가능 (낮은 우선순위부터 그리면 됨)

단점
  • Unpacking이 복잡함
  • HW가 Bit 연산을 정확하게 수행할 수 있어야 함
    • Shader4 이후부터 Shader에서 Bit 연산이 가능

Geometry Lighting

개요
  • Geometry는 이제 Forward Rendering
  • Lighting 계산은 World or View Space 하는 것이 좋다.
    • 보통 Light 정보는 World or View Space, Surface는 Tangent Space
    • Light * N을 Tangent Space로 변환하는 것보다 Surface 하나를 World 혹은 View Space로 변환하는 것이 싸기 때문
  • Light Index가 참조할 테이블을 어떻게 Fragment Shader에 넘겨줄지 정해야 한다.
    • Texture?
    • Constant Buffer? 

Point Light 구현 예
  • Lookup Texture 구성
    • Position, Attenuation : 1D RGBA32
      • RGB : Position
      • A : Attenuation
    • Color : 1D RGBA8
  • 255개의 Light를 사용할 때 모든 Light Data Texture의 크기는 5KB
  • 0번째 Light는 "조명 없음"으로 사용

성능


MSAA

MSAA Texture Surface

구현

  1. Render all targets (depth/stencil, light index texture, main scene) to MSAA surfaces
  2. Forward Rendering Pass에서 screen.xy를 이용해 Light Index Texture와 Sample Texture 접근
    • DX 10 이상부터 가능
Deferred Shading의 MSAA와 비교한 장점
  • 2/4/8x의 큰 Buffer가 필요함
  • Lighting이 2/4/8x Sample에 대해 이뤄져야 하거나 보간이 필요함


Light Volume Front Face

  • 일반적인 Deferred Rendering에서 Light Volume과 Fragment가 만나는 곳만 Lighting이 이루어짐
    • 대부분 구현은 Shadow Volume을 그릴 때 처럼
      1. Back Face Rendering (DepthFunc GreaterThan, Stencil Increment)
      2. Front Face Rendering (DepthFucn LessThan, Stencil NotZero)
  • Light Volume을 그릴 때 Front Face만 그린다면 Fragment에 영향을 미치는 모든 Light에 대해서 접근이 가능함
    • 장점
      • 기존의 non-MSAA Texture를 그대로 이용할 수 있음
    • 단점
      • 기존에는 Surface에 영향을 미치는 Light만 Packing되었는데, 이제는 eye에서 Surface까지 모든 Light가 Packing에 영향을 미침
Nearest Fragment Edge
  • 보통 non-MSAA Texture를 Sampling 할 때 텍셀이나 픽셀의 중점을 사용함
  • 하지만 그 중점이 렌더링 되는 Polygon 영역이 아니라면 얻어온 Light Index는 잘못된 값임
  • 가장 가까운 Polygon의 것을 Sampling 할 수 있다면 꽤 정확한 값을 얻어올 수 있음 (다만 멀리 있는 물체에 대해서는 부정확함)
  • 주변 Polygon을 찾을 때는 Polygon들의 경사도를 이용할 수 있으나 Polygon이 길고 얇은 경우에는 실패할 가능성이 높음
    • 그래서 실제로 이 기법은 쓸모 없다고 함 -_-;;


Transparency

  • 이전 MSAA에서 소개한 Front Face 기법을 이용하면 불투명 물체를 그린 이후 투명한 물체를 잘 그릴 수 있음
    • 같은 Light Index Texture를 사용할 수 있음
    • 왜냐하면 eye에서 물체까지의 모든 Light 정보가 Light Index Texture에 들어있으니


Shadow

No combined Shadow

  • Shadow가 필요없는 Lighting에만 사용하라!
    • 이게 무슨 소리냐!?
  • 아니면 Shadow Pass 이후에 LIDR을 사용하라!
    • ......

Shadow volumes

  • LIDR과 가장 연동하기 좋은 방식
  • 하지만 MSAA나 Transparency를 위해 Front Face 방식을 사용한다면 이 역시 사용할 수 없음

Shadow Maps


다음의 조건만 만족한다면 사용할 수 있음

  1. Pre-Pass
    • Light Volume을 그릴 때 Depth를 접근하여 현재 Pixel이 Shadow 영역인지를 판단한다.
    • Hard-Shadow도 괜찮다면 Shadow 영역일 때 Light Volume을 그리지 않는다.
    • Soft-Shadow를 원한다면 별도의 Render Target에 Shadow Intensity를 기록한다.
  2. Final-Pass
    • Shadow Map을 3D Texure 혹은 2D Texture Array에 기록한다.
    • LIDR의 Forward Rendering Pass에서 Shadow Map을 접근한다.
    • Shadow Map 행렬에 대해서 별도의 Light Table이 필요하고 Shadowed-Point-Light에는 사용이 어렵다.
      • Point Light는 빛이 사방으로 퍼지는데 3D Texture를 사용한다면 6개까지만 가능


Multi-Light Type Support

결론은 힘들다.

역시 우회하는 방법은 있지만...

  • 한가지 Light Type에만 사용하라.
    • Point Light는 LIDR로, Directional Light는 일반 Forward Rendering으로...
    • 혹은 Directional Light를 아주 아주 멀리 있는 Point Light로 처리해도 된다.
  • Light Data에 Light Type도 같이 넣어라.
    • 나중에 참조할 때 Light Type에 따라서 다른 분기의 Shader를 태우면 된다.
  • Light Type마다 별도의 Light Index Texture를 만들어라.
    • 대신에 얻게될 것은 Multi-Pass, 별도의 메모리, 복잡한 Shader...


Comparison (vs Other Rendering Technique)

vs Standard Deferred Rendering

장점

  • Deferred Rendering 처럼 많은 Render Target이 필요하지 않다.
  • Reflection vector 같은 Light 계산이 한번만 이뤄지면 된다.
  • MSAA를 지원하기 쉽다.
  • Transparency 지원이 가능하다.


단점

  • Projected Texture Light 같은 Light Type 지원이 힘들다.
  • 중첩된 Light 개수에 제한이 있다.
  • Geometry Pass가 2번 필요하다. (Depth Pre-pass + Forward Rendering)
  • Shadow 지원이 어렵다.


vs Multi-pass or Multi-light Forward Rendering

장점

  • 훨씬 저렴한 비용으로 많은 개수의 Light 지원이 가능하다.
  • 단 2번의 Geometry Pass만 있으면 된다.
  • Lighting을 위해 Geometry를 여러개로 쪼개서 그리지 않아도 된다. 
  • 모양이 있는 Light를 그릴 수 있다.

단점

  • Shader가 LIDR을 지원해야 한다.
  • Object나 Light 개수가 적을 때는 더 느리다.


참고

https://github.com/dtrebilco/lightindexed-deferredrender

https://mynameismjp.wordpress.com/2012/03/31/light-indexed-deferred-rendering/

http://bpeers.com/blog/?itemid=525


반응형

'Game Dev > Article' 카테고리의 다른 글

Inferred Lighting  (0) 2016.10.13
Light Pre-Pass Renderering (=Deferred Lighting)  (0) 2016.10.09
CppCon2014 : C++ in Huge AAA Games  (0) 2016.10.08
C4265 Warning을 켜자!  (0) 2016.09.22