ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Accurate atmospheric scattering
    Graphics/Graphics Study 자료 2020. 9. 1. 06:15

     

    Accurate atmospheric scattering

     
     
    최초작성 : 2020-09-01
    마지막수정 : 2020-09-01
    최재호

     

    목차

    1. 목표
    2. 사전지식
    3. 내용
      3.1. The Phase Function
      3.2. The Out-Scattering Equation
      3.3. The In-Scattering Equation 
      3.4. 구현을 위해 필요한 추가 식 (논문에서 가져옴)
    4. 실제구현
    5. 구현결과
    6. 실제구현 코드
    7. 레퍼런스
     

    1. 목표

    이 글은 GPU Gems2 의 16번째 챕터 Accurate Atmospheric Scattering를 이해하고 구현해보는 것이 목표입니다.
     

    2. 사전지식

    처음 이 챕터를 읽었을 때, 흡수와 산란에 대한 기본 개념이 없어서 이해하는데 많이 어려웠습니다. 이 부분을 파악하기 위해서 레퍼런스 항목의 1. Absorption and Scattering (흡수와 산란) 그리고 4. Hoffman, N., and A. J. Preetham. 2002. "Rendering Outdoor Scattering in Real Time." ATI 혹은 5. Rendering Outdoor Light Scattering in Real Time 를 참고하시면 좋습니다.
    이 저자는 6. Nishita "Display of the Earth Taking into Account Atmospheric Scattering."를 기반으로 한 자신의 첫번째 구현을 3. [번역]Real-Time Atmospheric Scattering 에서 구현하였고, 이 구현이 GPU에서 동작할 수 있도록 최종 개선한 버젼이 GPU Gems2의 16번재 챕터인 Accurate Atmospheric Scattering 입니다. 그래서 Gems2를 읽고 저자의 의도가 잘 파악되지 않는다면 이글의 "레퍼런스 6번" -> "레퍼런스 3번" 을 순서대로 읽은 다음 Gems의 글을 보면 도움이 될거라 생각됩니다. 저자는 예제코드도 모두 배포하고 있는데, 편의상 아래의 첨부파일로 같이 올려둡니다.
     
     

    16.zip
    0.78MB

     
     
     

    3. 내용

    이 알고리즘은 흡수와 산란 알고리즘을 행성의 대기에 적용한 것이 차이점입니다. 흡수와 산란 공식에서는 라이트가 매질을 통과할 때 입자의 밀도(대기의 밀도)가 동일하다고 가정합니다. 하지만 행성에서 대기는 지상에서 우주방향으로 이동할 수록 줄어들게 됩니다. 흡수와 산란 공식이 이 부분을 반영한 부분이 기존 흡수와 산란 공식과 가장 큰 차이점입니다.
     
    2. [변역] Chapter 16. Accurate Atmospheric Scattering의 글에서 중요한 함수와 공식들의 설명은 아래와 같습니다.
     

    3.1. The Phase Function

    위상함수가 나타내는 의미는 라이트가 특정 위치에서 산란되었을 때, 카메라가 있는 방향으로 산란되어질 비율을 나타냅니다.
    입자의 크기에 따라서 2가지 Phase Function이 존재합니다.
    1. Rayleigh scattering : 공기중의 작은 분자들
     
     

    그림1. Rayleigh scattering

     
     
    2. Mie scattering : 먼지와 같은 큰 입자
     
     

    그림2. Mie scattering

     
     
     
    P.S. 실제 Gems 에 소개된 Phase Function은 Rayleigh와 Mie scattering을 모두 다 포함 할 수 있는 형태로 제공되는데, 저자가 올린 코드에서는 두 가지 산란 공식을 별도로 사용합니다.
     
    위상함수가 사용되는 부분은 바로 In-Scattering Equation 부분입니다. Extinction(흡수와 Out-Scattering)에서는 산란되거나 흡수되어지는 양에 대한 정보만 있으면 되지만 In-Scattering은 산란되어지는 방향이 카메라로 향하느냐? 도 중요합니다. 산란되어진 라이트의 양이 내눈에 얼마만큼 더 추가되어지는지 계산할 수 있기 때문입니다.
     
    대기에 2가지 작은 그리고 큰 입자 2가지에 대해서 각자 산란 정도를 계산한 후에 더 해줍니다.
     

    3.2. The Out-Scattering Equation

    Out-scattering 은 라이트가 산란하여 강도가 감쇄하는 것을 표현합니다. Out-scattering은 Multiplicative 연산이며, 감쇄하는 것이 첫 라이트의 강도에 비례해서 줄어든다고 생각할 수 있습니다. 레퍼런스1의 흡수와 산란에서는 라이트가 매질을 통과할 때 매질안의 입자가 상수 밀도를 가진다고 가정합니다. 하지만 대기의 경우 고도가 올라갈 수록 대기의 밀도가 더 낮아지는 점을 고려해야만 합니다. 그래서 아래와 같이 Pa~Pb 구간에 대한 감쇄를 계산합니다.
     
     
     

    그림3. Out-scattering equation, H0는 평균 대기 밀도의 고도, h는 현재 고도, K는 표준대기의 바다 레벨에서의 분자 밀도 상수

     
     
     

    3.3. The In-Scattering Equation 

    In-scattering은 주변 라이트들의 산란 중 일부가 카메라 방향으로 향하는 경우입니다. 그래서 주변의 라이트에 영향을 받게 됩니다. In-scattering은 Additive 연산입니다.
     
     

    그림4. In-scattering equation

     
     
    이 식은 태양으로 부터 온 한개의 반직선이 대기로 진입하여 특정 위치에서 산란된 후 그중 카메라로 향하는 라이트의 강도를 설명하는 식입니다.
     

    3.4. 구현을 위해 필요한 추가 식 (논문에서 가져옴)

     
    Rayleigh 산란이 발생되는 경우 그림5의 식을 사용해 카메라 쪽으로 들어오는 라이트의 강도를 계산할 수 있습니다.
     
     

    그림5. Rayleigh scattering 때문에 반사된 라이트를 나타내는 식, I0 : 입사 라이트의 세기, K는 평균 대기 상수(바다 레벨에서의 분자 밀도), θ : LightDirection과 ViewDirection 사이 각, Fr : 위상함수, λ : 입사광의 파장

     
     
    그림5에 있는 ρ는 고도에 따른 대기의 밀도를 뜻합니다.
     
     

    그림6. 고도에 따른 대기의 밀도, H0: 대기의 평균밀도를 발견한 높이, h : 고도

     
     
     
     
     

    그림7. Out-scattering의 식과 유사하며, 고도0에서 부터 S까지 라이트가 이동한 경우 라이트의 Optical depth를 계산하는 식입니다.

     
     
     
     
     

    그림8. 레퍼런6에서 가져온 그림

     
     
    그림8을 기준으로 카메라(Pv)에 들어오는 라이트의 강도 공식을 봅시다.
     
     

    그림9. 점 P에 도착하는 라이트의 강도, 대기의 상단인 Pc에서 특정 점 P까지 이동하며 라이트가 감쇄되는걸 나타내는 식

     
     
     
     
     

    그림10. 점 P에서 산란되어진 후 점 P부터 Pa 지점까지 이동하면서 빛이 감쇄되는 것을 나타내는 식. 산란된 라이트 역시, 대기를 통과하는 중에 감쇄가 된다는 것을 보여줌

     
     
     
     
     

    그림11. 점 Pa에서 Pb 구간내에서 산란되는 모든 라이트들을 합산하면, 카메라로 들어오는 라이트의 총 강도를 계산할 수 있음.

     
     
     
    최종적으로 그림11에 있는 식이 실제 구현시에 사용될 코드입니다.
    exp 함수안에 t(PPc, λ), t(PPa, λ)가 있는데, t 또한 내부에 적분을 가지고 있습니다. 내부의 적분까지 하게 되면서 연산량이 상당히 늘어나게 됩니다. 이 부분을 완화하기 위해서 레퍼런스6의 Nishita 논문에서는 Optical depth를 위한 룩업테이블을 사용합니다. 그림7에 있는 t를 특정 고도에서 부터 특정 각도(라이트방향과 뷰방향) 를 기준으로 룩업 테이블을 구성합니다.
     
     

    그림12. Nishita의 논문에서는 Cj와 ri를 사용하여 2차원 룩업테이블을 구성하였지만, O'Neil은 고도와 사이각을 사용한 2차원 룩업테이블을 구성함(레퍼런스3)

     
     
    레퍼런스3에서는 이 룩업테이블을 현재 점의 고도와 각도를 기준으로 정의합니다. 그림 13 참고.
     

    그림13. 점 P의 고도와 PA 와 그에따른 각도별 Optical density를 저장

     
     
    Gems2에서는 한번 더 룩업테이블을 최적화합니다. 저자는 룩업테이블을 완전히 제거하고 간단한 방정식 연산으로 대체합니다. 저자는 그것을 Scale function 이라 부릅니다. 이 Scale function 은 P의 고도에 대한 종속성을 제거하기 위해서 현재 고도의 대기 밀도 값인 exp(-h/H0) 으로 Normalize 시킨 후 나온 식입니다. 그렇기 때문에 P점의 고도는 필요하지 않습니다. 대신 "scale function의 결과" X "특정 점 P의 고도" 방식으로, 점 P의 고도에서 특정 각도에 대한 Optical depth를 구할 수 있습니다.

    float scale(float cos)
    {
        float x = 1.0 - cos;
        return AverageScaleDepth * exp(-0.00287 + x * (0.459 + x * (3.83 + x * (-6.80 + x * 5.25))));
    }

    이렇게 Optical Depth 계산 최적화를 통해서 실제 이 예제에서는 1개의 적분만 수행합니다. 
     
     
    이제 이 식들로 적분 구간을 어떻게 구해내는지 알아봅시다. 카메라가 우주에 있는 경우 대기와 교차하는 점 A와 B를 구해냅니다. 우주공간에서는 산란이 일어나지 않기 때문에 아래와 같이 산란이 발생하는 2개의 지점을 구하는 것입니다. 그리고 계산을 간단히 하기위해서 샘플링할 지점을 선택합니다. 샘플링한 지점에서의 적분 연산은 샘플 구간길이를 곱하여 ("점 Pn에서 구해진 산란값" * (AB의 길이 / n)) 해당 샘플 구간에서의 산란양을 구합니다.
     
     

    그림14. 대기 산란을 위해서 대기와 교차하는 점 A와 B를 얻어내고, 그 사이 샘플링할 점의 개수를 지정함 P1~P5.

     
     
     
    이제 이 내용을 실제로 쉐이더로 구현합니다. 원글에서는 추가적인 내용이 더 있지만 실제 코드에서는 그런 부분까지 모두 반영되어있지 않습니다. 그래서 여기까지의 내용만을 가지고 실제 구현을 시작합니다.
     

    4. 실제 구현

    총 3단계 렌더링 패스

    4.1. Draw Ground

    // 렌더링할때마다 바인드해야할 유니폼 변수들
    auto BindAtmosphericUniforms = [&](const jShader* Shader)
    {
        SET_UNIFORM_BUFFER_STATIC(Vector, "CameraPos", MainCamera->Pos, Shader);
        SET_UNIFORM_BUFFER_STATIC(Vector, "ToLight", DirectionalLight->Data.Direction, Shader);
        SET_UNIFORM_BUFFER_STATIC(Vector, "InvWaveLength", Vector(1.0f / AppSettingInst.Wavelength4[0], 1.0f / AppSettingInst.Wavelength4[1], 1.0f / AppSettingInst.Wavelength4[2]), Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "CameraHeight", MainCamera->Pos.Length(), Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "InnerRadius", AppSettingInst.InnerRadius, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "OuterRadius", AppSettingInst.OuterRadius, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "KrESun", AppSettingInst.Kr * AppSettingInst.ESun, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "KmESun", AppSettingInst.Km * AppSettingInst.ESun, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "Kr4PI", AppSettingInst.m_Kr4PI, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "Km4PI", AppSettingInst.m_Km4PI, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "Scale", 1.0f / (AppSettingInst.OuterRadius - AppSettingInst.InnerRadius), Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "AverageScaleDepth", AppSettingInst.AverageScaleDepth, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "ScaleOverAverageScaleDepth", (1.0f / (AppSettingInst.OuterRadius - AppSettingInst.InnerRadius)) / AppSettingInst.AverageScaleDepth, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "g", AppSettingInst.g, Shader);
        SET_UNIFORM_BUFFER_STATIC(float, "g2", AppSettingInst.g * AppSettingInst.g, Shader);
        SET_UNIFORM_BUFFER_STATIC(Vector, "ScatterColor", AppSettingInst.ScatterColor, Shader);
    };
    
    g_rhi->BeginDebugEvent("[2.2]. Draw Ground");
    
    auto Shader = jShader::GetShader(IsInsideOfAtmospheric ? "GroundFromAtmospheric" : "GroundFromSpace");
    
    g_rhi->SetShader(Shader);
    MainCamera->BindCamera(Shader);
    
    BindAtmosphericUniforms(Shader);
    
    // Earth의 크기를 InnerRadius 크기로 맞춤
    EarthSphere->RenderObject->Scale = Vector(AppSettingInst.InnerRadius);
    EarthSphere->RenderObject->tex_object = EarthTexture;
    EarthSphere->RenderObject->samplerState = jSamplerStatePool::GetSamplerState("LinearWrap").get();
    g_rhi->SetFrontFace(EFrontFace::CCW);
    MainCamera->IsEnableCullMode = true;
    
    BlendSrc = EBlendSrc::SRC_ALPHA;
    BlendDest = EBlendDest::ONE_MINUS_SRC_ALPHA;
    g_rhi->SetBlendFunc(BlendSrc, BlendDest);
    
    EarthSphere->Draw(MainCamera, Shader, {});
    g_rhi->EndDebugEvent();

     

    4.2. Draw Atmosphere

    g_rhi->BeginDebugEvent("[2.2]. Draw Atmosphere");
    
    auto Shader = jShader::GetShader(IsInsideOfAtmospheric ? "SkyFromAtmospheric" : "SkyFromSpace");
    
    g_rhi->SetShader(Shader);
    MainCamera->BindCamera(Shader);
    SET_UNIFORM_BUFFER_STATIC(int, "UseTexture", 1, Shader);
    
    BindAtmosphericUniforms(Shader);
    
    BlendSrc = EBlendSrc::ONE;
    BlendDest = EBlendDest::ONE_MINUS_DST_ALPHA;
    g_rhi->SetBlendFunc(BlendSrc, BlendDest);
    
    EarthSphere->RenderObject->Scale = Vector(AppSettingInst.OuterRadius);
    EarthSphere->RenderObject->tex_object = EarthTexture;
    
    g_rhi->SetFrontFace(EFrontFace::CW);
    MainCamera->IsEnableCullMode = true;
    EarthSphere->Draw(MainCamera, Shader, {});
    g_rhi->SetFrontFace(EFrontFace::CCW);
    
    g_rhi->EndDebugEvent();

     

    4.3. HDR

    g_rhi->BeginDebugEvent("[4]. HDR");
    
    auto ClearColor = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
    auto ClearType = ERenderBufferType::COLOR | ERenderBufferType::DEPTH;
    auto EnableDepthTest = true;
    auto DepthStencilFunc = EComparisonFunc::LESS;
    auto EnableBlend = true;
    auto BlendSrc = EBlendSrc::ONE;
    auto BlendDest = EBlendDest::ZERO;
    
    g_rhi->SetClearColor(ClearColor);
    g_rhi->SetClear(ClearType);
    
    g_rhi->EnableDepthTest(EnableDepthTest);
    g_rhi->SetDepthFunc(DepthStencilFunc);
    
    g_rhi->EnableBlend(EnableBlend);
    g_rhi->SetBlendFunc(BlendSrc, BlendDest);
    
    g_rhi->EnableDepthBias(false);
    
    static jFullscreenQuadPrimitive* s_fullscreenQuad = jPrimitiveUtil::CreateFullscreenQuad(nullptr);
    auto Shader = jShader::GetShader(AppSettingInst.UseHDR ? "AtmosphericHDR" : "Scale");
    g_rhi->SetShader(Shader);
    
    if (AppSettingInst.UseHDR)
        SET_UNIFORM_BUFFER_STATIC(float, "Exposure", AppSettingInst.Exposure, Shader);
    
    g_rhi->EnableCullFace(true);
    MainCamera->BindCamera(Shader);
    s_fullscreenQuad->SetUniformBuffer(Shader);
    s_fullscreenQuad->SetTexture(PostProcessTarget->GetTexture(), nullptr);
    s_fullscreenQuad->Draw(MainCamera, Shader, {});
    
    g_rhi->EndDebugEvent();

     
    4.4 Shader 코드

    4.4.1. 우주 or 대기내에서 하늘을 바라본 경우
     

     

    그림15. 그림에서 처럼 행성이 아닌 대기에서 일어나는 산란만 보는 경우에 사용되는 쉐이더입니다.

     

     

    vec3 ToVertexFromCam = PosW - CameraPos;
    float Far = length(ToVertexFromCam);
    ToVertexFromCam /= Far;
    
    // View direction의 반직선과 대기 영역을 나타내는 Sphere간 충돌 지점 중 가까운 그리고 먼 거리 구함
    float Near = getNearIntersection(CameraPos, ToVertexFromCam, CameraHeight * CameraHeight, OuterRadius * OuterRadius);
    
    vec3 RayStart = CameraPos + ToVertexFromCam * Near;
    Far -= Near;
    
    // 대기와 만나는 레이의 시작위치 ~ 끝위치를 지날때의 Optical depth를 구함.
    float StartAngle = dot(ToVertexFromCam, RayStart) / OuterRadius;
    float StartDepth = exp(-1.0 / AverageScaleDepth);
    float StartOffset = StartDepth * scale(StartAngle);
    
    const int Samples = 3;
    // 대기를 통과하는 거리를 fSamples 수만큼 나눈다, 이 샘플 숫자만큼 적분을 근사함
    float SampleLength = Far / float(Samples);
    float ScaledLength = SampleLength * Scale;
    vec3 SampleRay = ToVertexFromCam * SampleLength;
    // 샘플 구간의 중심점을 기준으로 적분함, 루프를 돌면서 계속 증가
    vec3 SamplePoint = RayStart + SampleRay * 0.5;
    
    vec3 CalculartedColor = vec3(0.0);
    for (int i = 0; i < Samples; ++i)
    {
    	// 현재 샘플 포인트의 고도
        float Height = length(SamplePoint);
        // 현재 샘플 포인트의 대기 밀도 exp(-h/H0)
        float Depth = exp(ScaleOverAverageScaleDepth * (InnerRadius - Height));
        
        // 라이트와 샘플포인트 사이각, 그리고 카메라와 샘플포인트의 사이각을 구함. 이때 샘플포인트는 원점에 있다고 가정한 위치
        float LightAngle = dot(ToLight, SamplePoint) / Height;
        float CameraAngle = dot(ToVertexFromCam, SamplePoint) / Height;
        
        // Optical depth를 구함 (자세한건 아래 추가 그림으로 설명할 것임)
        float Scatter = StartOffset + Depth * (scale(LightAngle) - scale(CameraAngle));
        vec3 Attenuate = exp(-Scatter * (InvWaveLength * Kr4PI + Km4PI)) * ScatterColor;
    
    	// 현재 샘플포인트가 커버하는 구간에서의 In-scaterring 값을 구함
        CalculartedColor += Attenuate * (Depth * ScaledLength);
        
        // 다음 샘플포인트 위치로 이동
        SamplePoint += SampleRay;
    }
    
    // 레이리 스케터링 결과를 저장
    vec3 RayleighColor = CalculartedColor * (InvWaveLength * KrESun);
    
    // 미 스케터링 결과를 저장
    vec3 MieColor = CalculartedColor * KmESun;
    vec3 ToCameraFromPosW = CameraPos - PosW;
    
    float cos = dot(ToLight, ToCameraFromPosW) / length(ToCameraFromPosW);
    float cos2 = cos * cos;
    // 레이리 스케터링과 미 스케터링 각각에 맞는 위상함수를 적용함.
    color = vec4(getRayleighPhase(cos2) * RayleighColor + getMiePhase(cos, cos2, g, g2) * MieColor, 1.0);
    color.a = 1.0;

     

     

    그림16. 위에서 본 scale function을 봤을 때, fStartAngle은 V3Start 점에서 D까지의 Optical depth를 의미합니다.

     

     

     

    그림17. 그림의 빨간색 선 부분의 Optical depth를 구하는 식은 다음과 같습니다. StartOffset + Depth * (scale(LightAngle) - scale(CameraAngle)) 즉, "G~D Optical depth" - "P~H Optical depth" + P~A Optical Depth를 나타냅니다.

     

     
    카메라가 대기 내부에 있는 경우는 Ray의 시작이 CameraPosition이 되면 Vertex의 Far이 Camera와 Vertex사이 거리가 되는점을 제외하면 계산 방식은 모두 동일합니다.
     
    4.4.2. 우주 or 대기내에서 지면을 바라본 경우
    그림8에서와 같이 지면까지 라이트가 대기를 모두 통과해 도달했다가 다시 대기를 통과한 후 나가는 상황입니다.

    vec3 ToVertexFromCam = PosW - CameraPos;
    float Far = length(ToVertexFromCam);
    ToVertexFromCam /= Far;
    
    vec3 PosWNorm = normalize(PosW);
    
    // View direction의 반직선과 대기 영역을 나타내는 Sphere간 충돌 지점 중 가까운 그리고 먼 거리 구함
    float Near = getNearIntersection(CameraPos, ToVertexFromCam, CameraHeight * CameraHeight, OuterRadius * OuterRadius);
    
    vec3 RayStart = CameraPos + ToVertexFromCam * Near;
    Far -= Near;
    
    // 대기 가장 상단 부분의 대기 밀도 exp(-h/H0)
    float Depth = exp((InnerRadius - OuterRadius) / AverageScaleDepth);
    float CameraAngle = dot(-ToVertexFromCam, PosWNorm);
    float LightAngle = dot(ToLight, PosWNorm);
    float CameraScale = scale(CameraAngle);
    float LightScale = scale(LightAngle);
    
    // 대기 상단에서의 Optical depth. (대기 상단에서는 대기 밀도가 거의 0에 가깝다. exp(-1)이 대기 최상단인데 대략 0.3678...)
    float CameraOffset = Depth * CameraScale;
    
    // 두 방향 (Camera방향과 라이트 방향)의 scale function 합산. 여기에 고도만 곱하면, 해당 고도에서의 Optical depth 임.
    float Temp = (LightScale + CameraScale);
    
    const int Samples = 3;
    // 대기를 통과하는 거리를 fSamples 수만큼 나눈다, 이 샘플 숫자만큼 적분을 근사함
    float SampleLength = Far / float(Samples);
    float ScaledLength = SampleLength * Scale;
    vec3 SampleRay = ToVertexFromCam * SampleLength;
    
    // 샘플 구간의 중심점을 기준으로 적분함, 루프를 돌면서 계속 증가
    vec3 SamplePoint = RayStart + SampleRay * 0.5;
    
    vec3 CalculartedColor = vec3(0.0);
    vec3 Attenuate;
    for (int i = 0; i < Samples; ++i)
    {
        // 현재 샘플 포인트의 고도
    	float Height = length(SamplePoint);
    
        // 현재 샘플 포인트의 대기 밀도 exp(-h/H0)
    	float Depth = exp(ScaleOverAverageScaleDepth * (InnerRadius - Height));
    
        // 현재 고도에서의 Optical depth 에서 대기 최상단에서의 Optical depth(대략 0.3678...)를 빼줌.
        // 이것 때문에 대기의 상단에 있는 경우 Optical depth를 0이 되어서 In-scattering 되는 양을 0으로 만들어 줌.
    	float Scatter = Depth * Temp - CameraOffset;
    	Attenuate = exp(-Scatter * (InvWaveLength * Kr4PI + Km4PI)) * ScatterColor;
    
        // 현재 샘플포인트가 커버하는 구간에서의 In-scaterring 값을 구함
    	CalculartedColor += Attenuate * (Depth * ScaledLength);
    
        // 다음 샘플포인트 위치로 이동
    	SamplePoint += SampleRay;
    }
    
    // 레이리 스케터링 결과를 저장
    vec3 RayleighColor = CalculartedColor * (InvWaveLength * KrESun + KmESun);
    
    // 미 스케터링 결과를 저장
    vec3 MieColor = Attenuate;
    
    vec3 diffuseColor = texture(tex_object, TexCoord_).xyz;
    
    // 레이리 스케터링과 미 스케터링 각각에 맞는 위상함수를 적용함.
    color = vec4(RayleighColor + diffuseColor.xyz * MieColor, 1.0);

     

     

    그림18. 이 경우 보라색 점 P1, P2, P3를 대상으로 In-scattering 되는 라이트를 계산합니다.

     
     
    이 경우도 카메라가 대기 내부에 있는 경우는 Ray의 시작이 CameraPosition이 되면 Vertex의 Far이 Camera와 Vertex사이 거리가 되는점을 제외하면 계산 방식은 모두 동일합니다.
     

    5. 구현결과

     
     

    그림19. 최종결과
    그림20. 최종결과
    그림21. 최종결과
    그림22. 최종결과

     
     
     

     

    6. 구현코드

    https://github.com/scahp/Shadows/tree/AccurateAtmosphericScattering
     

    7. 레퍼런스

    1. Absorption and Scattering (흡수와 산란)
    2. [번역] Chapter 16. Accurate Atmospheric Scattering
    3. [번역]Real-Time Atmospheric Scattering
    4. Hoffman, N., and A. J. Preetham. 2002. "Rendering Outdoor Scattering in Real Time." ATI
    5. Rendering Outdoor Light Scattering in Real Time
    6. Nishita, T., T. Sirai, K. Tadamura, and E. Nakamae. 1993. "Display of the Earth Taking into Account Atmospheric Scattering." In Proceedings of SIGGRAPH 93, pp. 175–182.
    7. UnityShader-GPU GEM atmospheric scattering source code analysis
     

    'Graphics > Graphics Study 자료' 카테고리의 다른 글

    [GPU Gems 3]Advanced Techniques for Realistic Real-Time Skin Rendering  (2) 2020.11.10
    Physically-Based Cosmetic Rendering  (0) 2020.09.09
    Perspective Shadow Map (PSM)  (0) 2020.08.05
    Cascade Shadow Map  (0) 2020.07.10
    VolumeLight  (0) 2020.06.24

    댓글

Designed by Tistory & scahp.