TECH.ART.FLOW.IO

핵] [번역] UE5 포스트 프로세스 머티리얼에서 섀도맵을 처리할 수 있도록 해봤습니다.

jplee 2024. 12. 21. 17:12

역자의 말.

Kafues511 씨가 구현한 내용을 보면 매우 취미적인 구현이라는 생각이 듭니다. 실무에서 굳이 이렇게 할 필요가 없고 애초에 툰 셰이더 자체를 엔진 셰이더 내부에서 구현하는 것이 일반적이기 때문에 툰 셰이더에 셀프쉐도우나 케스트 쉐도우가 없을리 없고요. 머티리얼 에디터에서 Unlit 을 사용해서 툰 셰이더를 구현하는 것은 실무 측면에서 절대 추천하지 않습니다.다만 이런 실험적 구현 기사를 통해 언리얼 셰이더 익스프레션 및 버퍼 엑서스에 대한 정보등을 취득 해 보는 것에는 도움이 되는 기사일 법 하네요.


저자

작업 환경

  • Windows 10
  • Visual Studio 2022
  • Visual Studio Code
  • Unreal Engine 5.3

시작

과거 포스트 프로세스 머티리얼에서 섀도맵을 사용할 수 있도록 개조한 적이 있습니다.

그림자 포스트 이펙트 없음
그림자 포스트 이펙트 있음

제가 개조할 당시 버전은 5.0.3이었습니다.
아시는 분들도 계시겠지만, 5.0.3은 비권장 모드 버전에 속합니다.
언리얼 엔진 4에서 5로의 대규모 업데이트가 초기 단계에 있었고, 수정 커밋이 빈번하게 이루어졌기 때문이죠.필자는 기다릴 수 없어 모드 변경을 해버렸지만요.(엔진 코드의 차이를 보는 것도 꽤나 공부가 되니까요).
그런 불안정한 버전도 과거의 이야기가 된 오늘, 5.2 ~ 5.3 사이에 렌더링 관련해서 꽤 많은 변화가 있었습니다.그 결과, 당시 수정한 부분을 병합해도 동작 불량을 일으키고 있었다.(실제로 움직여보지는 않았지만, 그냥 병합한 정도로는 움직일 수 없을 것 같은 정도의 변경량이 있었다).
최근 개인 프로젝트를 5.2에서 5.3으로 업데이트한 것도 있고, 마침 타이밍이 좋아서 잠깐 5.3을 지원하게 되었습니다.(포스트 이펙트에서 대충 셰이딩을 하고 싶을 때 이 개조가 의외로 유용하게 쓰인다).

리포지토리

최신 버전만 유지 관리하고 있습니다. 과거 버전이 안된다고 하면 기본적으로 모르쇠로 일관하는 스타일입니다.

구현 내용에 관심이 없는 분들을 위해

"구현에 관심 없어, 그냥 움직이면 돼"라고 생각하시는 분들은 github의 릴리즈 기능에서 UnrealEngine-[version]-PPI-ShadowMap.zip을 다운로드하여 WinMerge로 차액을 가져다 쓰세요.디렉토리는 맞춰져 있기 때문에 쉽게 가져올 수 있을 것입니다.

작동 대상(절대 움직인다고는 말하지 않음)

  • 대응
    • Unreal Engine 5.3
    • Shadow Map과 Virtual Shadow Map(이하 'VSM'으로 표기)모두 지원
    • VSM은 동작 확인이 미흡하여 충돌 가능성도 있습니다.
      • 지향성 라이트만 지원
    • 멀티뷰, Scene Capture, Scene Capture Cube 지원
      • 동작 확인이 느슨해서 충돌 가능성도 있음
  • 비지원
    • 포워드 렌더링
    • 모바일 렌더링
    • 다중 방향성 조명
    • 포인트라이트, 스포트라이트 등
    • Substrate

엔진 개조 (암묵적) 규칙

