본문 바로가기

OpenGL Study

OpenGL "Depth Testing"

[DEPTH TESTING]

1. Before (Depth-test)

- depth-buffer

: color buffer와 마찬가지로 버퍼의 한 종류로, fragment의 정보를 저장하고 일반적으로 color buffer와 동일한 크기를 가지고 있다

: window system에 의해서 자동적으로 생성되고 depth value들을 float형의 16, 24, 32 bit로 저장한다 (대부분 24bit를 사용한다)

- depth testing을 사용할 수 있게 설정하면 OpenGL은 depth buffer의 내용에 따라 fragment의 깊이 값을 테스트한다

: 테스트가 통과되면 해당 depth buffer는 새로운 depth value로 수정된다

: 테스트가 실패하면 해당 fragment는 폐기된다

- depth tesing은 fragment shader가 수행된 후 screen space에서 수행된다

: screen space coordinate는 OpenGL의 glViewport함수에서 정의한 viewport와 직접적으로 관련이 있고. GLSL에서 gl_FragCoord 변수를 통해 접근할 수 있다

- gl_FragCoord

: gl_FragCoord 변수의 x, y 요소는 fragment의 screen space coordinate((0,0)이 화면의 좌측 하단)를 나타낸다

: gl_FragCoord 변수는 fragment의 실제 깊이 값을 가지고 있는 z요소도 포함하고 있다

- early depth testing

- 최근 대부분의 CPU는 fragment shader를 실행하기 전에 depth test를 수행할 수 있게 하는  early depth testing 기능을 지원한다

- 이 기능을 통해 fragment가 다른 object의 뒤에 위치할 때마다 해당 fragment를 미리 제거할 수 있다

: fragment shader는 일반적으로 많은 비용을 차지하기 때문에 실행을 최소화 하는 것이 좋다

- early depth testing을 위해서는 fragment shader에 깊이 값을 작성하면 안된다

: 깊이 값이 있다면 early depth testing의 실행이 불가능하다

glEnable(GL_DEPTH_TEST);

- 기본적으로 depth testing은 비활성화 되어있기 때문에 GL_DEPTH_TEST 옵션을 사용하여 활성화 시켜야한다

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

- depth testing이 활성화 되면 OpenGL은 자동적으로 depth test에 통과하면 fragment의 z 값을 depth buffer에 저장하고, 실패하면 fragment를 폐기한다

: 따라서 depth testing을 활성화했다면 각 렌더링 루프가 돌 때마다 GL_DEPTH_BUFFER_BIT를 사용하여 depth buffer를 비워줘야한다

: depth buffer를 비우지 않으면 마지막 렌더링 루프에서 작성된 깊이 값이 쌓이게 된다

glDepthMask(GL_FALSE);

- 모든 fragment에 대해 depth test를 수행하고 그에 따라 fragment를 제거하지만 depth buffer를 수정하는 것을 원하지 않을 때에는 read-only depth test를 사용한다

- depth mask를 GL_FALSE로 설정함으로써 depth buffer에 작성하는 것을 비활성화할 수 있다[

- depth testing을 활성화했을 때에만 사용할 수 있다

2. Depth Test Function

glDepthFunc(GL_LESS);

- OpenGL은 depth test에서 사용하는 비교 연산을 조정할 수 있는 glDepthFunc 함수를 제공한다

: 이것을 사용해 fragment를 어떨 때에 통과/제거 시킬지, depth bufffer를 언제 수정할지를 조정할 수 있다

- 위 표에 있는 option들을 사용해 비교 연산자를 설정할 수 있다

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);

- 교재에서 제공하는 코드 중 glDepthFunc의 option을 수정하여 depth testing을 비활성 했을 때의 결과를 테스트한다

: 마지막에 그려진 바닥이 이전에 그려진 컨테이너의 fragment들 위에 렌더링된다

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

