TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역]레이 트레이싱을 통한 커스텀 카툰 렌더링 프로젝션.

jplee 2024. 4. 2. 15:19

역자의 말.
최근 만들고 있는 MMORPG 테스트 빌드 기간이 2주 동안 이어지고 있기때문에 새로운 토픽을 공유할 기회가 부쩍 줄어들었습니다. 중국과 일본이 카툰렌더링을 사용한 서브컬처 게임 및 그와 관련 된 콘텐트를 주도하고 있는 추세라고 할 수 있습니다. 쿠로 게임즈의 명조는 물론이고 상해 소재의 게임 스타트업 등에서도 명일방주 이후 차기작을 준비 중이고 그 외에 넷이즈 광저우 사업부와 텐센트 심천의 모팡 사업부 등에서도 꾸준히 그것과 관련 된 프로젝트가 연이어 만들어지고 있고 출시 대기 중에 있지요. 우리에게는 원신과 붕괴 프렌차이즈 그리고 스타레일 스핀오프 등으로 유명한 미호요가 더 익숙 할 것입니다. 아무튼 중국의 이러한 기술적 고심등에 대해서 단순히 번역 된 토픽을 살펴 보더라도 배울 것이 있다면 배우고 그 위에 또 새로운 벽돌을 쌓을 수 있길 바랍니다.


 
 
이전 글의 많은 사진들이 그후 자동압축으로 인해 매우 흐릿하고 코드 사진 중 일부는 전혀 보이지 않는 것을 발견했습니다. 앞으로 기사를 작성할 모든 마크 다운 파일과 모든 사진은 다음 github 저장소로 이동해야하는 경우 github에 업로드 할 예정입니다:

GitHub - Yu-ki016/Yu-ki016-Articles: Yu-ki016的文章仓库

Yu-ki016的文章仓库. Contribute to Yu-ki016/Yu-ki016-Articles development by creating an account on GitHub.

github.com

또한 이 문서의 변경 사항은 아래의 커밋 로그에 해당하는 github에 업로드됩니다:

I. 머리말

1.1 배경 

이전 글에서 툰 렌더링을 위한 커스텀 데이터를 작성할 수 있도록 몇 가지 툰버퍼를 추가했지만, 렌더링 기능을 작성하기 전에 여전히 해결하기 어려운 문제가 하나 있는데, 바로 캐릭터 얼굴의 셀프 섀도잉 문제입니다:

캐릭터 얼굴의 셀프 셰이딩

또한, 예를 들어 투영을 좀 더 정교하게 제어하고 싶습니다:

- 캐릭터 A의 모든 부분이 셀프 섀도잉을 허용하거나 허용하지 않도록 선택할 수 있음
- 캐릭터 A의 모든 부분이 캐릭터 A에 투영할지 여부를 선택할 수 있음(예: 얼굴에 머리카락 투영을 켜거나 끌 수 있음)
- 포인트 1과 2에서 투영이 꺼져도 캐릭터 A는 여전히 캐릭터 B에 투영될 수 있음(A에 대해 꺼진 후에도 머리카락은 여전히 B에 투영될 수 있음).

1.2 셀프 섀도잉 처리하기 

셀프 섀도잉을 처리하는 방법은 여러 가지가 있는데, 가장 간단하고 잔인한 방법은 얼굴에 투영을 허용하지 않는 것입니다. 하지만 개인적으로 저는 캐릭터의 얼굴에 머리카락, 나뭇잎 등이 투영되는 것을 보고 싶지만 이 방법은 절대 사용하지 않을 것입니다. 하지만 개인적으로 캐릭터의 얼굴에 머리카락, 나뭇잎 등이 투영되는 것을 보고 싶지만 이런 방식은 절대 사용하지 않을 것입니다.

"내일의 소스를 위한 선원의 옷

얼굴이 그림자 맵의 깊이를 비교할 때 오프셋을 추가하여 자체 그림자를 제거하는 간단하고 구현하기 쉬운 방법도 있지만, 물론 머리카락 투영과 같이 얼굴에 가까운 물체의 투영도 차단되어 다소 문제가 있습니다. 하지만 머리카락 투영은 다른 방법(스크린 스페이스 접근 방식 등)으로 할 수 있으며 이러한 접근 방식이 불가능한 것은 아닙니다.

YivanLee의 공유에서는 ShadowProxy가 사용되었지만 그가 어떤 종류의 프록시를 사용하고 있는지 모르겠지만 제 겸손한 이해로는이 접근 방식이 어느 정도 문제가 될 것입니다.

https://www.bilibili.com/video/BV13K4y1r7Fm
그 외에는 두 개의 섀도뎁스를 렌더링하거나 오브젝트별 섀도를 사용할 수 있지만 구현하기가 약간 더 번거롭고, 셀프 섀도를 제거하기 위해 추가 섀도뎁스를 렌더링하는 것은 개인적으로 충분히 우아하지 않을 것 같습니다.

