참고
https://www.yes24.com/Product/Goods/24301920
https://learnopengl.com/Advanced-Lighting/SSAO
SSAO
이름에서 알 수 있듯이, 포스트 프로세싱 과정 중 하나이다. 이전에 Ambient Lighting
에 대해서 공부하였고 해당 라이팅은 고정된 빛에서 빛이 산란됨에 따라 장면이 그려지는 것을 표현한 것이다.
간접적인 라이팅의 한 종류를 Ambient occlusion
이라고 부른다. 이러한 종류의 간접 라이팅은 서로가 밀접하게 붙어있기 때문에 더 어둡게 보인다는 특징이 있다.
Crytek 발표자표
2007년에 Crytek에서 발표한 기술로 depth buffer를 screen-space에서 이용해 occlusion
의 크기를 결정한다.
이론은 매우 간단하다. 화면을 채우는 각각에 물체에 대하여 물체를 둘러싼 깊이값을 기반으로 한 occlusion factor
를 계산하는 것이 목표이다. 이 occlusion factor는 물체의 라이팅 양을 줄이는데에 사용된다. 이 factor는 여러개의 depth samples들을 통해서 얻어지는데, depth samples는 물체를 둘러싼 구 형태의 커널을 통해서 현재 물체의 depth value와 비교를 통해서 계산한다.
각각 회색으로 표시된 depth sample은 총 occlusion factor
값에 영향을 준다. 해당 sample이 많아질수록 ambient lighting은 약해진다.
샘플의 양을 줄이면 정확도가 너무 떨어지고 샘플의 양을 높게 잡으면 속도가 희생된다. 해결방법으로, 무작위로 회전해서 배치는 샘플 커널 기법을 이용해 낮은 양의 샘플로 높은 정확도를 뽑아낼 수 있다. 이러한 무작위한 샘플들은 블러(blur)를 통해 줄여야하는 noise pattern을 생산하게 된다.
Crytek이 사용한 샘플 커널은 구의 형태였는데, 해당 형태는 평평한 벽을 회색처럼 보이게 하는 점을 초래하였다.
LearnOpenGL
crytek에서 발생한 문제점(?)을 이유로, learnopengl에서는 구 형 샘플 커널은 사용하지 않는다.
대신에, 물체 표면의 normal vector를 이용한 반 구형 형태의 샘플 커널을 이용한다. 이 형태를 이용함으로써, 우리는 물체의 뒷면에 위치한 샘플들이 occlusion factor에 영향을 주는 것을 고려하지 않는다.
필요한 data는 아래와 같다.
- position vector
- normal vector
- albedo vector
- sample kernel
- random rotation vector used to rotate the sample kernel
view-space position을 이용해서 우리는 반구형태의 커널샘플을 위치시킨다. 각각의 커널샘플들은 position의 depth buffer와 비교해서 occlusion의 크기를 결정한다.
HLSL 프로그래밍 저서에서는 깊이 정보와 노말 정보를 미리 다운스케일 한 후 해당 값을 사용해서 AO 텍스쳐를 만든다.
절차
- SSAO Noise Texture 생성 : Texture2D 버퍼의 크기를 각요소의 크기를 Vector3로 설정하고
vector<Vector3>
를 만들어서 ssaoNoise Texture를 만든다. - SSAO 텍스쳐 만들기 : SSAO Noise텍스쳐와 kernel sample들을 통해서 SSAO 텍스쳐를 만든다.
- deferred lighting 하기.
해당 이미지에서 볼 수 있듯이 샘플커널을 이용해 반구형태에 범위에서 샘플커널들이 물체 아래쪽에 위치하게 되면 occlusion에 값을 더해준다.
SSAO 텍스쳐를 만들기 위해서 위의 과정을 진행해야 한다.
TBN 행렬을 만들어야 하는데, Tangent vector에 대해서는 정점의 tangent 벡터 값을 알 필요 없다. 왜냐하면 정확하게 물체표면에 정렬된 TBN 행렬을 얻을 필요가 없기 때문이다.
TBN 행렬을 만드는 이유는 물체의 표면(노말)을 기준으로 된 행렬이기 때문이다. 이 행렬을 통해 tangent->view space로의 변환을 진행하였다. (T를 구할 때 우린 NoiseTex
에서 추출한 randomVec를 이용하였다. 이는 SSAO의 효율성을 위해 randomness를 추가한 것이다.)
https://www.youtube.com/watch?v=lbL_GiWLQpM&ab_channel=GetIntoGameDev
viewFragPos에다가 해당 샘플 값들을 추가해주어야 한다. 그래야 그림[1]에서 봤었던 샘플들의 위치가 그려질 수 있는 것이다.
float3 _sample = mul(samples[idx].xyz, TBN); // sample의 direction이라고 보면 됨.
_sample = position + (_sample * radius); // 위치에서 여러 방향 값을 더해줌(확산)
우리는 이것을 view-space에서 그리는 것이 아니라 screen-space에서 바로 그린다. 그러므로, 코드에서 구한 _sample
을 screen-space로 변경해줄 필요가 있다. 변경해주는 이유는 샘플 커널의 위치에 효과를 주기위해서 screen-space texcoord로 바꾸어 해당 좌표의 깊이값을 알아내야 하기 때문이다.
float4 offset = float4(_sample, 1.0f);
offset = mul(offset, proj);
offset.xyz /= offset.w;
offset.y *= -1.0f;
offset.xy = offset.xy * 0.5f + 0.5f;
그렇게 texcoord의 좌표를 구했으면, 해당 좌표(샘플 커널 위치의 screen-space texcoord)를 이용해서 좌표의 물체 view-space에서의 깊이 값을 알아낸다.
해당 깊이 값과 샘플커널 위치의 깊이 값을 비교해 샘플 커널이 더 멀리(값이 크다면) occluded를 1.0으로 할당한다. 그렇게 되면 occlusion의 값이 더욱 커진다는 것을 의미한다.
추가적으로, AO는 물체 경계선에 밀접하게 정렬되는 모습을 볼 수 있는데 이부분을 해결하기 위해 일종의 range-check
를 도입해서 훨씬 더 자연스러운 모습을 연출하였다.
float sampleDepth = DepthTex.Sample(linearWrapSampler, offset.xy);
sampleDepth = GetViewSpacePosition(offset.xy, sampleDepth).z;
float occluded = (_sample.z + bias <= sampleDepth ? 0.0f : 1.0f);
//float occluded = step(sampleDepth, _sample.z + bias);
float intensity = smoothstep(0.0f, 1.0f, radius / abs(position.z - sampleDepth));
문제점
매우 가까이 가면 SSAO가 고장난다. 아마 _sample.z + bias <= samepleDepth
부분에서 발생하는 문제점 같다. sampleDepth
는 depth buffer의 값을 그대로 전달 받고 _sample
은 pos + _sample * radius
라는 식으로 도출된다.
sampleDepth는 [0, 1]로 정규화된 깊이 버퍼의 값을 갖고 온다. 반면에 sample은 view-space의 pos을 기반으로 하기 때문에 훨씬 더 큰 범위를 갖고 있다고 볼 수 있다. 이 부분은 내가 계산을 실수한 부분으로 [0, 1]로 정규화된 depth를 view-space에서의 depth값으로 변환시켜주면 문제가 해결된다.
다만 아쉬운점은 SSAO 결과 텍스쳐가 너무 어둡게 보인다는 점이다. 이 점으로 인해 렌더링 결과를 디버깅했을 때 육안으로 파악하기 힘들다는 아주 약간의 문제점이 있다.(결과적으로 작동은 잘됨)
결과물
오른쪽의 외곽부분이 살짝 더 어둡다는 것을 확인할 수 있다.
'Graphics' 카테고리의 다른 글
bug : ResizeBuffers() 가 정상작동하지 않는 문제. (0) | 2024.07.17 |
---|---|
bug : Shader stage did not run (0) | 2024.07.17 |
6. Deferred Lighting (0) | 2024.07.01 |
5. Normal Mapping (0) | 2024.06.27 |
[D3D11] 물체를 그리는 과정 (1) | 2024.06.24 |