| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- unrealengine
- VGPR
- UE5
- Graphics
- Wavefront
- ue4
- Study
- Shadow
- vulkan
- scattering
- atmospheric
- scalar
- Nanite
- rendering
- SIMD
- 번역
- GPU
- shader
- wave
- hzb
- RayTracing
- SGPR
- ShadowMap
- forward
- DirectX12
- texture
- DX12
- deferred
- GPU Driven Rendering
- optimization
- Today
- Total
RenderLog
[번역]Real-Time Atmospheric Scattering 본문
개인 공부용으로 번역한 거라 잘못 번역된 내용이 있을 수 있습니다.
또한 원작자의 동의 없이 올려서 언제든 글이 내려갈 수 있습니다.
출처 : https://www.gamedev.net/articles/programming/graphics/real-time-atmospheric-scattering-r2093/
Real-Time Atmospheric Scattering
Published May 04, 2004 by Sean O'Neil, posted by s_p_oneil
이 렌더링 알고리즘은 SIGGRAPH93 의 발행물을 기반으로 합니다 : Tomoyuki Nishita, Takao Sirai, Katsumi Tadamura, and Eihachiro Nakamae(앞으로 이 글에서는 Nishita 라고 할 것임)의 "Display of The Earth Taking into Account Atmospheric Scattering". 이 발행물은 이 글에 포함되어있습니다 여기. 만약 이것을 읽지 않았다면, 이 글을 읽기전에 먼저 읽어주세요. 수학에 관한것을 이해할 필요는 없습니다, 그러나 개념을 이해할 필요는 있습니다.
그리고 프로토타입을 실행하기 전에 README.TXT를 읽으세요. 만약 README.TXT에 있는 질문을 이메일로 보내는 사람이 있다면, 다음 버젼을 발행할때 프로그램이 실행될 때마다 자동으로 그것이 열리도록 할 것입니다. 그리고 파일이 없다면 크래쉬가 발생할 것입니다.
Disclaimer: 만약 당신이 필요한 것이 그저 예쁜 skybox라면, 이 글은 당신이 찾는것이 아닙니다. 만약 당신이 멋진 석양과 구름이 애니매이션 되어지는 skybox가 필요하다면, 이 글은 여전히 당신이 찾는 것이 아닙니다(그러나 몇가지 아이디어는 있을 겁니다)
나의 절차적 행성 렌더러를 위해서 사실적인 실시간 대기 산란을 구현하기 원했기 때문에 이 글을 썼습니다.
시작하기전, 나는 포럼에서 대기 산란에 대해서 많은 질문을 봤습니다. 멋진 질문들이 많았으나 내가 필요한 대답은 아니었습니다. 불행하게도, 특정 종류의 행성이나 달을 특정 종류의 대기로 뷰포인트에서 부터 정확하게 렌더링할 수 있는 것은 많은 불필요한 것들을 제거할 수 없다는 의미입니다. 당신은 대기의 밀도가 모든 곳에서 같다고 가정할 수 없습니다. 또한 산란 상수나 그들의 파장 의존성도 모든곳에서 동일하다고 가정할 수 있습니다. 이것은 당신이 시작하기 전에 대부분의 유용한 최적화를 무효화합니다.
완전한 소스코드와 함께 제공한 데모는, 행성을 그리지 않고 태양도 그리지 않습니다. 그것은 비어있는 대기 껍데기(shell)을 그립니다, Nishita의 발행물 그림6과 가장 비슷합니다. 그것은 행성의 표면에서 반사되는 것의 산란을 보여주기 위해 내부 구체의 바깥을 렌더링하고 대기를 통과하며 산란되는 것을 보여주기 위해서 외부 구체의 내부를 렌더링합니다. Rayleigh와 Mie 산란을 구현하며, 데모에서 모든 상수는 런타임에 수정가능합니다.
The Problem
Nishita가 설명한 주요 최적화는 대기에서 태양과 모든 점 사이의 optical length의 미리 계산된 조회 테이블이었습니다. 태양은 아주 멀리 있기 때문에 반직선은 평행하다고 생각할 수 있었기 때문에 가능했습니다. 불행히도, 카메라에서 부터 시작하는 반직선이 행성의 그림자에 들어가면, 대기에서 카메라와 모든 점 사이의 optical length를 결정에 대한 계산은 고비용으로 남겨두었습니다, 그리고 두 가지 optical depth(이것 자체도 비싼 지수 방정식의 성가신 적분입니다)를 포함하는 값비싼 지수 방정식의 성가신 적분. 성가신 중첩된 적분 중 하나는 조회 테이블로 처리됩니다만 다른 것은 그렇지 않습니다.
수학 복습: 적분이 무엇인지 배우지 않은(기억나지 않거나) 사람들은 걱정마세요. 가장 기본적인 형태에서, 적분은 간단하게 특정 범위에 대한 합입니다. 프로그래머들은 반복문을 합을 계산하기 위해서 항상 사용하며, 그것은 적분 방정식을 해결하기 위한 방법중 한 방법입니다. 반직선을 따라 optical depth의 근사를 계산하기 위해서, 간단하게 반직선을 작은 "샘플" 반직선들로 나눕니다, 그런 후 샘플 반직선들 순회하며 방정식(density * length)의 결과를 합산합니다. 더 작은 샘플 반직선은, 더 정확한 결과를 만듭니다.
이제 Nishta의 최적화를 사용해도 계산이 얼마나 고통스러운지 설명할 것입니다. 만약 10개의 샘플 반직선을 가지고 문제가 중첩된 경우, 버택스당 10*10배(Nishita 최적화가 없다면 10 * 20) 비싼 수학 방정식을 풀어야만 합니다. 각각의 컬러 채널(red, green and blue) 별로 분리하여 계산해야하기 때문에 3으로 곱하세요. 만약 Rayleigh 와 Mie 산란을 동시에 한다면 다시 2를 곱하세요. 우리는 버택스당 600개의 방정식까지 도달합니다, 그리고 반직선이 행성의 그림자에 들어갈지 말지 여부를 결정하는 것도 있지 마세요. 이것을 리얼타임에 처리하길 원한다면, 이 접근방법이 동작하지 않는 다는 것을 코딩하기전에 쉽게 알아차릴 수 있습니다.
A Better Lookup Table
optical length 조회 테이블을 조금 수정하므로써, 두가지 optical length 계산들 (하나는 라이트 소스 그리고 다른 하나는 카메라)에 쓸 수 있습니다, 그것은 특정 위치의 밀도를 알려주며, 언제 반직선이 행성의 그림자에 들어가는지 알려줍니다. 조회 테이블이 충분히 정밀하다면, 이것은 한개의 적분만 남겨줍니다, 3 or 4 샘플들로 충분히 보기 좋게 할 수 있습니다. 나는 지금까지 프로토타입만 구현했습니다만, CPU에서 100% 계산하며 꽤 무식한 렌더링 방법에서도 리얼타임으로 동작합니다. 내 시스템에서 50-100FPS 으로 동작하며, 좀 더 빠르게 만들기 쉬워야만 합니다.
내가 만든 조회 테이블은 발행물에서 설명한 것과 약간 다릅니다. 그것은 버택스/픽셀 쉐이더에서 사용할 경우 2D 텍스쳐맵에 잘 맞습니다. 그것은 2개의 채널을 Rayleigh 산란 그리고 2 채널을 Mie 산란을 위해 사용합니다. 모든 채널에 대해서, 가로축은 샘플포인트의 높이, 그리고 수직축은 수직으로 부터의 샘플 레이의 각을 나타냅니다. 각은 180도 까지 갑니다(바로 아래 방향) 왜냐하면 지면위의 수평선에서 뷰포인트는 90도 이상을 볼 수 있기 때문입니다.
만약 반직선이 대기의 바깥쪽 가장자리로 이동하다가 행성에 충돌한다면, 결과를 해석할 두가지 방법이 있습니다. 만약 당신이 태양(혹은 라이트 소스)으로의 optical length를 원한다면, optical length는 0이 되어야만 합니다, 왜냐하면 그것은 샘플포인트가 행성의 그림자안에 있다는 의미기 때문입니다. 만약 당신이 카메라로의 optical length를 원한다면, 반직선이 행성에 충돌하지 않은 것처럼 해야합니다. 왜냐하면 수평선에서 많은 삼각형은 행성 뒤에서 하나 혹은 두개의 버택스들을 가지기 때문입니다. 그리고 이 버택스들을 검정색으로 만드는 것은 수평선을 엉망으로 만듭니다.
채널 1 - 반직선이 행성에 충돌했다면, 이 값은 0. 만약 그렇지 않다면 이 값은 이 높이에 대한 Rayleigh scale density.
채널 2 - 샘플 반직선을 따라 샘플포인트 Rayleigh scale optical length.
채널 3 - 채널1과 같지만 Mie scale density 사용
채널 4 - 채널2와 같지만 Mie scale optical length 사용
조회 테이블은 CPixelBuffer::MakeOpticalDepthBuffer()에서 만들어집니다, 그리고 주석이 달려있습니다. 나는 이 설명과 주석이 무엇을 하려고 하는지 명확하게 만들어주었으면 좋겠습니다.
Using the Lookup Table
처음 아이디어를 떠올렸을 때 이 테이블이 직관적으로 보였지만, 구현하는데 놀랍도록 많은 문제를 만났습니다. 로빈경이 "이봐! 쉽네요!" 외치는 것처럼, I was snatched off my fake horse(so to speak). 운좋게도 나는 모든 문제를 해결할 수 있었습니다.
불행히도 나는 외부 적분을 4D 조회 테이블 없이 피하는 방법을 찾지 못했습니다. 4D 조회 테이블은 유연성이 떨어집니다, 그리고 나는 각 행성과 달의 대기에 차이가 필요했습니다. 해결할 방법이 없었기 때문에, 카메라에서 각 버택스로의 반직선을 추적하고, 샘플 반직선로 나누고 각 샘플 반직선의 산란을 계산해야만 했습니다.
각각의 샘플 반직선에 대해서, 중심점을 계산하고 그것의 높이와 라이트 소스에 대한 각도를 결정합니다. 테이블에서 높이과 각도를 사용하여 조회를 수행합니다. 만약 채널 1이 0이면, 샘플은 그림자 안에 있고 당신은 이 샘플 반직선을 무시하고 다음 것으로 이동합니다. 만약 그렇지 않다면, 샘플 포인트의 대기 밀도를 채널1에 저장합니다. 그리고 채널2에 샘플 반직선에서 라이트 소스까지의 optical depth로 저장합니다. 이제 샘플 반직선에 대해서 카메라로의 optical length를 결정할 때입니다, 그리고 이것은 약간 까다로운 부분입니다. 만약 카메라가 우주에 있다면 그냥 라이트소스 처럼 처리되기 때문에 쉽습니다. 샘플포인트의 높이와 카메라로의 각도를 계산하고, 테이블에서 조회를 실행하세요, 그리고 2채널에 첫번째 조회 때 저장한 optical depth 를 추가합니다.
카메라가 대기의 내부에 있을 때, 조회테이블은 깨집니다. 왜냐하면 대기안의 한점에서 대기의 꼭대기로의 optical depth를 저장하기 때문입니다. 대기내의 한점에서 다른 대기내의 한점으로의 optical depth는 저장하지 않습니다. 문제를 좀 더 복잡하게 만들려면, 특정 지점이 카메라 위에 있을때, 카메라에 대한 각도가 아래를 가리키면서 조회 버퍼의 잘못된 지점을 사용하게 됩니다.
첫번째 문제의 답은 2개의 조회 테이블을 쓰는 것입니다 (하나는 샘플포인트, 하나는 카메라), 그리고 다른 것으로 부터테이블을 뺍니다. 2번째 문제의 답은 대상 버택스가 카메라보다 더 높은 경우 두가지 조회 모두 반직선 방향을 역전시키는 것입니다. 코드를 깔끔하게 하기 위해서, 카메라가 우주에 있으면, 대기의 상단에 그리고 그것이 항상 대기에서 그려지고 있는 특정 버택스 보다 위에 있는 것 같이 처럼 다룰 수 있습니다.
pseudo-code는 다음고 같습니다:
if (camera is above vertex)
camera_lookup = lookup(camera_height, angle to camera);
else
camera_lookup = lookup(camera_height, angle from camera);
for(each sample ray)
{
light_source_lookup = lookup(sample_height, angle to light source);
density = light_source_lookup(channel 1);
if (density > 0)
{
density *= sample_ray_length;
optical_depth = light_source_lookup(channel 2);
if (camera is above vertex)
{
sample_point_lookup = lookup(sample_height, angle to camera);
optical_depth += sample_point_lookup(channel 2) - camera_lookup(channel 2);
}
else
{
sample_point_lookup = lookup(sample_height, angle from camera);
optical_depth += camera_lookup(channel 2) - sample_point_lookup(channel 2);
}
calculate_scattering_attenuation;
}
}
calculate_vertex_color;