https://www.bilibili.com/video/BV1yy421v75V
또한 1.1에서 언급한 세 가지 사항과 같이 투영을 좀 더 정교하게 제어하려고 하면 할수록 섀도우맵의 한계가 분명해졌습니다. 따라서 제가 구상한 효과를 구현하기 위해 레이트레이싱 섀도를 수정하여 카투슈 프로젝션의 제어를 구현해 보려고 합니다.

II. 실시간 레이트레이싱 기초

페이로드, 애니 힛 셰이더 등의 소스 코드를 보았을 때 변경하려는 의도의 시작은 완전히 흐릿하고 많은 사람들이 래스터 화 프로세스에 더 익숙하고 레이 트레이싱 프로세스를 잘 이해하지 못한다고 생각하므로 먼저 레이 트레이싱의 기본 사항을 조금 추가하겠습니다.

이 기사에서의 레이트레이싱에 대한 소개는 얕은 맛일 뿐이며, 그 분야에 대해 더 자세히 알고 싶다면 매우 자세한 따거님의 기사를 보시는 것을 추천 합니다.
https://www.cnblogs.com/timlly/p/16687324.html

剖析虚幻渲染体系(17)- 实时光线追踪 - 0向往0 - 博客园

17.1 本篇概述 17.1.1 本篇内容 UE的光线追踪一直是童鞋们呼吁比较高的一篇,虽然多年前博主已经在探究光线追踪技术及UE4的实现阐述过,但内容较基础和片面。那么,此篇就针对UE的实时光线追

www.cnblogs.com

SigGraph2018의 이 강의도 매우 잘 설명되어 있으며, 아래 이미지 중 상당수는 그의 파워포인트에서 발췌한 것입니다:
https://intro-to-dxr.cwyman.org/

https://intro-to-dxr.cwyman.org/

intro-to-dxr.cwyman.org

2.1 레이트레이싱 관리 라인

먼저, 가장 친숙한 래스터화된 파이프라인인 이 이미지를 살펴보세요:

https://intro-to-dxr.cwyman.org/presentations/IntroDXR_RaytracingShaders.pdf
래스터화 파이프라인과 달리 실시간 레이트레이싱의 파이프라인은 다음과 같습니다:

https://intro-to-dxr.cwyman.org/presentations/IntroDXR_RaytracingShaders.pdf
위 그림의 가속 구조입니다:
- 전체 3D 환경을 GPU 트래버스에 가장 적합한 형식으로 표현합니다. 2단계 계층 구조로 표현되는 이 구조는 GPU에 최적화된 광선 통과와 애플리케이션에 의한 동적 오브젝트의 효율적인 수정을 제공합니다.
그러나 가속 구조에 너무 많은 주의를 기울일 필요는 없으며, 레이트레이싱에서 프로그래밍 가능한 5개의 셰이더에 더 많은 주의를 기울일 필요가 있습니다:
- 레이(Ray) 생성: 레이를 생성하는 데 사용됩니다. 이 셰이더에서 TraceRay()를 호출하여 광선을 재귀적으로 추적할 수 있습니다. 모든 광선 추적(Ray Trace) 작업의 시작점인 호스트에서 시작된 스레드가 있는 간단한 2D 메시가 광선을 추적하여 최종 출력에 씁니다.
- 교차(Intersect): 이 셰이더는 사용자가 교차하는 오브젝트가 특수 메트릭(구, 세분화된 표면 또는 기타 메트릭 유형)인지 감지할 수 있도록 TraceRay() 내에서 광선이 오브젝트와 교차하는 것이 감지될 때 호출됩니다. 내장된 광선 삼각형 교차점과 함께 애플리케이션 정의 튜플을 사용하여 광선 교차를 계산합니다.
- 모든 히트: 사용자가 교차하는 오브젝트가 특수 요소(구, 세분화된 표면 또는 기타 요소 유형)인지 감지할 수 있도록 TraceRay() 내에서 오브젝트와 교차하는 광선이 감지되면 이 셰이더가 호출됩니다. 교차점이 발견되면 호출되며, 여러 교차점을 순서와 상관없이 호출합니다.
- 가장 가까운 히트(Closets Hit) 및 Miss: TraceRay()가 전체 장면을 횡단할 때 광선이 교차하는지 여부에 따라 이 두 셰이더를 호출하며, Cloesit Hit는 재질, 텍스처 찾기, 조명 계산 등과 같은 픽셀 셰이딩 처리를 수행합니다. Cloesit Hit와 Miss 모두 TraceRay()를 재귀적으로 계속 호출할 수 있습니다. 가장 가까운 광선의 교차점에서 호출되며 프로퍼티를 읽고 광선을 추적하여 페이로드를 수정할 수 있고, 찾을 수 없는 경우 호출되어 히트를 수락하고 광선을 추적하고 광선 페이로드를 수정할 수 있는 Miss가 호출됩니다.
위의 설명은 Aspiring Big Brother의 "언리얼 렌더링 시스템 해부 (17) - 리얼타임 레이 트레이싱" 글에서 복사한 것입니다.