엔진 개조를 한 부분에는 댓글을 남깁니다.

아래는 필자가 자주 사용하는 형식에 대한 설명입니다.이 포맷을 따를 필요는 없지만, 반드시 댓글만 남겨주시기 바랍니다.작성하지 않으면 인수인계할 때 화를 낼 것 같습니다.(작성해도 엔진 개조는 화를 내는 수준.매번 과거의 나에게 화를 낸다).

기존 구현에 추가로 작성하는 경우에는 지시문으로 원본 코드를 남겨두도록 하고 있습니다.남겨두면 엔진 버전업 시 WinMerge하는 부담을 상당히 줄일 수 있다.

//// ReinCarnation @kobayashi-arata 2023/12/27 ////
#if 0
    RenderLights(GraphBuilder, SceneTextures, TranslucencyLightingVolumeTextures, LightingChannelsTexture, SortedLightSet);
#else
    RenderLights(GraphBuilder, SceneTextures, TranslucencyLightingVolumeTextures, LightingChannelsTexture, SortedLightSet, &ShadowProjectionResourceMap);
#endif
//// ReinCarnation @kobayashi-arata 2023/12/27 ////

완전히 새롭게 구현, 삽입하는 경우에는 지시문이 필요하지 않습니다.하지만 코멘트는 남겨두자.

//// ReinCarnation @kobayashi-arata 2023/12/27 ////
#include "ShadowRendering.h"
//// ReinCarnation @kobayashi-arata 2023/12/27 ////

구현

 

지난번에는 복수 뷰를 지원하지 않았는데, 이번에는 해볼까요?공부 삼아. 구현은 github의 URL을 첨부했지만, 사이트 부하가 심하므로 Releases에서 다운로드하여 로컬에서 열람하시기 바랍니다.줄 수에 대한 주석도 달아놨으니 특별히 불편하지 않을 것 같습니다.(애초에 엔진 개조라는 행위 자체가 불편함 그 자체입니다.)

Engine/Source/Runtime/Renderer/Private/ShadowRendering.h

그림자 함수 내에서 생성한 ShadowMap 텍스처 리소스의 복사본을 저장하기 위한 구조체를 정의하고 있습니다.

FShadowProjectionPassResourcesMap은 TInlineAllocator<4>로 확보하기 때문에 최대 4개의 뷰는 동적 메모리 확보가 생략된다.반투명하게 구현을 유용했을 뿐이네요.

구조체명 설명
FShadowProjectionPassResources 섀도맵 리소스 및 뷰 크기, 가상 섀도맵(VSM, Virtual Shadow Map) 매개 변수
FShadowProjectionPassResourcesMap FShadowProjectionPassResources를 Views만큼 보유하고 있다.
FShadowProjectionViewResourcesMap 포스트 프로세스 함수에 전달할 때 const 수식어를 붙이고 싶은 것.

github에서 구현 보기(L1794-L1863)

Engine/Source/Runtime/Renderer/Private/ShadowRendering.cpp

ShadowMap의 ShadowMap을 복사하고 있습니다(웃음).

Virtual Shadow Map의 ShadowMap, Shadow Map의 ShadowMap, 틀린 말은 아닌데, VSM이 등장하면서 구분을 하면 이런 식으로 쓰게 되네요.위화감이 심하네요.역시 웃음이 나온다.

github 에서 구현 보기(L2020-L2027)
github 에서 구현 보기(L2031-L2034)
github 에서 구현 보기(L2082-L2084)
github 에서 구현 보기(L2089-L2091)
github 에서 구현 보기(L2097-L2109)

그림자 함수에 FShadowProjectionPassResourcesMap 인수를 추가하고 있습니다.

github 에서 구현 보기(L2119-L2126)
github 에서 구현 보기(L2170-L2177)

ShadowMap과 VSM 호출에 FShadowProjectionPassResourcesMap 인수를 추가하고 있습니다.

