참고
https://opengl-notes.readthedocs.io/en/latest/topics/texturing/aliasing.html
https://learnopengl.com/Advanced-OpenGL/Depth-testing
홍정모 - 그래픽스 Part3 강의
Depth Buffer
Depth buffer
는 D3D11의 경우에는, DepthStencilBuffer를 통해서 만들어진다. 해당 버퍼를 통해서 16, 24 또는 32 bit floats로 구성할 수 있다. 대게 Depth buffer 24bit, Stencil buffer 8 bit를 할당한다.
Depth란 결국 '화면에 그려지는 친구들'의 깊이 축(z-coordinate)이다. 화면에 그려진다는 것은 View Frustum 안에 들어와 있다는 이야기이다.
$$ F_{depth} = \frac{z-near}{far-near} \tag{1}$$
해당 공식을 통해서 z축을 정규화해서 $[0, 1]$범위로 transform하는 것이다. 하지만 실제로는 해당 linear depth buffer
를 사용하지 않는다. 아래와 같은 비선형 방정식을 사용한다.
$$F_{depth} = \frac{1/z-1/near}{1/far-1/near} \tag{2}$$
The equation to transform z-values (from the viewer's perspective) is embedded within the projection matrix so when we transform vertex coordinates from view to clip, and then to screen-space the non-linear equation is applied.
depth buffer를 그리기 위해서는 Texture2D가 필요 없다. 이미, depth stencil buffer를 srv를 통해 PS에 bind해서 사용하면 된다.
desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
그렇게 받은 depth 값은 $[0,1]$의 범위를 가지고 있으므로, NDC 값으로 변환 시켜줄 필요가 있다. 그리고 해당 NDC 값을 다시 $F_{depth}$의 값으로 만들어줄 필요(invProj)가 있다.
https://www.songho.ca/opengl/gl_projectionmatrix.html
float z = depth * 2.0 - 1.0; // back to NDC()
depth = (2.0 * near * far) / (far + near - z * (far - near));
return depth / far;
Fog Effect
Depth buffer를 활용해 만드는 효과이다. Depth buffer의 특징을 보면, 거리에 따라 값이 할당되는 것을 알 수 있고 이를 이용해 fog 효과를 구현할 수 있다.
해당 효과를 구현하기 위해서는 위에서 depth 값을 구했던 식을 변형해야 한다.
왜 변형해야 하는가?
먼저, (2) 수식은 말 그대로 depth buffer의 값을 정규화 시킨 것이다. 그렇기 때문에 아무리 멀리 있는 값이라도 depth 값이 기록되어 있기 때문에 화면에 잘 보인다.
fog 효과는 연기에 의해 멀리 있는 물체가 안 보이게 만들어야 한다. depth 값 계산하는 방식을 바꾸어 주어야한다.
참고한 자료에서는 texcoord를 이용한다. texcoord의 좌표는 Top-Left$[0,0]$를 기점으로 시작한다. 반면에 NDC 좌표는 Bottom-Left$[-1,-1]$부터 시작한다. 서로의 기준점이 다르므로, 우리는 일치시킨 후에 계산을 시작해야 한다.
float4 posProj;
posProj.xy = texcoord * 2.0 - 1.0;
posProj.y *= -1;
posProj.z = depthOnlyTex.Sample(linearClampSampler, texcoord).r;
posProj.w = 1.0;
// ProjectSpace -> ViewSpace
float4 posView = mul(posProj, invProj);
posView.xyz /= posView.w;
return posView.z;
먼저, NDC 좌표 범위인 $[-1, 1]$의 범위로 transform 해주고 y축에다가 $-1$을 곱해준다. 이는 서로 기준점으로 y축을 기준으로 반대이기 때문에 곱해주는 것이다. z축은 depth buffer에서 가져온 값을 설정해준다.
clip space -> NDC 값으로 변경해주기 위해서는 homogeneous coordinate에서 w값으로 나누어주어야 한다.
https://carmencincotti.com/2022-05-02/homogeneous-coordinates-clip-space-ndc/
$$\begin{pmatrix}x_{ndc} \ y_{ndc} \ z_{ndc}\end{pmatrix} = \begin{pmatrix}x_{clip}\over w_{clip} \ y_{clip}\over w_{clip} \ z_{clip}\over w_{clip} \end{pmatrix} \tag{3}$$
float3 fogColor = float3(1, 1, 1);
float fogMin = 0.0;
float fogMax = 8.0;
float dist = depth * fogStrength;
float fogFactor = (fogMax - dist) / (fogMax - fogMin);
fogFactor = clamp(fogFactor, 0.0, 1.0);
output = lerp(fogColor, output, fogFactor);
$fogMax - dist$인 이유는 멀리 있는 곳부터 안개가 발생해야 하기 때문이다. 해당 식으로 계산하게 되면 멀리 있을 수록 값이 1에 가까워진다.
여기서 lerp
의 작동원리를 알아야 한다.
$$P(t) = (1-t)P_0+tP_1 \tag{4}$$
lerp는 선형보간 함수로 라는 공식을 가지고 있다. 즉, $P_0$은 fogColor, $P_1$은 output이 되는 것이다. 여기서 비율을 결정하는 값은 fogFactor가 된다. 이렇게 계산하게 되면, fogFactor의 값에 따라서 어떠한 비율로 안개 값과 원래 색깔 값을 섞어야 하는지 자연스럽게 도출된다.
여기서 안개가 일직선이 아닌 원의 형태로 다가오는 걸 구현하고 싶다면, dist값을 조절하면 된다.
먼저, 해당 PS는 3D가 아닌 2D적으로 접근해야한다. 그렇다면, 우리가 받는 해당 input.texcoord들은 모두 화면이랑 똑같다고 보면된다.
그러면 해당 값을 NDC좌표로 바꾸어서 길이로 변환시켜 dist에 곱해준다면? $[-1, 1]$의 범위를 가지므로 가운데를 기준으로 원의 형태 처럼 안개가 생기게 된다.
float2 eye = input.texcoord.xy * 2.0 - 1.0;
eye.y *= -1;
float eyeLength = length(eye);
float dist = depth * fogStrength * eyeLength;
'Graphics' 카테고리의 다른 글
4. Point Shadow Mapping (1) | 2024.06.24 |
---|---|
3. Directional Shadow Mapping (0) | 2024.06.21 |
[D3D11] 사각형 및 육면체 그리기 (0) | 2024.05.20 |
1. Blinn Phong 모델 (0) | 2024.05.14 |
Texture 사용하기 (0) | 2024.05.14 |