위의 소개를 보면 레이트레이싱 파이프라인에 대해 명확하게 이해하지 못할 수 있으므로 자세한 소개가 이어집니다.

다음 그림에서 먼저 레이 제너레이션 셰이더가 TraceRay() 함수를 호출하면 화면의 각 픽셀이 광선을 방출하고 가속 구조의 각 광선은 모든 교차점을 통과하여 궁극적으로 교차점 정보를 레이 제너레이션 셰이더로 다시 가져옵니다.
다음 레이 제너레이션 셰이더입니다. 다음으로 광선 생성 셰이더는 교차 정보를 가져와 화면에 출력을 다시 씁니다.

간단히 말해 광선 생성 셰이더는 레이트레이싱의 입구와 출구라고 할 수 있습니다.

https://intro-to-dxr.cwyman.org/presentations/IntroDXR_RaytracingShaders.pdf

다음으로, 레이트레이싱 파이프라인이 어떻게 교차점을 계산하고 TraceRay() 함수를 호출한 후 RayGeneration 셰이더에 정보를 반환하는지 살펴봅시다.

https://intro-to-dxr.cwyman.org/presentations/IntroDXR_RaytracingShaders.pdf

우선, 광선은 Intersection Shader를 사용하여 오브젝트와 교차점이 있는지 확인합니다;

광선이 오브젝트와 교차할 때 아무 히트 셰이더가 호출되는데, 광선의 경로 아래에 교차점이 두 개 이상 있을 수 있으므로 아무 히트 셰이더는 두 번 이상 호출됩니다. 여기서 한 가지 주의할 점은 각 교차점의 순서는 무작위이므로 아래 그림에서 A 지점이 먼저 호출될지 B 지점이 먼저 호출될지 확실하지 않다는 점입니다.

일반적으로 애니히트 셰이더의 역할은 교차점의 히트 여부가 유효한지 판단하는 것입니다:

점 B가 먼저 호출되고 AnyHit 셰이더가 이 Hit가 유효하다고 판단하면 이 점 B를 가장 가까운 점으로 저장할 수 있으며, 교차점 셰이더는 교차점 A의 거리가 B보다 크다고 판단하여 교차점 A를 바로 버리고 AnyHit 셰이더를 다시 호출하지 않습니다. 
점 B가 먼저 호출되고 다음과 같은 경우 점 B가 먼저 호출되고 점 B가 마스크 머티리얼 속이 빈 곳 또는 투명 머티리얼인 경우 AnyHit Shader는 IgnoreHit() 함수를 호출하여 이 Hit를 폐기하여 가장 가까운 거리가 업데이트되지 않도록 한 다음 점 A에서 AnyHit Shader를 계속 호출할 수 있습니다. 
물론 점 A가 먼저 호출되는 경우도 가능합니다....

https://rtintro.realtimerendering.com/2-GPU.pdf

레이가 씬의 모든 교차점을 통과할 때 유효한 히트(Hit)가 없으면 미스 셰이더를 호출합니다.

레이가 씬의 모든 교차점을 통과할 때 유효한 가장 가까운 적중이 있으면 ClosestHit 셰이더를 호출합니다. 일반적으로 ClosestHit 셰이더는 교차점 거리, 교차점 색상, 노멀 및 기타 필요한 데이터를 레이 생성 셰이더에 반환합니다. 생성 셰이더가 후속 조명 계산을 할 수 있도록 필요한 데이터를 반환합니다.
2.2 페이로드 
위에서 페이로드에 대한 설명은 생략했지만, 페이로드는 레이의 페이로드이며, 사실 사용자가 정의한 구조이며, 2.1 컬러, 노멀 등의 데이터 중 일부가 레이제너레이션 셰이더로 돌아가는 것은 페이로드 리턴을 통해 이루어집니다.

아래는 5가지 셰이더의 정의로, AnyHit/Miss/ClosestHit 셰이더의 입력과 출력은 페이로드를 확인할 수 있습니다.

https://intro-to-dxr.cwyman.org/presentations/IntroDXR_RaytracingShaders.pdf

예를 들어, 다음 코드는 컬러 전용 페이로드를 정의하여 빛이 오브젝트에 닿지 않으면 미스 셰이더가 페이로드에 파란색을 쓰고, 빛이 오브젝트에 닿으면 페이로드에 빨간색을 씁니다.

https://rtintro.realtimerendering.com/2-GPU.pdf

2.3 DXR 내장 기능 

다음은 다이렉트 X 레이트레이싱(DXR)에서 제공하는 몇 가지 내장 기능입니다.