github 에서 구현 보기(L2334-L2341)
github 에서 구현 보기(L2353-L2361)

모바일은 비지원이므로 nullptr을 전달하고 있습니다.

github 에서 구현 보기(L2480-L2487)

Engine/Source/Runtime/Renderer/Private/SceneRendering.h

ShadowMap의 함수에 FShadowProjectionPassResourcesMap 인수를 추가하고 있습니다.

github 에서 구현 보기(L91-93)
github 에서 구현 보기(L2305-2312)
github 에서 구현 보기(L2321-2328)

Engine/Source/Runtime/Renderer/Private/LightGridInjection.cpp

포워드 렌더링은 본 개조 비지원이므로 nullptr을 지정합니다. 기본 인수로 지정하는 형태로도 문제 없습니다. 글에서 언급하고 싶었고, 버전업 시 본 함수의 사용처가 늘어났을 때, 에러를 출력하여 추가된 위치를 파악하고 싶어서 지정하지 않았을 뿐입니다.

githubで実装を見る(L1028-L1035)

Engine/Source/Runtime/Renderer/Private/LightRendering.cpp

그림자 함수에 FShadowProjectionPassResourcesMap 인수를 추가하고 있습니다.

github 에서 구현보기(L1224-L1231)
github 에서 구현보기(L1932-L1938)

Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.h

FShadowProjectionPassResourcesMap은 앞으로 선언한다.

5.2부터 렌더링 관련 클래스나 구조체는 정의체계 파일로 묶어 헤더에 포함시키는 것을 줄이는 움직임이 보인다.이 정도면 솔루션이 너무 커서 물 건너간 것 같기도 하지만, 에픽 나름대로 유니티에 비해 압도적으로 떨어지는 빌드 시간을 개선하기 위해 노력하는 것 같습니다.

github 에서 구현 보기(L63-L65)

라이팅과 섀도우 함수에 FShadowProjectionPassResourcesMap 인수를 추가합니다.

github 에서 구현 보기(L828-L835)
github 에서 구현 보기(L922-L929)

Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp

FShadowProjectionPassResourcesMap을 선언하고 있습니다.

라이트 함수의 인수로 전달하여 생성 및 복사한 섀도맵과 파라미터를 가져옵니다.

github 에서 구현 보기(L3864-L3866)
github 에서 구현 보기(L3910-L3916)

포스트 프로세스 함수에서 처리할 수 있도록 FPostProcessingInputs 구조체에 설정합니다.

github 에서 구현 보기(L4414-L4416)
github 에서 구현 보기(L4429-L4431)

Engine/Source/Runtime/Renderer/Private/Shadows/ShadowSceneRenderer.h

VSM의 함수에 FShadowProjectionPassResourcesMap 인수를 추가하고 있습니다.

github 에서 구현 보기(L19-L21)
github 에서 구현 보기(L81-L88)

Engine/Source/Runtime/Renderer/Private/Shadows/ShadowSceneRenderer.cpp

5.0.3에서 미지원되었던 VSM을 지원합니다. 주의할 점은 VSM은 적용하는 최적화에 따라 섀도맵 ScreenShadowMaskMaskTexture가 생성되지 않습니다.이 경우 복사본이 존재하지 않아 크래시가 발생할 수 있습니다.필자가 VSM을 자주 플레이하지 않기 때문에 다소 미흡한 대응입니다.

github 에서 구현 보기(L459-L466)
github 에서 구현 보기(L592-L617)

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.h

포스트 프로세스 함수의 인수인 FPostProcessingInputs에 FShadowProjectionViewResourcesMap을 추가합니다. 인클루드 크기를 줄이고 싶다면 포인터로 바꿔도 좋을 것이다. TranslucentRendering.h가 발리바리에 포함되어 있었기 때문에 필자도 신경 쓰지 않고 ShadowRendering.h를 포함시켰다.중간에 Validate 함수로 사용한 것이 잘못되었나 봅니다.조만간 개편될 것 같은 예감.

