갬장장이
[3DGraphics] 11. Z-buffer
갬장장이
갬장장이의 코드 대장간
갬장장이
전체
오늘
어제
  • 분류 전체보기 (216)
    • 게임 연구소 (6)
    • 게임 제작 (15)
      • We need more guns (2024~) (1)
      • Rovenhell (2023) (2)
      • Geophyte (2020~2021) (5)
      • 아드레날린 러시 (2021) (2)
      • Treadmill (2019) (1)
      • 습작들 (2019~) (2)
      • 그 외 (~2018) (2)
    • mathematics (33)
      • game mathematics (30)
      • linear algebra (3)
    • networking (3)
    • computer graphics (46)
      • 3D Graphics (23)
      • DirectX (8)
      • OpenGL (13)
      • graphics theory (2)
    • game engines (10)
      • Unity (0)
      • Unreal Engine (10)
    • os (6)
      • Linux (0)
      • operating system (1)
      • multithreading, parallel co.. (5)
    • lang (34)
      • c++ (15)
      • .NET (5)
      • python (1)
      • java (3)
      • erlang, elixir (1)
      • js, ts (7)
    • software engineering (47)
      • PS (25)
      • algorithms (15)
      • data structures (2)
      • design patterns (4)
    • cs (4)
    • database (2)
      • SQL (1)
    • web (6)
      • web (3)
      • frameworks, libraries (3)
    • finance (0)
    • 음악 제작 (1)
    • life (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • [공지] 블로그 안내

인기 글

최근 댓글

최근 글

hELLO · Designed By 정상우.
computer graphics/3D Graphics

[3DGraphics] 11. Z-buffer

2022. 7. 15. 19:51

image


이전에 Occlusion에 대해 다룰 때(6. backface_culling 참고) 우리는 한 가지 문제를 해결하지 못한 채 남겨두었다.
바로 여러 3D모델들이 서로를 가리지 못하는 현상이었는데, 위 사진처럼 오브젝트가 겹칠 때 제대로 가려지지 않는 모습을 볼 수 있다.

image


이를 해결하기 위한 가장 단순한 방법이 Painter's algorithm인데, 단순히 각 폴리곤들마다 z값 하나를 부여해 그 z값 순으로 정렬 후 뒤에서부터 그려내는 방식이다.
그러나 이 방법으로는 오브젝트들이 서로 겹쳐있지 않은 경우에는 해결되지만, 오브젝트들이 겹치게 되는 순간 해결할 수 없게 된다.

고로 이 문제를 완전히 해결하기 위해서는 우리는 폴리곤보다 더 작은 단위, 즉 픽셀 단위로 z값을 구해 어떤 픽셀에 무얼 그려야 하는지를 결정할 수 있다.
이를 Z-buffering 이라고 한다.

image


z-buffer는 화면 전체의 픽셀들에 대해 그 픽셀에 위치하는 폴리곤의 z값을 저장하는 행렬이다.
초기값은 Infinity로 초기화되며, 어떤 폴리곤을 어떤 픽셀 위에 그릴 지를 결정할 때 사용된다.
그 픽셀 위치에 내 z값보다 더 작은 z값이 들어있다면 이미 그 자리에 나보다 더 카메라와 가까운 폴리곤이 픽셀을 차지하고 있다는 뜻이므로 그리지 않고,
반대로 내 z값이 그 픽셀 위치에 저장된 z값보다 작다면 그 z값을 overwrite한 뒤 내 폴리곤의 점을 Draw()한다.
(초기화를 Infinity로 하는 이유도 이 때문이다.)


코드와 함께 살펴보자.

//ZBuffer.h
#pragma once

#include <limits>
#include <cassert>

class ZBuffer
{
public:
    ZBuffer( int width,int height )
        :
        width( width ),
        height( height ),
        pBuffer( new float[width*height] )
    {}
    ~ZBuffer()
    {
        delete[] pBuffer;
        pBuffer = nullptr;
    }
    ZBuffer( const ZBuffer& ) = delete;
    ZBuffer& operator=( const ZBuffer& ) = delete;
    void Clear()
    {
        const int nDepths = width * height;
        for( int i = 0; i < nDepths; i++ )
        {
            pBuffer[i] = std::numeric_limits<float>::infinity();
        }
    }
    float& At( int x,int y )
    {
        assert( x >= 0 );
        assert( x < width );
        assert( y >= 0 );
        assert( y < height );
        return pBuffer[y * width + x];
    }
    const float& At( int x,int y ) const // const Ref꼴의 zbuffer도 읽어올 수 있게 해주는 함수. 기능자체는 위와 동일.
    {
        return const_cast<ZBuffer*>(this)->At( x,y );
    }
    bool TestAndSet( int x,int y,float depth )// 값을 비교하고 필요 시 overwrite
    {
        float& depthInBuffer = At( x,y );
        if( depth < depthInBuffer )
        {
            depthInBuffer = depth;
            return true;
        }
        return false;
    }
private:
    int width;
    int height;
    float* pBuffer = nullptr;
};

image


Pipeline 생성시 zbuffer를 입력으로 받는다.

//Pipeline.h
            // prestep scanline interpolant
            iLine += diLine * (float( xStart ) + 0.5f - itEdge0.pos.x);

            for( int x = xStart; x < xEnd; x++,iLine += diLine )
            {
                // recover interpolated z from interpolated 1/z
                const float z = 1.0f / iLine.pos.z;
                // do z rejection / update of z buffer
                // skip shading step if z rejected (early z)
                if( zb.TestAndSet( x,y,z ) ) // zbuffer 확인 후 Draw할지 결정
                {
                    const auto attr = iLine * z;
                    gfx.PutPixel( x,y,effect.ps( attr ) );
                }
            }
        }
    }