https://intro-to-dxr.cwyman.org/presentations/IntroDXR_RaytracingShaders.pdf

2.4 레이트레이싱 섀도우 

다음으로 레이트레이싱 섀도우가 어떻게 구현되는지 살펴보겠습니다.

https://www.cnblogs.com/timlly/p/16687324.html

- 생성 셰이더는 TraceRay()를 통해 셰이딩 포인트에서 광원 방향으로 광선을 방출합니다. 
- 광선이 어떤 물체에 부딪히지 않으면(유효한 히트도 없음) 픽셀은 광원에 의해 조명됩니다. 
- 광선이 어떤 물체에 부딪히면 픽셀은 그림자가 됩니다. 
위의 정보를 기반으로 셀프 섀도를 제거하려면 AnyHit 셰이더에서 광선이 부딪힌 물체가 자신인지 아닌지를 판단하고 자신인 경우 IgnoreHit()를 호출하여 이 히트를 폐기하면 됩니다. 위의 정보를 바탕으로 셀프 섀도우를 제거하려면 광선에 비친 오브젝트가 AnyHit 셰이더에서 자체인지 아닌지를 판단하고, 자체인 경우 IgnoreHit()을 호출하여 해당 히트를 폐기하면 됩니다. 마찬가지로 셀프 섀도우 이외의 다른 투영을 제외하려면 AnyHit 셰이더에서도 판단을 수행하면 됩니다.

Nsight 인터셉트를 사용한 III 레이트레이싱

막상 파이프라인 수정을 시작하려고 하니 아래와 같이 렌더독이 레이트레이싱을 지원하지 않아 광원에 대한 레이트레이싱 섀도를 설정할 수 없고 활성화할 방법도 없다는 것을 알게 되었습니다.

경로 종속성을 없애고 다른 프레임 커팅 소프트웨어에 대해 알아보는 것이 좋은데, 여기서는 Nsight를 사용하며 다운로드 링크는 다음과 같습니다:
Nsight를 열면 다음과 같은 인터페이스가 나타나며, 왼쪽 상단의 연결 버튼을 클릭하여 언리얼에 연결할 수 있습니다:

활동에서 프레임 디버거를 선택합니다.

애플리케이션 실행 파일에 엔진 경로를 입력합니다:
명령줄 인수에 프로젝트 경로와 -game을 입력합니다.
Nsight를 사용하여 프레임을 가로채는 경우 Eitor 모드에서 시작하면 멈추므로 -game을 추가하면 프로젝트가 다음 모드인 별도의 프로세스로 시작됩니다.

다음으로 프레임을 잘라 UE의 레이트레이싱 섀도를 찾습니다:

프로젝트 세팅에서 레이트레이싱과 레이트레이싱 섀도를 켭니다.

광원 설정에서 빛 추적 그림자가 아직 켜져 있지 않은 경우 수동으로 켭니다.

다음으로 Nsight로 프로젝트를 시작하고 F11을 눌러 프레임을 자릅니다.

Nsight의 왼쪽은 많은 이벤트를 가로채는데, 필터 기능을 사용하여 일부 이벤트를 필터링하면 더 쉽게 찾을 수 있습니다.

이렇게 하면 레이트레이싱 섀도 패스를 빠르게 찾을 수 있습니다.

IV. 레이트레이싱 섀도 수정

4.1 AnyHit 셰이더를 툰 머티리얼에 바인딩하기
섹션 2.4에 따르면 AnyHit  셰이더를 변경하여 셀프 섀도를 제거할 수 있지만, 그 전에 일반적으로 불투명한 오브젝트는 AnyHit  셰이더에 바인딩되지 않는다는 점에 유의해야 합니다.

빛이 불투명 오브젝트와 교차할 때 히트 유효성 및 히트 폐기 여부를 판단할 필요 없이 불투명 머티리얼의 히트만 유효하면 되기 때문에 AnyHit 셰이더를 사용할 필요가 없습니다.

예를 들어, 테스트해 볼 수 있는데, UE 레이트레이싱 섀도에서 AnyHit 셰이더를 사용하는 셰이더는 MaterialAHS의 RayTracingMaterialHitShaders.usf이며, 셰이더 앞에 다음 코드를 추가하여 툰 관련 머티리얼 오브젝트가 투영되지 않도록 만듭니다.

Toon의 투영이 전혀 변경되지 않았음을 알 수 있습니다.

UE 는 Intersection, AnyHit, ClosestHit 셰이더를 HitGroup 이라는 그룹으로 결합합니다.

일반적인 불투명 오브젝트에 사용되는 히트 그룹은 이 FOpaqueShadowHitGroup이며, ClosestHit 셰이더만 설정하는 것을 확인할 수 있습니다:

RayTracingMaterialHitShaders.cpp

가장 가까운 히트 셰이더는 매우 간단하며, 레이 히트 포인트의 거리를 페이로드에 기록하기만 하면 됩니다.