- glDepthFunc의 option을 다시 GL_LESS로 설정하면 depth testing이 활성화된 결과를 확인할 수 있다

3. Depth Value Precision

- depth buffer는 0.0 - 1,0 사이의 깊이 값을 가지고 있고 사용자의 관점에서 scene의 모든 object들의 z값과 비교된다

: view space의 z값은 projection 절두체의 near과 far 사이의 값이 된다

- view-space의 z값을 [0, 1] 범위로 변환시키기 위해 위 공식을 사용하여 z값을 0.0 - 1.0 사이의 값으로 변환한다

: 공식의 near, far 값은 절두체를 생성하기 위해 projection matrix에 전달해왔던 near, far 값이다

- z값과 해당 깊이값의 관계를 나타낸 그래프로, 모든 방정식에서 object가 가까이 있을 때 깊이 값이 0.0과 가까워지고 object가 far 평면에 가까이 있을 때 1.0과 가까워지는 것을 알 수 있다

- 이와 같은 linear depth buffer는 일반적으로 사용되지 않는다

-  올바른 투영 특성을 위해 비선형의 depth 방정식이 주로 사용되는데, 위 공식이 바로 near과 far 거리를 염두하는 비선형 depth 방정식이다

: 이 depth buffer 내부의 값들은 projection matrix가 적용되기 전의 view-space에서는 선형적이나, screen-space에서는 비선형적이다

- 비선형 depth 방정식은 1/z와 비례한다

: z 값이 작을 때 큰 정밀도를 가지고 z 값이 멀리 있을 때 정밀도가 떨어진다

- 이 비선형 함수는 1.0 - 2.0 사이의 z값을 0.5 - 1.0 사이의 깊이값으로 변환한다

: 이를 통해 작은 z값에 대해 큰 정밀도를 가지게 하는 것이다

- z 값과 이 depth buffer의 값의 관계를 나타낸 그래프이다

: 깊이 값들은 작은 z 값에서 큰 정밀도를 가진다

- z 값을 변환시키는 이 방정식은 projection matrix에 포함되어 있으므로 vertex coordinate를 view에서 clip으로 변환하여 screen-space로 이동할 때 적동된다

4. Visualizing The Depth Buffer

void main()
{             
    FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}

- fragment shader의 gl_FragCoord vector와 z 값은 특정 fragment의 깊이 값을 가지고 있다

- 이 깊이 값을 컬러로 출력한다면 모든 fragment의 깊이 값들을 scene에 출력할 수 있다

: fragment의 깊이 값을 기반으로 color vector를 return하여 이를 수행할 수 있다

- 위 코드를 사용하여 프로그램을 실행하면 좌측처럼 모든 깊이 값이 1.0인 것처럼 모든 것이 하얀색으로 출력된 것을 확인할 수 있다

: 이는 깊이 값이 거리에 따라 급격히 증가하여서 대부분의 모든 vertex들이 1.0에 가까운 값을 가지기 때문이다

- object에 점점 가까이 다가가면 우측처럼 어두워지는 색상을 확인할 수 있다

: z 값이 점점 작아지는 것으로, 이 두 이미지는 깊이값의 비선형성을 잘 나타내고 있다

- fragment의 비선형 깊이 값을 다시 선형으로 변환하기 위해 깊이 값을 얻기 위해 사용했던 projection 과정을 반대로 해야한다

: 아래에서 그 과정을 진행한다

float ndc = depth * 2.0 - 1.0;

-  우선 [0, 1] 범위의 깊이 값들을 [-1, 1] 범위의 NDC 좌표로 변환한다

float linearDepth = (2.0 * near * far) / (far + near - ndc * (far - near));

- 변환한 깊이 값에 역변환을 적용하여 선형 깊이 값을 얻는다

- 위 방정식은 비선형 깊이 값을 구하기 위해 사용한 projection matrix로부터 얻을 수 있다

#version 330 core
out vec4 FragColor;

