[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 |