RayTracingMaterialDefaultHitShaders.usf

이 경우 각 머티리얼에 아무 히트 셰이더를 바인딩할 필요 없이 간단한 클로저스트 히트 셰이더만 사용하면 됩니다.

씬에 마스크 머티리얼이나 투명 머티리얼과 같은 특수 머티리얼이 있는 경우 조건에 따라 TMaterialCHS를 사용하여 ClosestHit 셰이더, AnyHit 셰이더 또는 Intersection 셰이더를 바인딩합니다:

RayTracingMaterialHitShaders.cpp

UE 가 오브젝트에 대해 FOpaqueShadowHitGroup 또는 TMaterialCHS 를 바인딩하는 방법은 구체적으로 FDeferredShadingSceneRenderer::CreateRayTracingMaterialPipeline() 을 살펴보면, 언리얼이 다음과 같이 하고 있음을 알 수 있습니다. 메시 커맨드의 bOpaque 프로퍼티를 사용하여 이를 결정합니다:

RayTracingMaterialHitShaders.cpp

따라서 SetupRayTracingMeshCommandMaskAndStatus()에서 툰 셰이딩 모델의 메시 커맨드를 사용하고 그 bOpaque를 false로 설정합니다.

RayTracingInstanceMask.cpp

또한, 툰 셰이딩 모델을 사용할 때는 GetMaterialHitShader 함수의 UseAnyHitShader와 TMaterialCHS의 bWantAnyHitShader를 true로 설정해야 합니다.

RayTracingMaterialHitShaders.cpp

 

RayTracingMaterialHitShaders.cpp

 
그러면 애니히트 셰이더가 툰 머티리얼에 성공적으로 바인딩되었음을 알 수 있습니다:

4.2 툰버퍼 수정하기
다음으로 레이트레이싱 섀도 수정을 준비하기 위해 툰버퍼에 몇 가지 추가 데이터를 써야 합니다.

레이트레이싱 섀도를 위해 준비한 데이터는 다음과 같습니다:

 
명칭타입렌더타겟비트
SelfIDuintToonBufferA.r8
ObjectIDuintToonBufferA.g8
ToonModeluintToonBufferA.b3
ShadowCastFlaguintToonBufferA.b5

그 의미에 대해 조금 더 자세히 알아보세요.

SelfID는 실제로 3D 프로세스에서 동일한 모델의 다른 부분을 구분하기 위해 매우 일반적으로 사용되는 ID 맵입니다.

ObjectID는 액터를 구분하는 데 사용하는 것으로, 각 액터에 대해 임의의 숫자를 작성하여 ObjectID로 사용합니다.

ToonModel은 서로 다른 Toon ShadingModel을 구별하기 위해 사용되는데, 처음에는 Toon과 ToonFace 두 개의 ShadingModel을 정의했지만 나중에 Toon 관련 ShadingModel이 점점 더 많아질 것 같아서 ShadingModel을 하나만 사용한 다음 ShadingModel을 하나만 사용하고 이 3Bit ToonModel을 사용하여 구분하는 것이 좋습니다.

ShadowCastFlag는 다양한 캐스팅 상황을 정의하는 데 사용됩니다.

Engine/Shaders/Private/Toon/ToonShadingCommon.ush를 열고 FToonBuffer 구조체를 추가한 다음 EncodeUintToFloat() 같은 몇 가지 함수를 정의합니다.

#define TOONMODEL_DEFAULT           0  
#define TOONMODEL_FACE              1  
#define TOONMODEL_HAIR              2  
#define TOONMODEL_EYE               3  
#define TOONMODEL_SKIN              4  
// SCF表示ShadowCastFlag  
#define SCF_DEFAULT                  0x00  
#define SCF_DISABLEONSELF            0x01  
#define SCF_DISABLEONFACE            0x02  
#define SCF_DISABLEONEYE             0x04  
#define SCF_DISABLEONTOON            0x08  
#define SCF_DISABLEALLTOON           0x10  
  
struct FToonBuffer  
{  
    // -----------------------------------ToonBufferA-------------------------------  
    uint SelfID; // ToonBufferA.r (8)  
    uint ObjectID; // ToonBufferA.g (8)  
    uint ToonModel; // ToonBufferA.b (3)  
    uint ShadowCastFlag; // ToonBufferA.b (5)  
};

uint GetBitsMaxValue(int Bits = 8) {return (1L << Bits) - 1;}  
  
float EncodeUintToFloat(uint Src, int BitSrc = 8)  
{  
    return float(Src) / float(GetBitsMaxValue(BitSrc));  
}  
  
uint EncodeFloatToUint(float Src1, int BitsSrc1 = 8)  
{  
    return saturate(Src1) * GetBitsMaxValue(BitsSrc1);  
}  
  