github 에서 구현 보기(L9-L11)
github 에서 구현 보기(L48-L51)
github 에서 구현 보기(L57-L59)

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp

포스트 프로세스 함수에서 포스트 프로세스 머티리얼 함수를 호출할 때 필요한 리소스 파라미터를 FPostProcessMaterialInputs에 다시 저장하고 있네요.이런 부분들이 의외로 꼼꼼하게 구현되어 있네요.솔직히 FPostProcessingInputs를 전달해도 될 것 같은데... 에픽의 기분에 따라 달라지는 것 같아요.

github 에서 구현 보기(L312-L314)
github 에서 구현 보기(L523-L524)

Engine/Source/Runtime/Renderer/Public/PostProcess/PostProcessMaterialInputs.h

포인터를 붙인 이유는 앞으로의 선언에 사용하기 위함입니다.

github 에서 구현 보기(L9-L11)
github 에서 구현 보기(L130-L132)

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessMaterial.h

포스트 프로세스 머티리얼의 셰이더 파라미터에 VSM의 파라미터인 DistanceFadeMAD와 섀도맵 텍스처와 샘플러의 LightAttenuationTexture, Sampler를 추가했습니다.

언리얼 엔진이 자동으로 정렬을 맞춰주기 때문에 16의 배수를 의식하지 않고 정의할 수 있다는 점이 좋았어요.

github 에서 구현 보기(L35-L39)

Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessMaterial.cpp

포스트 프로세스 머티리얼 파라미터에 생성 및 복사한 섀도맵 리소스를 설정합니다.

생성/복사한 섀도맵 리소스가 존재하지 않는 경우, 더미 White 텍스처를 설정하여 그림자가 없도록 하고 있습니다. 전체 그림자를 만들고 싶다면 Black으로 설정하면 됩니다. (그런 경우는 없을 것 같습니다.)

github 에서 구현보기(L559-L561)
github 에서 구현보기(L565-L567)
github 에서 구현보기(L629-L642)

Engine/Shaders/Private/Common.ush

DistanceFadeMAD를 셰이더 쪽에도 정의합니다.

github 에서 구현보기(L796-L798)

Engine/Content/Functions/MF_LightAttenuationFromShadow.uasset

포스트 이펙트에서 사용되는 가상의 머티리얼 함수입니다. 엔진 콘텐츠에 적당히 배치했지만, 위치는 어디든 상관없습니다. 원하는 곳에 배치하시면 됩니다.

커스텀 노드의 코드는 EULA에 위배되기 때문에 여기서는 공개할 수 없습니다. 로컬에서 확인하시기 바랍니다.

동작 확인

MF_LightAttenuationFromShadow를 사용한 포스트 프로세스 머티리얼을 만들어 Unlit에 그림자를 드리우고, SceneCapture의 캡처 결과를 Plane 메시에 부착한 머티리얼에 투영해 봅니다.

포스트프로세스 소재

 

포스트 이펙트 없음 (옷은 DefaultLit)

 

포스트 이펙트 있음

메인뷰와 SceneCapture에서 모두 포스트 이펙트가 적용된 것을 확인할 수 있었습니다.
혹시 동작이 안 되는 부분이 있으면 댓글로 알려주세요. 기분 내키는 대로 수정하겠습니다.

귀엽고 너무 좋네요.

VSM Unlit 마스크

VSM에서는 Unlit에 그림자가 안 나오는데, 아무래도 VSM 측에서 마스크를 씌운 것 같습니다.

필자는 VSM을 많이 사용하지 않는 사람이라서 대응할 만큼의 관심은 없습니다.

마스크가 씌워져 있습니다.


원문

https://kafues511.jp/2024/10/25/286/

 

【UE5】ポストプロセスマテリアルでシャドウマップを扱えるようにしてみた - kafues511

作業環境 Windows 10 Visual Studio 2022 Visual Studio Code U

kafues511.jp