| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- ue4
- wave
- scalar
- rendering
- shader
- SGPR
- hzb
- atmospheric
- optimization
- 번역
- Study
- DirectX12
- DX12
- unrealengine
- forward
- deferred
- Shadow
- Nanite
- texture
- SIMD
- scattering
- vulkan
- ShadowMap
- GPU
- UE5
- RayTracing
- Wavefront
- Graphics
- VGPR
- GPU Driven Rendering
- Today
- Total
RenderLog
Atmospheric Shadowing 본문
Atmospheric Shadowing
최초 작성 : 2021-04-14
마지막 수정 : 2021-04-14
최재호
목차
1. 목표
2. 내용
2.1 책(FGED2)에 소개된 알고리즘 분석
2.1.1. Ray marching 할 두 지점 구하기
2.1.2. Ray marching 시 이동하는 거리(Step)을 점점 더 커지도록 설정
2.1.3. 랜덤샘플로 성능 향상
2.1.4. Anistropy scattering
3. 실제 구현
4. 구현 결과
5. 실제 구현 코드
6. 레퍼런스
1. 목표
Atmospheric Shadowing 을 이해하고 실제로 구현해봅니다.
이글은 VolumeLight 글과 같은 내용을 다루되 레퍼런스1의 Atmospheric Shadowing 챕터를 참고하여 구현하였습니다.
2. 내용
알고리즘의 핵심은 간단합니다. 두 지점 (카메라에서 대상 지점까지) 사이를 조금씩 이동하며, 현재 위치에서 Light가 들어오는지 여부를 판단합니다. 만약 Light가 들어오면 Inscattering 되는 구간이라 판단하고 지정한 Inscattering에 수치를 더해줍니다. Light가 현재 위치에 들어오는지 여부는 Depth & Shadow Buffer를 사용합니다. (InScattering 에 대해서는
Absorption and Scattering 를 참고해주세요)
이렇게 지점 A에서 B까지 이동하며 특정 연산을 반복적인 수행하는 것을 Ray marching 이라 부릅니다.. (레퍼런스2 참고)
그리고 이 모든 과정은 PostProcess 과정에서 처리 됩니다.
2.1 책(FGED2)에 소개된 알고리즘 분석
2.1.1. Ray marching 할 두 지점 구하기
2.1.1.1. NDC 공간에서 카메라 공간으로 공간변환
- NDC 공간의 좌표 v, 초점거리 g 그리고 종횡비 s가 있으면, 카메라 좌표 q를 구할 수 있습니다. 아래의 식1을 봐주세요.

- 카메라 공간의 좌표는 카메라가 원점에 위치하기 때문에 카메라 원점에서 대상 표면으로의 벡터로 볼 수 있습니다.
2.1.1.2. 쉐도우 텍스쳐로 공간변환
- 카메라 공간의 좌표에 ShadowMap 용 Projection matrix를 곱해 주어 카메라 공간의 벡터를 ShadowMap에서의 벡터로 변환시켜 줍니다. (ShadowMatrix * (InvViewMat * PositionInCameraSpace))
2.1.1.3. 카메라 원점도 쉐도우 텍스쳐에서 위치를 구함
- 카메라의 원점에서 부터 벡터방향으로 이동해야 하므로 Camera의 위치 또한 ShadowMap에서의 위치로 변환시켜줍니다.
2.1.1.4. 위의 과정에서 나온 카메라 위치와 벡터를 사용하여 ShadowMap에서 Ray marching을 진행
2.1.1.5. Ray marching 할 시작점과 끝점을 p1, p2로 지정
- 카메라 위치와 초점거리에 있는 평면사이의 거리로 나눠주어 거리를 |q|와 dmin와 dmax 를 사용하여 p1, p2를 구합니다.