uint4 EncodeFloatToUint(float4 Src1, int BitsSrc1 = 8)  
{  
    return saturate(Src1) * GetBitsMaxValue(BitsSrc1);  
}

다음으로, 머티리얼을 수정하여 필요한 데이터를 ToonBuffer에 쓰도록 합니다.

사용자 정의 노드 내부의 코드:

uint Out1 = 0;

    Out1 |= DisableSelfShadow   > 0.5f ? SCF_DISABLEONSELF  : 0;
    Out1 |= ShadowCastFlag.r    > 0.5f ? SCF_DISABLEONFACE  : 0;
    Out1 |= ShadowCastFlag.g    > 0.5f ? SCF_DISABLEONEYE   : 0;
    Out1 |= ShadowCastFlag.b    > 0.5f ? SCF_DISABLEONTOON  : 0;
    Out1 |= ShadowCastFlag.a    > 0.5f ? SCF_DISABLEALLTOON : 0;
    Out1 |= EncodeFloatToUint(ToonModel/7, 3) << 5;
    
    ToonBufferA_R = EncodeFloatToUint(SelfID);
    ToonBufferA_G = EncodeFloatToUint(ObjectID);
    ToonBufferA_B = Out1;
    return 1;

커스텀 노드가 위에서 정의한 함수를 사용할 수 있도록 DeferredShadingCommon.ush에 ToonShadingCommon.ush를 포함시키는 것을 잊지 마세요.

한 가지 더 주목할 점은 ToonBuffer의 형식을 float에서 uint로 변경했는데, 이는 부동 소수점 정밀도 문제로 인해 데이터에 오류가 발생하는 것을 방지하기 위한 것입니다.

SceneTexturesConfig.h
ToonPassRendering.cpp
ToonPassShader.usf

이렇게 하면 ToonBuffer에 데이터가 기록되는데, 아래에서는 게으름을 피워서 얼굴에 다른 SelfID를 설정했습니다.

ToonBuffer 제작에 대한 지난 포스팅도 참고하세요!

【UE5】引擎修改:添加自定义Pass并写入自定义Buffer

发现下面的图片被知乎压得很糊,我把文章的markdown文件和所有图片都上传到github上了,如有需要,可以访问以下链接查看: https://github.com/Yu-ki016/Yu-ki016-Articles/blob/main/UE5%E5%8D%A1%E9%80%9A%E6%B8…

zhuanlan.zhihu.com

4.3 툰버퍼 읽기 

다음으로 레이트레이싱 섀도우의 제너레이션 셰이더에서 툰버퍼를 읽어야 합니다.

셰이더에서 텍스처를 읽는 방법은 4단계로 나뉩니다:

1. 해당 셰이더의 파라미터에 ToonBuffer의 정의를 추가합니다:

RayTracingShadows.cpp

FOcclusionRGS 의 파라미터에 모든 GBuffer 텍스처를 넣는 FSceneTextureParameters 가 있는데, 여기에 TBuffer 를 추가하여 GBuffer 에 액세스하는 셰이더가 모두 TBuffer 에 액세스할 수 있도록 합니다.

SceneTextureParameters.h

2. 파라미터에 TBuffer를 전달할 위치를 찾습니다:

SceneTextureParameters.cpp의 두 GetSceneTextureParameters는 모두

3. 셰이더에서 해당 텍스처를 선언합니다.

FSceneTextureParameter 는 DeferredShadingCommon.ush 에 정의되어 있습니다.

4. 셰이더의 샘플링 매핑

레이트레이싱 섀도 셰이더는 GetGBufferDataFromSceneTexturesLoad를 통해 GBuffer를 샘플링하며, 여기에 ToonBuffer의 샘플링도 넣습니다.

RayTracingDeferredShadingCommon.ush

ToonBuffer의 데이터에 쉽게 액세스하려면 구조체 FGBufferData에 FToonBuffer를 추가합니다:

DeferredShadingCommon.ush

그리고 DecodeGBufferData 함수를 약간 오버로드하세요:

FGBufferData DecodeGBufferData(
	float4 InGBufferA,
	float4 InGBufferB,
	float4 InGBufferC,
	float4 InGBufferD,
	float4 InGBufferE,
	float4 InGBufferF,
	uint4 InTBufferA,
	uint4 InTBufferB,
	uint4 InTBufferC,
	float4 InGBufferVelocity,
	float CustomNativeDepth,
	uint CustomStencil,
	float SceneDepth,
	bool bGetNormalizedNormal,
	bool bChecker)
{
	FGBufferData GBuffer = DecodeGBufferData(InGBufferA, InGBufferB, InGBufferC, InGBufferD, InGBufferE, InGBufferF,
											 InGBufferVelocity,
											 CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal,
											 bChecker);

	if (GBuffer.ShadingModelID == SHADINGMODELID_TOON)
	{
		GBuffer.ToonBuffer = DecodeToonDataFromBuffer(InTBufferA, InTBufferB, InTBufferC, InGBufferD);
	}

	return GBuffer;
}

