완전 처음이냐 하면 아니긴 하지만 이걸 갖고 뭘 만들어 본적은 없었기 때문에 그냥 처음이라고 봐도 무방할 것 같다.
우선 5.6버전이 최신이길래 다운로드 했다.
GUI가 Unity 스러워져서 매우 만족스럽다. 이상한 볼륨 들어간 예전 꺼보다 이렇게 심플한 검은 톤 화면이 마음에 든다.
일단 이 상황에서 내가 뭘해야 엔진을 빠르게 익힐 수 있을까?
뭘 하든 언제나 시작은 똑같다. 이걸 가지고 만들고 싶은 목표를 하나 정하고, 이 목표를 향해 망망대해를 헤엄치는 것. 그렇다면 Unreal Engine의 매우 좋은 간접광 품질(루멘같은거)을 응용해서 카툰 캐릭터와 고급진 배경의 조합으로 달성되는 렌더링을 목표로 해보자.
자, 그러면 거두절미하고 카툰 렌더링을 하려면 어떻게 해야하나?
이미 배경지식이 약간은 있어서 커스텀 라이팅 모델을 정의하고 싶긴 한데, 이러면 소스 코드 수정을 해야 한다고 해서 소스코드 다운 받고 빌드하는 시간을 생각하면 뭔가 시작하기에 애매한 느낌이 든다. 일단 유튜브 검색해서 나오는 여러 가지를 우선적으로 시험해보도록 하자.
https://www.youtube.com/watch?v=mzydOmgN7mc
여기 꺼를 보고 한번 따라해봤다. 간단하게 화면 색상을 흑백화 하는 PP인데, 볼륨에 postprocessing material을 등록함으로써 동작할 수 있게 구조가 되어 있다.
이런건 Unity에 비하면 아주 천국이구만. 패스를 추가하는 게 매우 간단하다. 하지만 결국 인젝션 포인트는 제한적이라서..
DOF 전, DOF 이후, 등등 나오는데, 그러고 보니 반투명을 언리얼 엔진에서 어떻게 처리하는 거지? 분명히 반투명은 Forward Rendering같은걸로 처리해야 할텐데?
흠.. 일단 여러 PP Material을 중첩시키는건 간단하게 동작하는 듯. 인젝션 포인트만 일치시키면 되는 것 같다.
따로 텍스처를 정의할 필요도 없는 것 같고. PostProcessingInput0만 갖고 하면 이전 결과에 누적이 가능한 것 같다. 실제로 동작이 어떻게 되는지 확인은 어떻게 하지?
흠.. 일단 유니티의 Frame Debugger 같은 기능은 따로 지원하지 않는 것으로 보인다.. 그냥 RenderDoc같은거로 까보는 수 밖에 없나?
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-renderdoc-with-unreal-engine
이거 보고 렌더독의 폴더 패스만 프로젝트 세팅에 등록했고, 에디터 재시작하니 버튼이 다음과 같이 생긴 것을 확인했다.
그대로 눌러서 캡처해보자.
캡처는 되긴 하는데 렉이 겁나 걸린다. 뭘 하기가 어려울 정도;;
버전 문젠가? 5.6이랑 RenderDoc 1.39가 뭔가 아다리가 안맞아서 생긴건가.. 렉 겁나걸리고 뭐 확인할 것도 없다. 그냥 호출 순서만 보인다.
일단 여러가지 키워드는 주웠다. Nanite, Lumen, HZB, Vitual Shadow, SkyAtomsphere, Stochastic Lighting, TSR, VT, Volumetric RenderTarget, RDG, ... 뭐 이런 것들이 열심히 돌고 있다. 앞으로 알아가야 하는 것들이겠지. 대충 뭐겠거니 알겠는것도 있지만 Nanite만 해도 내부적으로 엄청나게 명령이 많이 돌고 있다.
일단 RenderDoc으로 정상적으로 데이터 확인할 수 있도록 만드는 게 우선일 듯 하다.
RHI를 DirectX11로 변경하고 캡처하니까 정상적으로 캡처가 되었다. 일단 DirectX12보다 보기 편하기도 하고.
쭉 내리다가 포스트 프로세싱쪽이 있어서 보았다.
Scene Color 인풋으로 뭐가 들어왔나 궁금해서 찾아보니 PostDOFTranslucency.ScenColor라는 이름의 텍스처 리소스가 들어온다. 이 텍스처는 RenderTargetView 1개와 ShaderResourceView 2개로 이루어져 있다.
즉 Post Processing 패스가 실행되면 Compose TranslucenyToNewSceneColor 패스를 통해서 먼저 화면을 다 복사하고, 이 복사한 녀석을 PP_Toon이라고 내가 만든 material에 input으로 넣고, PostProcessMaterial이라는 이름으로 생성한 텍스처를 RenderTarget으로 두고 쓴다.
PostProcessing에 쓰는 텍스처 포맷은 HDR 데이터를 담고 있어서 그런가 죄다 R11G11B10 포맷이다. 나중에 쓸모있을 수 있어서 기록한다..
기존에 복사했던 텍스처가 아니라 완전히 새로운 텍스처인 듯 하다.
이 RenderTarget 결과를 다시 Shader Input으로 놓는데, PP_2라고 이름지은 2번째 Material에서 쓰는 RenderTarget은 또 다른 PostProcessingMaterial이라는 이름의 텍스처이다.
설마 PostProcessMaterial 개수만큼 RenderTarget이 새로 생긴다는 건가? 이건 좀 큰일인데.. 생각보다 렌더텍스처 재활용을 잘 안하는 것 같다. 그때그때 새로 생성하는 거보니.. 아니면 에디터라서 각 패스 결과를 저장하려고 이렇게 한건가? 실제로 런타임은 다르려나?
이후 DownSample 패스의 인풋으로 들어간다. RenderTargetView 눌러보니 BloomX라고 뜨던데, Bloom에서 사용하는 데이터인가보다.
이후 Local Exposure라는 패스가 실행된다. 무슨 알고리즘으로 계산되는지는 모른다. 하지만 ShaderResource로 BloomX, 즉 이전에 DownSample 했던 텍스처를 집어넣는다.
CS로 텍스처 색상 변한거 가지고 얼마나 밝기 차이 나나 뭐 이런거 계산하나보다. 노출 값 계산하는거니까 다운 샘플된 텍스처를 써서 최적화를 한거고. 어쨋든 현재 PostProcessing은 앞에서 처리한 결과를 받아서 다음에 처리하고 이거의 반복이다. 다행히 이해하기 쉬운 흐름.
이후 BloomX 텍스처는 Bloom 계산을 위한 DownSampling 을 한다.
이 다음부터는 내가 모르는 영역이라 그냥 기록만 일단 한다.
BloomY(화면의 1/4 크기의 다운 샘플된 텍스처)를 넣어서 더 작은 텍스처에 값을 쓰고, 그다음 가우시안 블러를 하고 있다. Local Exposure를 잘 몰라서 뭐하는 건지 감도 안잡힌다.
그후 Bloom Setup 패스에 EyeAdaption이랑 LocalExposure를 다 구겨넣는데, Bloom 계산할 때 필요한 것 같다... 노출 개념이 Bloom에도 적용이 되나보다..
이후 BloomSetUp 텍스처를 다시 놓고 돌려서 Bloom을 최종적으로 처리한다.
Bloom 이후에는 ToneMapping을 하고, FXAA를 돌리고, 그 후에 SceneView GUI 같은걸 붙인 후에, Upscale을 해서 마무리한다.
흠... 이렇게 보면 생각보다 간단한 것 같기도 하고..
Scene에 반투명까지 다 그린 후에 이걸 한번 복제하고, 이 복제한 거에다가 사용자 정의 PP를 먹이고, 그 후에 LocalExposure, EyeAdaption 등을 계산해서 Bloom 처리를 하고, 이후 톤매핑을 하고 Upscale을 해서 최종 마무리를 한다. 대충 이런 흐름이다. 내가 PostProcessing에 뭔가 더 얹으면 달라질 것 같기도 한다. 일단은 바닐라 상태에서 측정한 바로는 이렇다.
물로 그 이후 또 기괴한 패스가 있긴 하다.
Post Processing 이후에는 VirtualTextureUpdate, ExtractUniformBuffer라는 패스를 실행한다.
Post Processing 전에는 Scene이라는 항목 내에서 다음과 같이 이것저것 패스를 실행한다. 정말 간단한 맥락으로는 Unity랑 동일하게 Opaque 그리고 Transparent 그리고 끝이긴 하다만... 항목이 아무래도 좀 많다.
이 항목 안에도 뭐가 또 이것저것 들어있다. SSAO라던지... 일단 큰 흐름만 파악하는 목적으로 보면 대충 GBuffer 그리고 Lighting 하고 이후 Sky 그리고 반투명 그린다.
처음부터 흐름을 그러면 이해해보자.
우선 CPU에서 값을 받아서 업데이트를 이것저것 반복한다. Map-Unmap이 반복적으로 실행된다.
이후 Raytracing과 Skinning Batch 업데이트 어쩌구를 하는데, Raytracing을 Skinned Mesh에 하려면 Vertex Buffer 자체에 값을 저장해야 하는 걸로 알고 있어서 Raytracing 한 김에 Skinned Mesh Update 관련 정보를 처리하는 건가 싶기도 하다.
근데 비어있어서 그냥 더미 패스인듯. 아마 DirectX11로 해서 DXR를 못쓰니까 Raytracing이 안돌아서 생긴 현상 같다.
그 후로 또 뭘 꼼지락꼼지락 업데이트하는데, Culling, Light, Fog 이런거 나오는 거 봐서 Unity로 따지면 Culling 처리를 하고 있는 듯 하다.
이후 GPUMessageManager.MessageBuffer라는 걸 날리는 작업 후 Update DistanceFieldAtlas라는 패스를 또 돌리는데, 이것도 잘 모르겠다. 언리얼 엔진은 Distance Field라는 개념을 엄청 응용하는데, 몇번 써보긴 했지만 아직도 잘 모르는 개념이라...
물체의 경계를 기준으로 내부, 외부에 대해 거리 값을 저장한 데이터가 어떻게 응용될 여지가 있는지 연결 점을 못 찾고 있는 상태이다. 언리얼 엔진을 본격적으로 다룬다면 이 Distance Field를 제대로 이해할 필요가 있겠지.
결국은 Unity랑 크게 보면 비슷한 동작이다.
CPU ->GPU 데이터 전달 - Raytracing - SkinedCache Update - 컬링 - Distance Field 업데이트 - Opaque - Deferred Lighting - Volumetric Cloud - Sky - HeightFog - Translucent - Bloom - ToneMapping - AntiAliasing(FXAA) - Upscale
이런 느낌..?
다만 Lumen, Nanite 지원이 안되는 것 같다. 대충 검색했을 때 API가 DirectX12랑 Vulkan일 때만 지원한다고 한다.