- 이렇게 식을 구해두면 방향에 상관없이 dmin, dmax 거리를 가진 Radial distance 인 p1와 p2를 얻을 수 있습니다.
- p1과 p2는 t를 0-1 까지로 변화시키며 보간할 수 있습니다.
- ShadowMap에서는 좌표는 xy축만 있으며, 이 x, y에 대응하는 z값 또한 구해야 합니다. 이 역시 동일하게 방식으로 보간하여 구할 수 있스빈다. 식3 참고.
2.1.2. Ray marching 시 이동하는 거리(Step)을 점점 더 커지도록 설정
- 현재 위치에서 더 멀리 있을 수록 쉐도우맵에서의 비지빌리티 밀도가 떨어집니다. 오버샘플링을 줄이기 위해서 카메라에서 멀어질 수록 Step을 증가시킵니다.
- 이 식은 복잡하지만 정확하게 밀도에 비례하는 Step을 조정하는 식은 아닙니다. 그래서 큰차이가 없는 경우는 고정 Step 이동을 하는 것도 괜찮다고 생각합니다.
- u(t) 함수를 정의하여서 이런 작업을 수행합니다. 아래의 총 Brightness 식 유도를 참고해주세요.



2.1.3. 랜덤샘플로 성능 향상
- t = i / n을 t = (i + delta) / n 으로 설정 delta는 노이즈 64x64 텍스쳐에서 가져옵니다.
- 이웃 픽셀을 갖고 블러를 해도 되지만 그렇게되면 배경의 light shaft가 전경의 솔리드 오브젝트로 번지는 라이트 블리딩 현상이 발생가능합니다.
2.1.4. Anistropy scattering
2.1.4.1. 디렉셔널 라이트의 성질
- 대기에서 디렉셔널 라이트는 등방성 산란을 하지 않습니다, 대부분은 라이트의 방향 그대로거나 아주 적은 각도만 변하고 뒤쪽이나 큰 각도로 변하는 경우는 적습니다. 그래서 태양을 바라보는 경우는 라이트가 강하지만 태양을 바라보지 않으면 라이트는 아주 적습니다.
2.1.4.2. 비등방성 성질을 추가하기 위해 식

- α : 카메라에서 표면으로 향하는 방향과 라이트 방향 사이의 각
- 90보다 적으면 라이트가 앞쪽으로 산란하는 포워드 스케터링 90보다 크면 뒤쪽으로 산란하는 백워드 스케터링입니다. 아래 식을 참고해주세요.

- g : g가 0 이면 모든 방향으로 균일하게 산란합니다. 그리고 g > 0 이면 입사하는 라이트 방향에 가까운 방향으로 산란하며 g값이 더 클수록 라이트의 방향으로 더 많이 산란합니다.
- g 항이 계속해서 커지는 경우, 알파 값이 0 일때 가장 큰 값을 생성합니다. 그래서 최대 Intensity는 아래와 같습니다.

- 이러한 값의 변화는 level의 anisotropy 값을 변경했을 때 Brightness 또한 변경되므로 원치 않는 결과를 일으킬 수 있음. 그래서 최대값인 Φg(0)을 사용하여 'Φg(알파) 값을 정규화 하여 사용합니다.


- g값을 그냥 제공 하는 것 보다 아티스트가 선택한 최소 Intensity R을 통해 g를 만들어내는 것이 더 유용할 수 있습니다.
- 알파가 180도 인 경우 R이 최소값이 되기 때문에 아래식을 통해 g를 유도합니다.
- 최대값의 경우 정규화 된 값이 항상 1이므로 사용하기 어렵기 때문에 최소값을 사용함. 최소값은 g가 바뀜에 따라서 값이 달라지므로 Backward scattering과 Forward scattrering의 비율로 볼 수 있습니다.


