
나만의 tiny renderer 만들기(4) - zbuffer, 애파인변환
여기서 설명하는 zbuffer의 원리를 이용한게
DepthBuffer(Z-Buffer)임
Depth test라는건
프래그먼트의 z를 z-buffer에 기록하기 위해 수행하는 연산을 의미함
이론적으로는
depth test는 fragment shader가 수행된 후,
screen space에서 stencil test이후 실행됨
위 링크에서 볼 수 있다싶이
depth test를 거치는 과정은 다음과 같음
이때 fragment들에 대한 depth test는 픽셀단위로 됨
그리고 위의 링크에서는 uint8_t를 사용했지만
실제에서는 16, 24(정배), 32비트 부동소수점를 사용함
이론상의 depth test는 fragment shader이후에 수행되어
출력되지 않을 fragment를 결정짓고 출려한다고 했음
근데 이렇게 되면
fragment shader할 필요가 없는 부분에 대하여 쉐이딩을 하게 되니
성능상의 이슈가 있을수밖에 없음
그래서 등장한 개념이 early depth test임

다시 이 사진을 보자
fragment shader에서는 주로 fragment의 색상을 결정지음
그리고 정점의 변환은 주로 vertex shader, geometry shader에서 일어남
그럼 정점 변환이 일어난 후에 depth test를 해버려서 무거운 색상 계산 shader인 fragment shader에서의 부담을 줄일 수 있지 않을까?
맞음!
그래서 등장한게 eraly depth test임
래스터라이저에서는 shape assembly에서 정점끼리 연결된 면이 넘어오면
그 면에 대한 각 픽셀의 fragment를 만들게 됨
그리고 early depth test를 수행시켜서
fragment shader이전에 버려야할 fragment를 버리는거지 ㅇㅇ
다만 early Z를 지양해할 경우도 있음
- fragment shader에서 직접적으로 fragment의 z를 수정할때
- RGBA로 alpha값이 보간되어야하는 fragment에 대해서는 동작하지 않을 수 있음
depth buffer를 사용하는건 간단함
//rendering 전 준비단계
glEnable(GL_DEPTH_TEST);
//rendering단계
while(...)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//optional
//glDepthMask(GL_FALSE);
//glDepthMask(GL_TRUE);
}
위에서 glDepthMask라는 옵션을 볼 수 있음
glDepthMask는 depth test를 수행하지만,
프래그먼트의 z 값은 depth test이후의 z buffer를 갱신할때 사용하지 않을때 사용하는거임
예를들어 유리창이 있을수 있음
| 상태 | 설명 | 결과 |
|---|---|---|
GL_TRUE (기본값) | 깊이 버퍼 쓰기 허용 | 테스트를 통과한 프래그먼트의 깊이 값이 깊이 버퍼에 저장 |
GL_FALSE | 깊이 버퍼 쓰기 차단 | 테스트를 통과해도 깊이 버퍼는 업데이트되지 않움 (읽기 전용 모드) |
따라서
GL_TRUE를 하게 되면
zbuffer에도 갱신이 일어나서 완전 불투명한 물체를 렌더링 할때 사용하게 되고
GL_FALSE를 하게되면
zbuffer에는 갱신이 되지 않지만 물체끼리의 z index비교는 수행하여 반투명한 물체의 back-to-front렌더링을 수행하게 됨
glDepthTest의 테스팅 방식을 결정짓는 메서드임
glDepthFunc(GL_LESS)가 기본값
| 함수 | 설명 |
|---|---|
| GL_ALWAYS | 깊이 테스트가 항상 통과 |
| GL_NEVER | 깊이 테스트가 절대 통과되지 않음 |
| GL_LESS | 프래그먼트의 깊이 값이 저장된 깊이 값보다 작으면 통과 |
| GL_EQUAL | 프래그먼트의 깊이 값이 저장된 깊이 값과 같으면 통과 |
| GL_LEQUAL | 프래그먼트의 깊이 값이 저장된 깊이 값보다 작거나 같으면 통과 |
| GL_GREATER | 프래그먼트의 깊이 값이 저장된 깊이 값보다 크면 통과 |
| GL_NOTEQUAL | 프래그먼트의 깊이 값이 저장된 깊이 값과 다르면 통과 |
| GL_GEQUAL | 프래그먼트의 깊이 값이 저장된 깊이 값보다 크거나 같으면 통과 |
제일 위에서 z buffer의 z는 16, 24, 32비트 부동소수점 방식을 이용해 처리한다고 했음
그럼 z index는 어떻게 계산하는걸까?
기본적인 개념은

그래픽스에서 이런 공식 많이 보이는데
정확히 뭐라부르는지 모르겠음...
대충 2개의 near, far절두체를 두고
near와 가까울때는 z = near
far와 가까울때는 z = far로
선형 보간이 이뤄지는 방식임
하지만 이것만으로는 부족함
실제로 여러 물체가 거~의 비슷한 z index를 가지고 있어서
특정 범위내의 물체까지는 z index를 더 정밀한 값으로 계산을 해야한다고 하면?
즉,
가까이 있는 물체는 더 높은 정밀도로 처리가 되고
멀리 있는 물체는 비교적 중요도가 낮으므로 낮은 정밀도로 처리되도록 하는거임
그 공식이 바로...
임

이렇게 z index가 낮을수록 더 높은 정밀도로 depth를 판별하게되고
z index가 높을수록 낮은 정밀도로 depth를 판별하게 됨
void main()
{
FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}
fragment shader의 로직을 위처럼 바꿔보자
gl_FragCoord가 바로 z index를 나타내어주는 좌표계임

이렇게 멀때는 흰색이다가
가까워질수록 색이 검은색으로 급격하게 변하게 됨
이게 바로 gl의 depth test를 할때 z값 계산이
비선형적이라는 증거임
선형적으로 나타낼땐 어떻게 보이냐?
대충
float near = 0.1;
float far = 10.0;
float z = gl_FragCoord.z * 2.0 - 1.0;
float depth = (2.0 * near * far / (far + near - z * (far - near))) / far;
FragColor = vec4(vec3(depth), 1.0);
이렇게 0~1범위가 되어있던 z값을 ndc좌표계인 -1~1범위로 바꿔주면...

요로코롬 선형적으로 바뀜~~~

이런걸 본적있을거임
이게 바로 물체간의 z값의 차이가 정말 계속 z buffer가 변경되며 생기는 문제인
z fighting임
이를 해결하는 방법이 몇가지 있음