이 그림은 반직선 하나가 3개의 샘플 반직선으로 나누어진 것과 몇가지 샘플 카메라 위치와 버택스를 보여줍니다. 오랜지 선은 산란 계산을 위해 통과해야하는 반직선들과 각도 계산을 위한 반직선의 방향을 가리킵니다. 파랑색 선은 optical depth 테이블에서 첫번째 조회로 부터 빼야만 하는 부분을 가리킵니다. 반직선이 샘플 반직선으로 쪼개진 경우, 파랑색 선은 첫번째 샘플 위치만 보여줍니다. 각 샘플 포인트를 가리키는 빨상색 선은 각 샘플포인트의 높이와 각도 차이를 알아보기 쉽게 해줍니다. 각 샘플 포인트로 부터의 초록색 선은 높이와 각도가 라이트소스에 대한 optical depth가 얼마나 영향을 미치는지 쉽게 확인하게 해줍니다.
위의 pseudo-code 실제 코드는 CGameEngine::SetColor()에 있습니다, 그리고 주석이 달려있습니다. 나는 pseudo-code above 와 그림이 무엇을 하려고하는지 명확하게 해주길 바랍니다. 그렇지 않다면, 내가 그것을 포럼에서 듣게 될것이라 확신합니다.
Optimizations
더 빠르게 동작하게 하기 위해서 당신이 할 수 있는 수많은 최적화가 있습니다. 우선, 많은 최적화는 SetColor() 함수에서 이루어질 수 있습니다. 변수의 캐싱과 몇개의 1D 조회 테이블의 생성은 많은 계산을 빠르게 합니다. 예를들어, 위상함수 계산은 한 채널이 Rayleigh 그리고 한채널은 Mie인 1D 조회 테이블로 대체 가능할 것입니다. 같은 내용을 감쇄 계산에서 exp() 3번 호출하는 것을 피하기 위해서 3채널을 사용하여 처리할 수 있습니다. 나는 다른 것도 있다고 확신합니다.
숨겨진 면을 제거를 향상시켜 CGameEngine::SetColor()가 많이 호출되는 것을 피하는 것도 좋은 생각입니다. 동적 level-of-detail 구현은 SetColor()를 호출하는 회수를 최소화 하는데 도움을 줄 것입니다.
만약 충분하지 않다면, 당신은 매 프레임 매 버택스마다 컬러를 새로 계산하지 않기 위해서 프레임 일관성을 사용할 수 있습니다. 카메라나 라이트 소스가 버택스로 부터 멀리 이동해서 눈에 띄는 컬러 오류가 발생한 경우만 버택스 컬러를 업데이트할 필요가 있습니다. 실제로, 대기가 행성과 함께 회전하지 않는다면(한쪽이 광원을 향한다면), 당신은 카메라가 움직이는 것에 관해서만 신경쓰면 됩니다.
이것을 위한 몇몇 작업은 GPU로 작업을 덜어낼 수 있습니다. 만약 그럴게 없다면, 적분의 합으로 부터 컬러값 자체로 변환은 버택스 쉐이더에서 완료될 수 있습니다. 만약 위상함수 계산을 픽셀쉐이더에 넣는다면, 더 부드러운 그라데이션을 얻을 것입니다. 그것은 Mie 산란에서 더 좋아보일 것입니다. (낮은 테셀레이션에서는 정말 보기 좋지 않습니다.)
마지막으로, 나는 다른 누군가가 더 좋은 방법을 떠올리길 바랍니다. 비록 내가 온라인에서 찾을 수 있었던 것을 감안할때 내가 한것이 자랑스럽지만, 나는 계속해서 다른 좋은 방법이 있을것이라 생각합니다, 아마 3D buffer나 여러개의 2D 버퍼와 연관되어 있을 것입니다.
Other Thoughts
나는 real-time volumetic cloud을 작업하고 있습니다, 그러나 아직 전체 행성에서 리얼타임으로 구동되지 않습니다. 내가 하게 되면, 예쁜 석양을 위해서 대기 산란을 적용하는 것은 어렵지 않을 것입니다. 어려울 것 같은 것은 대기 산란 반복문에서 구름 그림자 버퍼를 샘플링하는 것입니다, 왜냐하면 멋진 shafts를 만들어야만 하기 때문입니다. 그것은 레이트레이싱에 너무 가깝게 보이지만, 내가 그것을 빠르게 만들 수 있다고 확신 할 수 없습니다.
당신이 질문하기 전에, 버택스가 brute-force spheres에서 너무 멀리 떨어져있기 때문에카메라가 지면에 가까이 있을 때 "지면" 산란은 멀리 보입니다. 당신의 발 바로 아래 지면은 너무가까워서 검정색으로 보입니다(즉, 항상 산란되어 들어오는 빛이 거의 없음), 그러나 가장 가까운 버택스가 멀어지면 그렇지 않을 것입니다. 내 행성 렌더러가 사용하는 것 처럼 동적 LOD 구조를 쓰면, 더 좋게 보일 것입니다.
'Graphics > 참고자료' 카테고리의 다른 글
| [번역]A Better Way to Scalarize a Shader (0) | 2020.10.31 |
|---|---|
| [번역] Effective Water Simulation from Physical Models (0) | 2020.09.22 |
| [번역] INTRO TO GPU SCALARIZATION – PART 2 -SCALARIZE ALL THE LIGHTS (0) | 2020.08.08 |
| [번역] Accurate Atmospheric Scattering (0) | 2020.08.07 |
| [번역] INTRO TO GPU SCALARIZATION – PART 1 (0) | 2020.07.20 |