ToonShadingCommon.ush에는 DecodeToonDataFromBuffer 함수도 있습니다.

// 디코더ToonBuffer
FToonBuffer DecodeToonDataFromBuffer(uint4 ToonBufferA, uint4 ToonBufferB, uint4 ToonBufferC, float4 CustomData)  
{  
    FToonBuffer ToonBuffer;  
    //uint4 ToonBufferABit = EncodeFloatToUint(ToonBufferA);  
    uint4 ToonBufferABit = ToonBufferA;  
    ToonBuffer.SelfID = ToonBufferABit.r;  
    ToonBuffer.ObjectID = ToonBufferABit.g;  
    ToonBuffer.ShadowCastFlag = (ToonBufferABit.b >> 0) & GetBitsMaxValue(5);  
    ToonBuffer.ToonModel = (ToonBufferABit.b >> 5) & GetBitsMaxValue(3);  
    return ToonBuffer;  
}

4.4 레이트레이싱 섀도 수정하기 

ToonBuffer의 데이터를 저장하기 위해 FOcclusionShadingParameters를 수정합니다.

RayTracingOcclusionRGS.usf
레이트레이싱오클루전RGS.usf

다음으로 ToonShadingCommon.ush에서 페이로드를 정의합니다.

struct FToonPayloadData
{
	uint ToonData;					

	// 按位编码ToonPayload
	uint GetSelfID()					{return (ToonData >> 0) & GetBitsMaxValue(8);}
	void SetSelfID(uint ID)					{ToonData |= ((ID & GetBitsMaxValue(8)) << 0);}

	uint GetObjectID()					{return (ToonData >> 8) & GetBitsMaxValue(8);}
	void SetObjectID(uint ID)				{ToonData |= ((ID & GetBitsMaxValue(8)) << 8);}
	
	uint GetShadowCastFlag()				{return (ToonData >> 16) & GetBitsMaxValue(5);}
	void SetShadowCastFlag(uint Flag)			{ToonData |= ((Flag & GetBitsMaxValue(5)) << 16);}
	
	uint GetToonModel()					{return (ToonData >> 21) & GetBitsMaxValue(3);}
	void SetToonModel(uint ToonModel)			{ToonData |= ((ToonModel & GetBitsMaxValue(3)) << 21);}
	
};

레이 제너레이션 셰이더는 툰을 사용할 때 커스텀 트레이스 가시성 레이 함수를 사용합니다.

RayTracingOcclusionRGS.usf

다음으로 약간의 TraceToonVisibilityRay를 구현합니다:

ToonShadingCommon.ush를 포함시키는 것으로 시작합니다.

RayTracingCommon.ush

구조체 FPackedMaterialClosestHitPayload : FMinimalPayload에 SetShadingModelID 함수를 추가합니다(STRATA_ENABLED에 있는 함수가 아님에 유의하세요).

RayTracingCommon.ush
void SetShadingModelID(uint ShadingModelID) {IorAndShadingModelIDAndBlendingModeAndFlagsAndPrimitiveLightingChannelMask |= (ShadingModelID & 0xF) << 16; }

그런 다음 RayTracingCommon.ush의 TraceToonVisibilityRay를 임의의 어딘가에 추가하고 FPackedMaterialClosestHitPayload에 PackedCustomData가 있으므로 여기에 ToonPayloadData를 직접 작성하겠습니다. (각 픽셀에 페이로드 사본을 저장해야 하므로 데이터가 많을수록 메모리 사용량이 증가합니다.) FPackedMaterialClosestHitPayload에 추가 데이터를 추가할 필요가 없습니다.

#if !STRATA_ENABLED  
FMinimalPayload TraceToonVisibilityRay(  
    in RaytracingAccelerationStructure TLAS,  
    in uint RayFlags,  
    in uint InstanceInclusionMask,  
    in FRayDesc Ray,  
    in FToonPayloadData ToonPayloadData)  
{  
    FPackedMaterialClosestHitPayload PackedPayload = (FPackedMaterialClosestHitPayload)0;
    PackedPayload.SetFlags(RAY_TRACING_PAYLOAD_INPUT_FLAG_SHADOW_RAY);
    PackedPayload.PackedCustomData = ToonPayloadData.ToonData;  
    PackedPayload.SetShadingModelID(SHADINGMODELID_TOON);
    TraceVisibilityRayPacked(PackedPayload, TLAS, RayFlags, InstanceInclusionMask, Ray);  
    FMinimalPayload MinimalPayload = (FMinimalPayload)0;    
    MinimalPayload.HitT = PackedPayload.HitT;    
    return MinimalPayload;  
}  
#endif

 


다음으로 RayTracingMaterialHitShaders.usf를 찾아 AnyHit 셰이더를 수정하고, 처음에 다음 세 줄의 테스트 코드를 삭제하는 것을 잊지 마세요.