3. 실제 구현
실제 구현은 책에 있는 내용과는 다르게 구현하였습니다.
이유는 아래와 같습니다.
책의 구현을 따라갔을 때, ShadowRay(쉐도우 텍스쳐 공간에서의 반직선)에 Camera 공간의 DepthRatio를 곱해주는 부분이 있었고, 이 부분이 dmin = 1.0, dmax=1000 인경우는 p2 값이 너무 큰 값으로 유도되어 (500이상) 쉐도우 텍스쳐 공간에서 Ray marching이 불가능하였기 때문입니다.

float GetAccumulatedInscatteringValue(float InTravelDist, vec3 InToPixelNormalized)
{
float Depth = texture(DepthSampler, TexCoord_).x;
float dt = 1.0 / TravelCount;
float dw = 2.0 * (1.0 - SlopeOfDist) * dt;
float t = dt;
if (UseNoise != 0)
t += dt * rand(vec2(gl_FragCoord.x * gl_FragCoord.y / CameraFar)) * 3.0;
float tmax = t + 1.0;
// Start and end Depth to march ray in NDC
float z1 = TransformNDC(CameraPos + InToPixelNormalized * CameraNear).z;
float z2 = TransformNDC(CameraPos + InToPixelNormalized * InTravelDist).z;
// Start and end pos in shadowmap texture space
vec3 p1 = TransformShdowMapTextureSpace(CameraPos + CameraNear);
vec3 p2 = TransformShdowMapTextureSpace(CameraPos + InToPixelNormalized * InTravelDist);
float weight = SlopeOfDist; // first weight is always SlopeOfDist
float AccumulatedValue = 0.0;
for (; t <= tmax; t += dt)
{
float u = (t * (1.0 - SlopeOfDist) + SlopeOfDist) * t;
float z = mix(z1, z2, u);
// When the z meet Depth on screen, stop raymarching
if (z > Depth)
break;
vec3 CurrentPosInShadowMapTS = mix(p1, p2, u);
float ShadowMapDepth = texture(ShadowMapSampler, CurrentPosInShadowMapTS.xy).x;
if (ShadowMapDepth > CurrentPosInShadowMapTS.z)
AccumulatedValue += weight;
weight += dw;
}
return AccumulatedValue;
}
코드1. Raymarching (출처 : 직접 구현)
원본 알고리즘과 다른 점은 아래 3가지 입니다.
- Noise 텍스쳐 대신 rand 함수를 만들어 적용
- ShadowMapTS 공간에서 Raymarching할 p1, p2를 구하고 보간하여 사용
- NDC 공간에서 Raymarching할 z1, z2를 구하고 보간하여 사용
float AnisotropyIntensity(vec3 InFromSurfaceToCamera)
{
float atmosphereBrightness = InScatteringLambda * (CameraFar - CameraNear) / TravelCount; // lambda * (dmax − dmin) / (n + 1)
float g = AnisoG;
vec3 anisotropyConst = vec3(1.0 - g, 1.0 + g * g, 2.0 * g); // (1 − g, 1 + g * g, 2 * g)
float invLength = 1.0;
float h = anisotropyConst.x * (1.0 / sqrt(anisotropyConst.y - anisotropyConst.z * dot(InFromSurfaceToCamera, LightCameraDirection)));
float intensity = h * h * h * atmosphereBrightness;
return intensity;
}
코드2. Anisotropy scattering (출처 : 직접 구현)
4. 구현 결과





5. 실제 구현 코드
github.com/scahp/Shadows/tree/sponza_atmosphericshadowing
6. 레퍼런스
1. Foundations of Game Engine Development, Volume 2: Rendering
'Graphics > Graphics' 카테고리의 다른 글
| Color Science (2) | 2021.12.25 |
|---|---|
| [PBR] Substance/Roughness/Metalic (0) | 2021.05.03 |
| Pixel-projected reflections (0) | 2021.03.07 |
| Progressive Refinement Radiosity (0) | 2021.01.12 |
| Diffuse IrradianceMap과 Spherical harmonics를 통한 최적화 (0) | 2020.12.12 |