ABOUT ME

Today
Yesterday
Total
  • [week5] PixelShader - Fog
    UE5 2025. 4. 4. 22:18

    학습 목표

    • Pixel Shader의 동작을 이해한다.
    • 후처리 (Post Process) Pass 구현을 통해 Multi-Pass Rendering에 대해 이해한다.

     

    Fog

    • Fog는 Post Process에서 구현할 수 있지만, 제대로 하려면 Main-pass에서 처리하는 게 맞다.

     

    왜 메인 패스에서 Fog를 처리하는가?

    1. 픽셀이 “생성될 때” 안개를 적용해야, 물리적으로 맞는 색이 나온다

    • 픽셀이 생길 때는:
      • 조명 계산
      • 머티리얼 색상
      • 그림자 여부
    • 이때 안개가 존재하면, → 빛의 감쇠, 색 번짐, 환경 색과의 혼합 같은 걸 고려해서 최종 색상을 결정함.

    👉 즉, Fog는 그냥 “화면 위에 덮는 투명 레이어”가 아니야.
    빛이 대기 속을 통과하면서 감쇠되는 걸 모델링하는 과정이야.


    2. 깊이별/고도별 안개량이 오브젝트별로 다르기 때문

    • 후처리에서 하면 그냥 Depth 텍스처를 참조해서 "화면 상"에서만 안개 처리 가능
    • 그런데 메인 패스에서는 각 픽셀이 실제 어떤 오브젝트에서 왔는지,
      어떤 머티리얼을 쓰는지, 그 위치가 월드상 어디인지까지 알고 있어

    ✔ 즉, 정확한 안개 처리는 픽셀이 그려질 때가 가장 이상적인 시점


    ❌ 후처리에서 Fog만 쓰면 어떤 문제가 생기냐면…

    1. Z 테스트 이후에 덮기 때문에 깊이 정렬에 문제가 생김

    → 예: 앞에 있는 캐릭터도 안개에 가려지거나, 이상한 투명함 생김

    2. 라이팅, 그림자 위에 덮어씌우기만 함

    → 물리적으로 맞지 않음 (빛 감쇠 없이 색만 바뀜)

    3. 픽셀이 "어디서 왔는지"에 대한 정보 없음

    → 같은 색이라도 가까운 캐릭터랑 먼 산이 같은 안개량 적용될 수 있음

     

    💡 그럼 후처리에서 Fog는 못 써?

    쓸 수 있어!
    특히 성능이 중요한 모바일 게임이나,
    간단한 분위기용 페이크 안개엔 후처리 Fog도 많이 써.

     

    PostProcessing에서도 Depth 갖고와서 Fog만들 수 있는데 왜 Main에서 하나?

     

    거리기반으로 안개를 만들어줄 수는 있지만.. 다른 효과는 고려 못한다!

    ❌ 그런데… "그게 메인 패스랑 완전히 같은 건 아냐"

    1. 후처리는 이미 픽셀이 만들어진 후라, 조명/재질/셰이딩이 끝난 상태야

    • 후처리는 그냥 색상을 후처리할 뿐,
    • 그 픽셀이 왜 그런 색이 되었는지 (예: 머티리얼, 라이팅, 그림자) 정보는 없음

    👉 즉, “빛이 안개 속에서 감쇠되었다” 같은 효과를 못 만들어


    2. 후처리는 픽셀이 만들어진 ‘이유’를 모름

    • 후처리는 그냥 화면에 찍힌 색상을 보고 그 위에 안개 색을 섞는 거야
    • 예를 들어, 어떤 픽셀이:
      • 먼 배경 오브젝트라서 안개에 가려져야 할 것처럼 보여도,
      • 그게 반사라거나, 광원이 닿은 부분이라거나 하면?
      • 그냥 덮어씌우는 식의 후처리는 이걸 구분 못 해

    ✔ 즉, 화면에 있는 색상이 어떤 공간 위치에서 왔는지 정확히 알 수 없음

     

     

    안개 속에서의 빛 감쇠 효과는 어떻게 일어나는가?

    빛이 안개 속을 지나오면서 약해지는 과정을 수식으로 표현하면:

    FinalColor = LightColor * SurfaceColor * transmittance(d)

    여기서 transmittance(d)는 빛이 거리 d만큼 안개를 통과하면서 얼마나 약해졌는지를 나타내는 값이야.

    이걸 계산하려면:

    • 빛이 지나온 거리
    • 그 경로에 어떤 안개 밀도가 있었는지
    • 표면의 위치
    • 표면에 닿은 조명 정보

    가 필요함

     

    ❌ 그런데 후처리에서는?

    후처리 셰이더는 그 픽셀이 어떻게 만들어졌는지 모름.

    • 예: 어떤 표면에 어떤 조명이 비췄고, 거기까지 빛이 얼마나 감쇠되었는지
    • 그걸 표현하려면 빛 → 표면까지의 "빛의 경로", 그리고 표면 → 카메라까지의 "시야 경로" 두 개가 필요해

    하지만 후처리는 그저:

    float4 color = tex2D(SceneColor, uv);
    float depth = tex2D(SceneDepth, uv).r;

    ✔ 이렇게 색과 깊이만 보고 있을 뿐이야.
    빛이 어떻게 거기까지 갔는지는 알 수 없어.

     

    추가적으로, 메테리얼도 안개를 후처리하면 문제가 생기는데..

    💣 문제점 구체적으로 보면:

    1. 빛의 감쇠를 무시함

    • 진짜 안개는 빛이 약해지는 거야 (transmittance)
    • 후처리는 그냥 픽셀 색에 fogColor를 섞는 거라 빛 감쇠 효과가 없음

    2. 재질 속성 무시

    • 금속, 유리, 나무, 살결… 다 같은 방식으로 안개 처리됨 ❌
    • 예: 반사 머티리얼은 실제로 안개 속에서 빛을 더 많이 반사할 수도 있는데 그걸 반영 못함

    3. 서로 다른 머티리얼끼리의 안개 표현 불일치

    • 반사 오브젝트에 안개가 덜 끼고, 암석 표면엔 진하게 보여야 하는데
    • 후처리는 무조건 색만 보고 동일하게 처리

     

    메인패스 안개는 이렇게

    픽셀 셰이더에서 머티리얼/조명/거리/고도/빛 방향 등 모든 정보를 가지고:

    float3 litColor = CalculateLighting(worldPos, normal, ...);
    float transmittance = exp(-fogDensity * distance);
    float3 finalColor = lerp(fogColor, litColor, transmittance);

     

    → 이건 빛이 재질을 때리고, 다시 카메라로 오면서 감쇠된 걸 계산하는 진짜 안개 표현

     

     

     

    결국에는, Fog를 제대로 구현하기 위해서는 MainPass에서 같이 연산이 일어나야한다!

    Fog를 미리 RTV에 구현해서 그 정보를 참조해서 MainPass에서 곱해주던,

    MainPass에서 같이 생성해서 연산하던 해야함!

     

     

     

    Exponential Fog를 RTV생성 후 샘플링 vs 메인패스에서 바로 계산

     

    🎯 왜 RTV 방식이 더 무거운가?

    1. 추가 렌더 패스가 생김

    • FogPass()라는 드로우콜이 별도로 하나 더 실행
    • GPU 입장에선 똑같은 해상도로 전 화면 한번 더 돌리는 셈

    2. RTV에 쓰기(write) + **메인 패스에서 읽기(read)**가 둘 다 발생

    • Fog Pass에서 FogRTV에 픽셀 쓰고,
    • Main Pass에서 다시 FogRTV를 샘플링함

    ✔ 이건 GPU 메모리 I/O (bandwidth) 비용이 들어


    3. 캐시 미스와 텍스처 리소스 바인딩

    • 렌더 타겟 교체 (OMSetRenderTargets)는 비용이 작지만 무시 못 할 수준
    • FogRTV는 텍스처로 쓰기 → 텍스처로 읽기까지 GPU 캐시 바뀌는 타이밍이 발생
      • 이걸 GPU는 리소스 상태 전환(resource barrier)이라고 부르지

    4. 동일한 안개 수식도 샘플링 방식으로 바꾸면 간접 비용 발생

    // 메인 패스 인라인
    float fogFactor = exp(-density * depth);
    
    // RTV 방식
    float fogFactor = tex2D(FogRTV, uv).r;

     

    • 똑같은 수식이라도 샘플링 비용이 있고, 텍스처 필터링 비용도 따름
    • 압축된 포맷일 경우 디코딩 비용까지 생김

    ✅ 실제 비용 차이 어느 정도?

    대부분의 경우, 인라인 계산 방식이 1.5~3배 더 빠르다고 보면 돼
    (단순한 안개만 있을 경우)

    • Exponential 수식 1줄을 픽셀 셰이더에 넣는 건 거의 공짜 수준
    • 반면 RTV 방식은 Fullscreen Quad 드로우 + 텍스처 샘플링 → 확실히 더 무거움

     

    안개 RTV를 PostProcessing에서 사용하는 경우에는 안개 RTV를 만드는것이 큰 도움!

    📦 정리: 안개 RTV가 필요한 경우 요약표

    후처리에서 안개 반영 Tonemap, Bloom, LUT 등 색상/밝기 조절
    반사 처리에 안개 적용 SSR, Planar Reflection 반사 씬에도 안개 있어야 함
    UI 요소와 자연스럽게 섞기 미니맵, 투명 HUD 등 깊이감 있는 UI 효과
    Volumetric Light/God Rays 빛의 퍼짐 연산 시 필요 빛 감쇠량 조정
    DOF, MotionBlur에서 연산 보정 흐림 강도 조정 거리감 보정
    Deferred Lighting에서 조명 감쇠 계산 조명도 안개 통과함 정확한 조명 표현
    Tonemap HDR → LDR 보정 밝기/대비 조정
    Bloom 밝은 영역을 번지게 하이라이트 강조
    LUT 색상 톤 전체 조정 감성/무드 연출

     

     

    언리얼에서 Exponential Height Fog, 기본 산란(Basic Inscattering) 안개(Fog)를 구현은 어떻게 되어있나?

    Unreal의 BasePassPixelShader.usf 또는 DeferredShadingCommon.ush 안에 보면
    ApplyHeightFog() 같은 함수로 처리되고 있어.

    float FogFactor = ComputeHeightFog(ViewSpacePos, FogParams);
    FinalColor = lerp(FogColor, LitColor, FogFactor);

     

    postprocessing에서 안개 정보를 사용해야할때도, exponential height fog나 basic inscattering fog와 같이 간단한 안개는 

     

    • 계산이 너무 단순하다
    • 픽셀당 viewDepth or worldPos.y로 다시 계산 가능하다
    • 후처리에서 이 값을 쓰고 싶으면 그냥 다시 계산하면 됨

    ✅ 정리: RTV가 "필수"인 건 언제인가?

    Exponential Fog ❌ 필요 없음 수식 간단, 재계산 가능
    Height Fog ❌ 필요 없음 마찬가지
    Volumetric Fog ✅ 필요 계산 무거움, 공유 필요
    포스트 효과에서 반복 사용 상황에 따라 ✅ SSR, DOF, Bloom 등에서 재사용
    씬에서 다른 Pass가 참조해야 할 경우 ✅ 필요 멀티 Pass 공유
    모바일에서 Fog Prepass로 처리할 때 ✅ 조건부 필요 퍼포먼스 최적화 목적

     

     

     

    'UE5' 카테고리의 다른 글

    [week8] Delegate  (3) 2025.04.25
    [week6] Light  (1) 2025.04.12
    [week2] Texture Rendering  (1) 2025.03.20
    [UE5] Development Editor에서 효과적으로 디버깅하는 방법  (1) 2025.01.17
    [CG] 레스터라이저 상태(Rasterizer State)  (1) 2025.01.15
Designed by Tistory.