이 두 가지의 판단을 수정합니다:

RayTracingMaterialHitShaders.usf

마지막에 툰 처리를 추가합니다:

RayTracingMaterialHitShaders.usf
#if !STRATA_ENABLED && MATERIAL_SHADINGMODEL_TOON

	// 방사선 방출원이 툰이 아닌 경우, 특별한 처리가 없는 경우
	if (PackedPayload.GetShadingModelID() != SHADINGMODELID_TOON)
	{
		return;
	}

	FToonPayloadData RayGenToonPayload = (FToonPayloadData)0;
	FToonPayloadData HitToonPayload = (FToonPayloadData)0;
	RayGenToonPayload.ToonData = PackedPayload.PackedCustomData;
#ifdef  HAVE_GetToonMaterialOutput0
	float4 ToonBufferA = float4(GetToonMaterialOutput0(MaterialParameters), 0);
	HitToonPayload = GetToonPayloadDataFromBuffer(ToonBufferA);
#endif
	
	// SCF_DISABLEALLTOON을 사용하면 툰 머티리얼에 투사되지 않습니다.
	if(HitToonPayload.GetShadowCastFlag() & SCF_DISABLEALLTOON)
	{
		IgnoreHit();
	}
	
	// 동일한 액터인지 확인
	bool IsSelfActor = HitToonPayload.GetObjectID() == RayGenToonPayload.GetObjectID();
	if(IsSelfActor)
	{
		if (HitToonPayload.GetShadowCastFlag() & SCF_DISABLEONTOON)
		{
			IgnoreHit();
		}
		// 셀프 섀도잉 방지
		if (HitToonPayload.GetShadowCastFlag() & SCF_DISABLEONSELF && RayGenToonPayload.GetSelfID() == HitToonPayload.GetSelfID())
		{
			IgnoreHit();
		}
		if (HitToonPayload.GetShadowCastFlag() & SCF_DISABLEONFACE && RayGenToonPayload.GetToonModel() == TOONMODEL_FACE)
		{
			IgnoreHit();
		}
		if (HitToonPayload.GetShadowCastFlag() & SCF_DISABLEONEYE && RayGenToonPayload.GetToonModel() == TOONMODEL_EYE)
		{
			IgnoreHit();
		}
	}

#endif

GetToonPayloadDataFromBuffer 함수는 ToonShadingCommon.ush에 아래에 정의되어 있습니다:

FToonPayloadData GetToonPayloadDataFromBuffer(float4 ToonBufferA)  
{  
    FToonPayloadData Out = (FToonPayloadData)0;  
    uint4 ToonBufferABit = ToonBufferA;  
    Out.SetSelfID(ToonBufferABit.r);  
    Out.SetObjectID(ToonBufferABit.g);  
    Out.SetShadowCastFlag((ToonBufferABit.b >> 0) & GetBitsMaxValue(5));  
    Out.SetToonModel((ToonBufferABit.b >> 5) & GetBitsMaxValue(3));  
    return Out;  
}

아래 이미지에서는 얼굴의 셀프 섀도잉, 얼굴의 머리카락 투영, 눈의 투영이 꺼져 있습니다(머리카락 투영은 여러 각도에서 보기 어려우므로 나중에 화면 공간 연습을 통해 처리 중입니다).

툰에 다른 오브젝트의 투영을 유지하고 툰에 툰의 투영을 켜거나 끄도록 선택할 수 있습니다.

(눈과 눈썹과 같은 부분의 질감을 조정하지 않았고 음영 효과가 약간 이상하지만 신경 쓰지 마십시오.)
V. 참고
언리얼 렌더링 시스템 해부하기 (17) - 리얼타임 레이 트레이싱:
https://www.cnblogs.com/timlly/p/16687324.html

剖析虚幻渲染体系(17)- 实时光线追踪 - 0向往0 - 博客园

17.1 本篇概述 17.1.1 本篇内容 UE的光线追踪一直是童鞋们呼吁比较高的一篇,虽然多年前博主已经在探究光线追踪技术及UE4的实现阐述过,但内容较基础和片面。那么,此篇就针对UE的实时光线追

www.cnblogs.com

다이렉트X 레이트레이싱 소개
https://intro-to-dxr.cwyman.org/

https://intro-to-dxr.cwyman.org/

intro-to-dxr.cwyman.org


원문.
https://zhuanlan.zhihu.com/p/688722298?utm_psn=1755578166465581056&utm_id=0

【UE5】通过Raytracing自定义卡通渲染投影

发现之前写的文章很多图片被知乎压得很糊,一些代码的图片更是根本看不清,所有我打算以后写文章时把markdown文件和所有图片都上传到github上,如果有需要还请移步以下github仓库: GitHub - Yu-k

zhuanlan.zhihu.com