float near = 0.1; 
float far  = 100.0; 
  
float LinearizeDepth(float depth) 
{
    float z = depth * 2.0 - 1.0; // back to NDC 
    return (2.0 * near * far) / (far + near - z * (far - near));	
}

void main()
{             
    float depth = LinearizeDepth(gl_FragCoord.z) / far; // divide by far for demonstration
    FragColor = vec4(vec3(depth), 1.0);
}

- screen-space에서의 비선형 깊이 값을 선형 깊이 값으로 변환하는 최종 fragment shader code

- 변환된 선형 깊이 값들은 near와 far 사이의 값이므로 대부분의 값이 1.0보다 높아 완전한 하얀색으로 출력될 것이다

: 이는 main에서 선형 깊이 값을 far로 나눔으로써 선형 깊이 값을 대략적으로 [0, 1] 범위로 변환할 수 있다

- 프로그램을 실행하면 깊이 값들이 0.1에 위치한 near 평면과 100에 위치한 far 평면 사이에 선형적으로 존재하고, far 평면은 멀리 있기 때문에 전체적으로 검은색으로 출력된 것을 확인할 수 있다

-결과적으로 비교적 near 평면과 가깝고, 낮은 깊이 값들을 얻게 된다

5. Z-fighting

- Z-fighting이란 두 개의 평면/삼각형이 아주 가깝게 나란히 위치해 있을 때 depth buffer가 두 개의 도형 중 어떤 것이 앞에 있는지 계산할 만큼의 정밀도를 가지지못하여 결과적으로 두 도형이 계속해서 순서가 바뀌는 것처럼 보이는 현상을 말한다

: 지금까지 사용한 scene에서도 컨테이너의 밑면과 바닥 평면이 동일 평면상에 존재하므로 이 곳에서 Z-fighting 현상을 확인할 수 있다

- Z-fighting은 depth buffer와 관련된 흔한 문제로, 멀리 있는 object에서 더 많이 발생한다

: depth buffer는 z 값이 클수록 더 작은 정밀도를 가지기 때문이다

5-1. Prevent Z-fighting

- 첫 번째 방법 : 삼각형들이 겹쳐지지 않을 정도로 object를 가깝게 두지 않는 것

: 두 개의 object 사이에 아주 작은 offset을 생성함으로써 두 개의 object에 대한 Z-fighting 현상을 없앨 수 있다

: 지금 사용 중인 프로그램에서는 컨테이너를 y축의 양의 방향으로 약간 움직이는 것을 통해 해결할 수 있다

: 이 방법은 testing 전반에 걸쳐 Z-fighting을 만들 수 있는 object를 없애기 위해 각 object에 대해 수작업으로 조정해야한다

- 두 번째 방법 : near 평면을 가능한 멀리 설정하는 것

: 정밀도는 near 평면에 가까울 때 극도로 커지기 때문에  near 평면을 시점으로부터 멀리 이동시킨다면 전체 절두체 범위에 걸쳐 정밀도를 높일 수 있다

: 이 방법은 가까이 있는 object를 자를 수 있으므로 여러 번의 테스트를 거쳐 최적의 near 거리를 찾아야한다

- 세 번째 방법 : 높은 정밀도의 depth buffer를 사용하는 것

: 대부분의 depth buffer는 24bit의 정밀도를 가지고 있지만, 최근 대부분의 그래픽 카드들은 32bit의 depth buffer를 지원한다

: 32bit의 depth buffer는 정밀도를 크게 증가시키므로 depth testing의 정밀도를 높일 수 있다

'OpenGL Study' 카테고리의 다른 글

OpenGL "Blending"  (1) 2023.02.01
OpenGL "Stencil testing"  (0) 2023.02.01
OpenGL "Model"  (0) 2023.01.25
OpenGL "Mesh"  (0) 2023.01.24
OpenGL "Assimp"  (0) 2023.01.24