흥미로운 것은, zbuffer를 사용함으로써 전보다 오히려 성능 개선이 발생할 수 도 있다는 점이다.
모든 점들을 싹다 그리는 것이 아닌, 필요한 점들만 그리는 것이기 때문이다.

더 성능개선을 하기 위해서는 폴리곤(혹은 픽셀)들을 z가 작은 것부터 큰 순으로 정렬하고 앞에서부터 그리는 방식이 있다. 심지어 이때 정렬은 대충 해줘도 되는데, 어차피 zbuffer를 통해 엄격한 occlusion이 처리되기 때문이다.
앞에서부터 그리는 이유는 그래야 셰이더 연산 및 Draw()를 해줄 일이 더 줄어들기 때문이다. (이미 앞에 가로막는 애가 있으면 굳이 셰이더 연산 및 Draw()를 안해줘도 되는데, 앞에서부터 그리게 되면 가로막는 횟수가 증가한다.)


하드웨어의 경우 z-buffer는 z를 저장하는 게 아닌, 1/z를 저장한다. 이는 z-buffer에 약간의 오차를 발생시키지만, 엔비디아 등은 성능을 위해 이를 사용한다.
(관련 자료: https://developer.nvidia.com/content/depth-precision-visualized)

image


한가지 언급할 만한 점은, 만약 translucent한 픽셀을 그리려면 지금의 정보만으로는 불가능하다는 점이다. translucent할 경우 어떤 픽셀을 그릴 때 바로 뒤의 정보만 가지고서는 제대로 그려낼 수 없기 때문이다. 위 사진철머 투명한 유리가 삼중으로 겹쳐 있을 경우 빨간 유리를 그릴 때는 z-buffer에 저장되어있는 초록유리의 z값 이외에도 파란유리에 대한 정보 또한 저장되어 있어야 한다.
그렇지 않다면 파란유리가 그려지지 못할 것이다. 이는 현대의 그래픽스 라이브러리도 해결을 시도하고 있는 분야이다.

image


또 한가지 언급할 만한 부분으로, Stencil buffer가 있다. 이는 z-buffer과 함께 자주 사용되며, zbuffer처럼 각 픽셀마다 값이 저장되는 형태이다.
HUD나 거울효과 등을 구현할 때 사용되는 범용적인 버퍼이다. 관련된 내용은 나중에 다뤄보겠다.

저작자표시 비영리 변경금지 (새창열림)

'computer graphics > 3D Graphics' 카테고리의 다른 글

[3DGraphics] 13. Geometry shader  (0) 2022.07.24
[3DGraphics] 12. Vertex shader  (0) 2022.07.24
[3DGraphics] 10. Perspective correction  (0) 2022.07.15
[3DGraphics] 9. Pixel shader  (0) 2022.07.15
[3DGraphics] 8. 3D Pipeline  (0) 2022.07.15
'computer graphics/3D Graphics' 카테고리의 다른 글
  • [3DGraphics] 13. Geometry shader
  • [3DGraphics] 12. Vertex shader
  • [3DGraphics] 10. Perspective correction
  • [3DGraphics] 9. Pixel shader
갬장장이
갬장장이
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.