언리얼 렌더링 시스템 분석(06) - UE5 특집 1부(기능 및 Nanite)
🌐 원문 링크: 剖析虚幻渲染体系(06)- UE5特辑Part 1(特性和Nanite) (cnblogs.com/timlly)
📅 원문 발행일: 2021-06-24 | ✍️ 저자: Timlly (00 / )💡 시리즈 분류:
Unreal Engine Rendering| 국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료
2020년 5월 초 Unreal은 Unreal 5의 렌더링 기능을 보여주는 Lumen in the Land of Nanite 비디오를 공식적으로 출시했습니다. 이 비디오에서는 가상 마이크로 다각형 기하학과 실시간 전역 조명 Lumen 기술을 기반으로 하는 Nanite를 시연하여 영화 및 TV 수준의 시청각 경험을 실시간 게임에 구현했습니다.
당시 Unreal은 공식적으로 2021년 상반기에 UE5 프리뷰 버전을 출시하겠다고 약속했습니다. 예상대로 그들은 약속을 지켜 2021년 5월 말 프리뷰 버전 **UE5 Early Access(EA)**를 성공적으로 출시했습니다. 따라서 우리는 UE5의 에디터, 툴 체인, 새로운 렌더링 기능은 물론 해당 UE5 EA 버전 소스 코드와 그에 따른 리소스 프로젝트 AncientGame을 연구할 수 있습니다.

UE5 에디터 개요. 주요 장면은 UE5 EA로 출시된 AncientWorld 프로젝트입니다.
6.1.1 이 글의 내용
이 글에서는 UE5 Early Access(EA) 버전을 기반으로 UE5의 다음 내용을 주로 설명합니다.
UE5 기본 및 시작하기.
UE5의 새로운 기능.
UE5 렌더링 시스템이 변경되었습니다.
나나이트 가상 마이크로 기하학 기술.
루멘 실시간 글로벌 동적 조명 기술.
기타 UE5 관련 기술.
글의 길이를 제한하기 위해 두 부분으로 나누어서 작성하겠습니다. 첫 번째 부분에서는 편집기, 새로운 기능 및 Nanite 기술에 대해 설명하고 두 번째 부분에서는 Lumen, 기타 렌더링 기술 및 요약에 대해 설명합니다.
6.1.2 기본 개념
이 기사에 포함된 렌더링의 기본 개념과 분석 중 일부는 다음과 같습니다.
개념 약어 중국어 번역 분석하다
루멘
- 루멘스 UE5의 실시간 전역 조명 기술입니다.
나나이트
- 나노로봇 UE5의 가상 마이크로폴리곤 기술.
6.2 UE5의 새로운 기능
이 섹션에서는 UE4와 다른 UE5의 설치, 에디터, 새로운 기능에 대해 설명합니다.
6.2.1 UE5 에디터
6.2.1.1 편집기 및 리소스 다운로드
- UE5 엔진 다운로드
첫 번째 단계는 Epic Game Launcher를 업데이트하고 다시 시작하는 것입니다.
두 번째 단계에서는 UE5 페이지를 클릭하고 "Download Early Access Version"을 클릭합니다.

세 번째 단계는 "라이브러리" 페이지에서 5.0.0 버튼을 찾아 클릭하여 다운로드하는 것입니다.

- AncientGame 프로젝트 다운로드
UE5 페이지로 전환하고 맨 아래로 스크롤한 후 UE5의 새로운 기능을 보여주는 샘플 프로젝트에서 Get Sample 버튼을 클릭합니다.

- EA 소스 코드 다운로드
UE5 페이지를 열고 "소스 코드 액세스" 버튼을 클릭하세요:

아니면 5.0 Early Access 페이지를 직접 열어서 소스코드(zip)나 소스코드(tar.gz)를 다운로드하고 압축을 푼 후 UE4 프로세스에 따라 설정하고 컴파일할 수 있습니다.
6.2.1.2 샘플 프로젝트 시작
다운로드 또는 컴파일된 UE5 에디터를 사용하여 AncientGame 프로젝트를 엽니다. 성공적으로 시작되면 다음 프롬프트 페이지가 나타납니다.

Ctrl+Space를 눌러 콘텐츠 서랍을 열고 AncientContent/Maps를 입력한 다음 AncientWorld 지도를 엽니다.

레벨을 열면 화면이 검게 변합니다. 당황하지 말 것. 정상입니다. UE5는 장면을 정상적으로 렌더링하기 전에 많은 데이터 전처리를 수행해야 하기 때문입니다.

긴 데이터 처리가 끝날 때까지 기다린 후 AncientWold의 주요 장면을 미리 볼 수 있습니다.

6.2.1.3 편집기 리본
UE4에 비해 UE5 에디터의 메인 인터페이스는 레이아웃과 UI 스타일이 확연히 달라졌습니다. UI 스타일은 평면화되어 DCC 도구와 비슷해졌습니다. 레이아웃은 레벨 편집 영역을 강조하고 구성 요소 및 콘텐츠 브라우저와 같은 영역의 점유를 줄입니다.

위 그림과 같이 각 영역의 기능은 다음과 같습니다.
UE4와 유사한 메뉴 바.
구성 요소 추가, 콘텐츠 검색, 레벨 청사진, 시퀀스 및 기타 도구 구성 요소.
맵 브러시 편집 모드.
재생 및 미리보기.
월드, 프로젝트, 플러그인 설정을 포함한 설정.
World Partition, Data Layer 등의 페이지.
레벨 액터 목록과 속성 패널은 UE4와 유사합니다.
백그라운드 작업 상태 및 버전 제어.
콘텐츠 브라우저 및 명령줄 도구. 콘텐츠 브라우저는 단축키 Ctrl+Space를 통해 빠르게 표시하거나 숨길 수 있습니다. 명령줄에서는 UE4처럼 ~ 키를 누를 필요가 없습니다. 콘솔 변수를 설정하고 디버깅 효율성을 높이는 것이 더 편리합니다.
레벨 편집 메인 창, 구체적인 기능은 UE4와 유사하지만 차이점도 있습니다. 예를 들어 Lit는 가상 마이크로폴리곤 기술과 관련된 정보를 표시하기 위해 Nanite 시각적 그룹을 추가했습니다.

레벨 편집의 Lit 모드는 Ninite 시각화 그룹을 추가합니다. 배경의 소음은 버그가 아니지만 나나이트 삼각형 모드를 표시합니다.
6.2.2 새로운 렌더링 기능
이 섹션에서는 UE5의 새로운 렌더링 기능에 대해 설명합니다.
6.2.2.1 나나이트 가상 마이크로폴리곤
Nanite는 나노로봇을 의미하며, UE5는 이를 차세대 메시 처리 및 셰이딩 기술의 이름으로 사용합니다. 의도는 분명합니다. 기존 컬링 및 래스터라이제이션 셰이딩 기술을 메시 LOD를 세분성으로 대체 및 업그레이드하고 매우 작은 세분성을 사용하여 메시와 삼각형을 처리하려는 것입니다.
UE5의 Nanite의 전체 이름은 Nanite Virtualized Geometry(Nanite virtual micro-geometry, Nanite virtual micro-polygon)입니다. 고정밀 메쉬 모델, 매우 세밀한 표면 및 픽셀 수준 삼각형의 대규모 개체의 자동 처리를 지원합니다. 필요한 데이터만 적절한 수준에서만 처리하므로 표면 세부 정보가 손실되거나 너무 많은 데이터가 처리되는 것을 방지할 수 있습니다. Nanite는 렌더링하기 전에 메시, 텍스처, 애니메이션 및 기타 데이터에 대해 많은 전처리를 수행하고 이를 고도로 압축되고 세분화된 바이너리 스트림에 저장하고 LOD를 자동으로 처리합니다.

AncientGame 샘플 프로젝트의 Nanite 마이크로폴리곤 기술 개요입니다. 왼쪽 위: AncientGame의 사원; 오른쪽 상단: 왼쪽 상단에 해당하는 마이크로폴리곤 시각화. 왼쪽 하단과 오른쪽 하단은 각각 프로젝트의 보스와 산 세부정보입니다.
UE5에서 Nanite 그리드 기술을 활성화하는 방법은 세 가지가 있습니다:
- 정적 메쉬를 가져올 때 설정 인터페이스에서 켤 수 있습니다.

- 그리드 편집기의 그리드 속성 패널에서 설정할 수 있습니다.

- 리소스 브라우저의 마우스 오른쪽 버튼 클릭 메뉴를 통해 일괄적으로 열 수 있습니다.

Nanite 기술을 활성화하면 다음과 같은 많은 이점을 얻을 수 있습니다.
UE4보다 훨씬 더 많은 기하학적 복잡성, 더 많은 수의 삼각형 및 객체에 대한 실시간 처리 및 렌더링을 지원합니다.
실시간 프레임 성능은 더 이상 다각형 수, 그리기 호출 및 메시 메모리 사용량으로 제한되지 않습니다.
(잠재적으로) ZBrush와 같은 DCC 소프트웨어에서 제작된 고정밀 모델의 직접 가져오기를 지원합니다.
미리 구운 노멀 맵 대신 고정밀 메시를 직접 사용합니다.
자동 LOD 처리로 더 이상 메시 LOD를 수동으로 설정하고 처리할 필요가 없습니다.
LOD 전환 중 품질 손실이 없거나 약간만 발생합니다.
Nanite 메시는 전통적인 정적 메시와 유사합니다. 이는 여전히 본질적으로 삼각형 메쉬이지만 핵심 차이점은 많은 양의 세부 사항과 높은 수준의 데이터 압축입니다. 무엇보다도 Nanite는 완전히 새로운 시스템을 사용하여 매우 효율적인 방식으로 데이터 형식을 렌더링합니다.
기존의 정적 모델에서는 Nanite 기술을 활성화하려면 플래그를 설정해야 합니다. Nanite 메시는 여러 세트의 UV 및 버텍스 색상을 지원할 수 있습니다. 재료는 메시의 일부에 할당되므로 이러한 재료는 다양한 셰이딩 모델과 동적 효과(셰이더에서 수행할 수 있음)를 사용할 수 있습니다. 재료 할당은 다른 정적 메시와 마찬가지로 동적으로 교체될 수 있으며 Nanite는 재료를 굽는 데 추가 처리가 필요하지 않습니다.
Nanite는 렌더링 성능이 더 좋고 메모리와 하드 디스크를 덜 차지하므로 정적 메쉬의 Nanite 속성을 최대한 켜야 합니다. 정적 메쉬에 Nanite 기술을 더 잘 활용하려면 다음 사항을 충족하는 것이 가장 좋습니다.
많은 수의 삼각형이 포함되어 있거나 삼각형이 화면에서 작은 크기를 차지합니다.
동일한 장면의 인스턴스가 여러 개 있습니다.
다른 Nanite 기하학을 차단하기 위한 교합체로 사용할 수 있습니다.
현재 버전에서 Nanite는 다음 구성 요소 유형을 지원합니다.
정적 메쉬
인스턴스화된 정적 메쉬
계층 인스턴스화된 정적 메쉬
기하학 컬렉션
Nanite가 지원하지 않는 애니메이션 유형은 다음을 포함하지만 이에 국한되지는 않습니다.
뼈대 애니메이션
모프 타겟, 블렌드 셰이프
월드 포지션 오프셋이 있는 머티리얼(머티리얼의 월드 포지션 오프셋)
스플라인 메쉬
또한 Nanite는 다음 기능을 지원하지 않습니다.
맞춤형 깊이 또는 스텐실
인스턴스의 버텍스 페인팅
Nanite 메시의 정점별 접선은 기존 정적 메시처럼 메시 데이터에 저장되지 않으므로(공식 문서에서는 이것이 데이터 크기를 줄이기 위한 것이라고 설명합니다) 따라서 접선은 픽셀 셰이더에서 동적으로 계산됩니다. Nanite는 탄젠트 공간과 기존 방법 간의 사용 차이로 인해 가장자리에서 불연속성을 일으킬 수 있습니다.
Nanite는 다음 구성의 재질도 지원하지 않습니다.
- 불투명을 제외한 블렌드 모드.
마스크 및 반투명은 지원되지 않습니다.
딜레이 데칼.
와이어프레임 모드.
픽셀 깊이 오프셋.
월드 포지션 오프셋.
인스턴스별 데이터를 맞춤화했습니다.
양면 소재입니다.
Nanite는 다음 속성을 사용하는 재료를 제대로 렌더링할 수 없습니다(사라질 수도 있음).
버텍스 보간기 노드.
맞춤형 UV.
Nanite는 다음 렌더링 기능을 지원하지 않습니다.
- 다음과 같은 보기 관련 개체 필터링이 사용됩니다.
최소 화면 반경
거리 자르기
FPrimitiveSceneProxy::IsShown() 필터링
다음 기능을 갖춘 장면 캡처:
숨겨진 구성 요소
-배우 숨기기
표시된 구성 요소만
배우만 표시됨
포워드 렌더링
VR 모드의 스테레오 렌더링
분할 화면
MSAA
광 채널
완전히 상세한 Nanite 메시를 위한 레이 트레이싱
부분 시각화 모드
Nanite는 최신 드라이버와 다음 GPU를 갖춘 PlayStation 5, Xbox Series S|X, PC 및 기타 플랫폼을 지원합니다.
NVIDIA: Maxwell 또는 최신 그래픽 카드
AMD: GCN 또는 최신 그래픽 카드
나나이트의 성능을 모니터링하기 위해서는 다음 사항에 주의해야 합니다.
Aggregate Geometry: Aggregate Geometry는 머리카락, 나뭇잎, 풀과 같이 작고 연결되지 않은 많은 것들을 멀리 있는 하나의 볼륨(볼륨)으로 결합합니다. 이는 LOD 및 폐색 선별 기술에 영향을 미칩니다.
촘촘하게 쌓인 표면: Nanite는 뷰의 상단 표면에 가까운 객체를 병합하고 객체 간의 폐색 및 숨기기 관계에 관계없이 함께 쌓인 모든 객체를 그립니다.


위 그림은 AncientGame의 일반 화면이고, 아래 그림은 Closely Stacked Surfaces를 사용한 해당 인스턴스 시각화입니다. 검은색 부분은 나나이트가 동적 캐릭터를 지원하지 않아서 발생합니다.
대부분의 경우 밀접하게 쌓인 표면은 그리기 호출을 줄이지만 어떤 경우에는 반대 효과가 있을 수 있습니다. Overdraw 시각화 모드에서 카메라를 이동하면 이러한 쌓인 표면이 렌더링되는 방식을 볼 수 있습니다.

- 면처리된 가장자리 노멀: 이상적으로는 메시의 버텍스 수가 삼각형보다 적어야 합니다. 꼭지점 개수와 삼각형의 비율이 2:1 이상이면 문제가 있을 수 있으며, 특히 삼각형 개수가 많은 경우 문제가 발생할 수 있습니다. 3:1의 비율은 메쉬가 완전히 면 처리되어 각 삼각형에 세 개의 정점이 있고 그 중 어느 것도 다른 삼각형과 공유되지 않음을 의미합니다. 이는 일반적으로 해당 법선이 스무딩되지 않아 동일하지 않기 때문입니다. 다음 두 이미지는 다각형과 부드러운 노멀 간의 유사점과 차이점을 보여줍니다.

위 그림의 왼쪽은 패싯 노멀을 사용하고, 위 그림의 오른쪽은 스무딩 그룹 노멀을 사용합니다.

위 그림의 왼쪽은 면처리된 법선의 Nanite 삼각형 시각화를 사용하고 위 그림의 오른쪽은 부드러운 그룹 법선의 Nanite 삼각형 시각화를 사용합니다. 스무딩 그룹의 법선을 사용하면 그리는 데 더 적은 수의 삼각형이 사용되는 것을 볼 수 있습니다.
위에서 언급한 여러 시각화 모드 외에도 UE5는 다양한 시각화 방법도 제공합니다. 모든 데이터는 개요 모드를 통해 볼 수 있습니다.

UE5는 각각 다른 데이터를 표시하는 Nanite의 다양한 시각화 모드를 제공합니다.

Nanite의 개요를 열면 모든 시각화 모드 섬네일이 표시됩니다.
콘솔 변수 Nanitestats를 사용하여 현재 화면의 Nanite 통계를 실시간으로 볼 수 있습니다.

나나이트의 크로핑 데이터와 지오메트리 데이터는 오른쪽에 표시됩니다.
6.2.2.2 루멘 전역 동적 조명
루멘은 UE5의 완전히 동적인 전역 조명 및 반사 시스템입니다. UE5의 기본 전역 조명 및 반사 방법입니다. 루멘은 밀리미터에서 킬로미터 수준까지 대규모의 세부 환경에서 디퓨즈의 무한 바운스와 간접 스페큘러 반사를 렌더링할 수 있습니다.
Lumen을 활성화하려면 프로젝트 설정에서 다음 옵션을 활성화해야 합니다(모두 기본적으로 활성화되어 있음).

Lumen을 켜면 Lumen GI가 SSGI 및 DFAO를 대체하고 Lumen 반사가 SSR을 대체하며 정적 조명이 비활성화되고 모든 조명 맵이 숨겨집니다.
Lumen에서 지원하는 렌더링 기능은 다음과 같습니다.
- 루멘 글로벌 일루미네이션
Lumen GI는 장면 객체의 간접 조명 부분을 해결합니다. 예를 들어, 직사광선 픽셀은 근처 픽셀에 영향을 미칩니다. 이러한 현상을 색 번짐(Color Bleeding)이라고도 합니다. 동시에 메시는 일부 간접광을 차단하고 흡수하므로 Lumen은 간접광의 그림자 폐색도 올바르게 처리할 수 있습니다.

루멘 전역 조명은 간접 조명 조명과 그림자 효과를 실시간으로 동적으로 처리할 수 있습니다.
Lumen은 실시간 렌더링을 위해 낮은 해상도에서 간접 조명을 계산하는 동시에 전체 해상도의 일반 세부 사항을 구현합니다.
- 루멘 스카이 조명
Lumen은 Final Gather 단계에서 하늘 조명 문제를 해결하여 실내와 실외의 하늘 빛을 상당히 다르게 만들고 실내는 더 어둡게 만듭니다. 또한 Lumen의 스카이라이트는 낮은 품질의 투명 조명과 체적 안개 GI 효과도 지원합니다.

- 루멘 발광 소재
발광 재료는 추가 소비 없이 Lumen의 Final Gather를 통해 빛 전파를 완료합니다. 그러나 발광 물질의 방사 영역의 크기와 밝기에도 제한이 있으며 그렇지 않으면 소음 결함이 발생합니다.

- 루멘 반사
Lumen은 모든 러프니스 범위의 재료에 대한 간접 반사 효과를 해결합니다.

또한 Lumen은 바니시 재료의 반사도 지원합니다.
EA 버전에서는 Lumen의 광원 특성 지원이 다음과 같이 설명됩니다.
방향성 조명, 포인트 조명, 스포트라이트, 직사각형 조명, 스카이라이트를 포함한 모든 광원 유형을 지원합니다.
[만]은 방향성 조명의 조명 기능을 지원합니다.
루멘을 켜면 정적 조명과 조명 맵이 비활성화되므로 광원의 정적 속성을 지원하지 않습니다.
프로젝트 설정 및 포스트 프로세스 볼륨에서는 소프트 레이 트레이싱 모드, 디테일 추적 모드, 글로벌 추적 모드, 하드웨어 추적 모드, GI 및 반사와 같은 Lumen의 다양한 매개변수를 설정할 수 있습니다.
6.2.2.3 가상 그림자 맵
**VirtualShadowMap(VSM)**은 일관된 고해상도 그림자, 영화 품질 자산 및 대규모 오픈 월드를 갖춘 동적 조명을 제공하는 데 사용되는 새로운 그림자 캐스팅 방법입니다.
프로젝트 설정의 섀도우 맵 방법에서 VSM을 활성화합니다(기본적으로 활성화되어 있음):

VSM을 켜면 고정된 사전 계산 그림자, 디스턴스 필드 그림자, 미리보기 그림자, 객체별 그림자, 캐스케이드 그림자, 움직이는 동적 그림자 등을 포함한 기존 그림자 기술을 대체합니다.
VSM을 켠 후 **SMRT(Shadow Map Ray Tracing)**를 사용하여 반그림자, 부드러운 그림자, 접촉 하드 그림자 등을 포함하여 더 정확하고 명확한 그림자 및 관련 기능을 얻을 수 있습니다.

SMRT 기술을 사용하여 점광원은 부드러운 그림자와 접촉하는 단단한 그림자의 특성을 구현합니다.

왼쪽: PCF가 중요한 표면 세부 사항을 흐리게 하고 제거합니다. 오른쪽: SMRT는 더욱 사실적인 부드러운 그림자와 접촉형 단단한 그림자를 제공합니다.
우리 모두는 전통적인 렌더링에서 CSM이 방향성 조명의 그림자를 최적화하는 데 사용된다는 것을 알고 있습니다. 마찬가지로 UE5는 방향성 조명에 클립맵 기술을 사용합니다.
단일 가상 그림자 맵은 더 넓은 영역을 포괄할 만큼 충분한 해상도를 제공하지 않습니다. 지향성 조명은 카메라 주위로 확장되는 몽타주 구조를 사용하며, 각 몽타주 레벨에는 자체 16K VSM이 있습니다. 각 클립맵 레벨은 동일한 해상도를 갖지만 이전 레벨 반경의 두 배를 포함합니다.

왼쪽: 클립 시각화; 오른쪽: VSM 페이지 시각화.
스포트라이트는 밉 체인이 포함된 단일 16k VSM을 사용하여 클립맵 대신 섀도우 LOD를 처리합니다. 점 광원은 각 면에 대해 16k VSM, 총 6개의 큐브 맵을 사용합니다.


위: 스포트라이트 효과; 하단: 해당 단일 VSM 시각화.
섀도우 렌더링 성능을 최적화하기 위해 UE5는 캐싱 및 대략적인 페이지와 같은 기술을 사용합니다.
6.2.2.4 시간적 초해상도
**TSR(Temporal Super Resolution)**은 기존 TAA를 대체하는 데 사용되는 차세대 템포럴 안티앨리어싱(TAA) 알고리즘입니다. 지원하는 기능은 다음과 같습니다.
1080p의 저해상도 입력을 활용하면 출력이 기본 4K 렌더링 품질에 가까워 더 높은 프레임 속도와 더 나은 렌더링 충실도가 가능합니다.
고주파수 배경에서 잔상이 줄어듭니다.
복잡도가 높은 기하학적 객체의 깜박임을 줄입니다.
Shader Model 5 호환 하드웨어에서 실행을 지원합니다: D3D11, D3D12, Vulkan, PS5, XSX, Metal은 아직 지원되지 않습니다.
PS5 및 XSX의 GPU 아키텍처에 특별히 최적화된 셰이더입니다.
시간적 초해상도는 프로젝트 구성에서 켜거나 끌 수 있습니다. 기본적으로 UE5에는 이 기술이 켜져 있습니다.

왼쪽: 4K 기본 해상도 렌더링, 프레임 속도는 20.57입니다. 오른쪽: 시간적 초해상도 기술을 사용하여 1080p 4K 화질을 출력하고 프레임 속도가 44.22로 증가합니다.
6.2.2.5 모바일 렌더링
UE5는 다음을 포함하여 모바일 단말기용 렌더링 모듈을 일부 개선했습니다.
모바일 렌더러는 RDG(렌더링 종속성 그래프)를 사용합니다.
디스턴스 필드 앰비언트 오클루전(거리 필드 AO) 및 글로벌 디스턴스 필드(글로벌 디스턴스 필드)를 지원합니다.
DXC(DirectX 셰이더 컴파일러)가 Android Vulkan의 기본 셰이더 컴파일러가 됩니다. 또한 OpenGL ES에 대한 DXC 지원이 추가되었습니다.
향상된 모바일 디퍼드 렌더러. IBL, 지연된 데칼, IES 조명 및 PC 수준 품질과 일치하는 기타 조명 기능을 포함하여 일부 모듈의 안정성과 성능이 향상되었습니다. 또한 더 적은 수의 셰이더 순열이 사용됩니다.
6.2.3 기타 새로운 기능
6.2.3.1 월드 파티션
월드 파티션은 에디터와 런타임 모두에서 사용할 수 있는 새로운 데이터 관리 및 스트리밍 시스템으로, 스트리밍 데이터를 관리하고 데이터 경합을 줄이기 위해 월드를 수많은 하위 레벨로 수동으로 나눌 필요가 전혀 없습니다.
월드 파티션을 사용하면 월드가 하나의 지속적인 레벨로 존재합니다. 에디터에서는 월드가 그리드로 나누어져 있으며, 관심 영역을 기준으로 지도 데이터를 부분적으로 로드할 수 있습니다. PIE를 굽거나 시작할 때 세계는 런타임 스트리밍에 최적화된 그리드 셀로 나누어져 독립적인 스트리밍 수준이 됩니다.
메뉴 표시줄 창/월드 파티션에서 월드 파티션 편집기를 열고, 왼쪽 버튼을 드래그하여 지정된 범위의 모든 그리드를 빠르게 선택하고, 마우스 오른쪽 버튼을 클릭하여 선택한 그리드 로드, 선택한 그리드 언로드 및 카메라를 현재 그리드로 이동하는 등의 작업 메뉴를 팝업할 수 있습니다.

또한 세계는 데이터 레이어, HLOD(Hierarchical Level of Detail), 레벨 인스턴싱, 액터당 하나의 파일과 같은 기능도 지원합니다.
6.2.3.2 애니메이션
UE5의 애니메이션 모듈은 Full Body IK, Control Rig, Motion Warping, 애니메이션 툴 스크립트, 시퀀서 지원을 추가합니다.

전신 IK 다이어그램.

컨트롤 리그 효과 다이어그램.

모션 워핑 효과 다이어그램.
6.2.3.3 물리학
새로운 기능 중 UE5의 물리적 효과도 매우 눈길을 끕니다. 여기에는 주로 다음과 같은 기능이 포함됩니다.
- 혼돈
Chaos는 차세대 게임의 요구 사항을 충족하기 위해 처음부터 제작된 UE5용 경량 물리 시뮬레이션 솔루션입니다. 지원하는 주요 기능은 다음과 같습니다.
강체 역학
강체 애니메이션 노드 및 옷감 물리학
파괴

봉제인형 물리학
차량
물리학 분야. 물리 시스템을 사용하면 사용자는 런타임 시 특정 공간 영역에서 카오스 물리 시뮬레이션에 직접 영향을 미칠 수 있습니다. 이러한 필드는 강체에 힘을 적용하고, 형상 컬렉션 클러스터를 깨고, 부서진 강체를 고정하거나 비활성화하는 등 다양한 방식으로 물리 시뮬레이션에 영향을 미치도록 구성할 수 있습니다.

유체 시뮬레이션
헤어 시뮬레이션
6.2.3.4 게임플레이
GamePlay 프레임워크는 게임 및 모듈 로직, 데이터 레지스트리 및 추가 입력 시스템을 추가합니다.

데이터 레지스트리 편집기.
6.2.3.5 성능 및 플랫폼 관리
- Unreal Insights용 메모리 분석 도구
UE5는 Unreal Insights의 Memory Insights 모듈을 통해 향상된 메모리 추적 및 디버깅 지원을 제공합니다. 다음 기능이 지원됩니다:
세션 중 특정 시간에 할당된 모든 메모리의 스냅샷을 봅니다.
서로 다른 두 시간에 할당된 모든 메모리의 스냅샷을 비교합니다.
각 메모리 할당에 대한 호출 스택을 봅니다.
장기 및 단기(또는 임시) 메모리 할당을 식별합니다.
메모리 누수를 찾아보세요.

Unreal Insights의 Memory Insights 편집기 개요입니다.
- 플랫폼 툴체인
Mac에서 iOS 원격 빌드 프로세스의 안정성이 향상되었으며 크로스 플랫폼 라이브러리가 추가되어 USB를 통해 iOS 장치와 상호 작용하는 안정성이 향상되었습니다.
또한 오디오 사운드 효과, 재설계된 VR 템플릿, Unreal Turnkey 등이 추가되었습니다.
6.3 UE5 렌더링 시스템 변경
이번 장에서는 UE4.26 버전의 소스코드를 비교하고, UE5 EA버전과 UE4.26 버전의 소스코드 차이점을 체계적으로 설명하겠습니다. 쉬운 비교를 위해 Beyond Compare의 폴더 비교 기능을 사용하세요.

두 버전 간의 소스 코드 차이는 상당히 크지만 이 장의 후속 장에서는 주로 다음 폴더에 집중된 기본 모듈, 렌더링 시스템, 셰이더 등의 차이점에 중점을 둘 것입니다.
엔진\소스\런타임\코어
엔진\소스\런타임\CoreUObject
엔진\소스\런타임\D3D12RHI
엔진\소스\런타임\엔진
엔진\소스\런타임\메시설명
엔진\소스\런타임\렌더러
엔진\소스\런타임\RHI
엔진\소스\런타임\RHICore
엔진\셰이더
6.3.1 코어 및 CoreUObject
- 핵심:
Algo, IO, Async, Hash, Memory, Math, Container, String, Serialization 및 UObject와 같은 기본 모듈을 수정하고 추가합니다.
FileCache, HAL, Logging, Misc, Modules, Stats와 같은 지원 모듈을 수정합니다.
Android, Apple, IOS, HoloLens, Mac, Unix, Windows 등의 시스템 모듈을 수정합니다.
UObject:
Class, Obj, Package, Property 및 관련 모듈을 포함하여 UObject의 다양한 속성 모듈에 많은 수정과 개선이 이루어졌습니다.
ObjectHandle, ObjectPathId, PackageResourceManager, PropertyClassPrt, PropertyObjectPrt 및 기타 모듈이 추가되었습니다.
자산등록:
AssetData의 일부 로직을 수정했습니다.
AssetBundle 관련 로직을 개선하고 AssetDataTagMap 개념을 추가합니다.
AssetDataTagMapSerializationDetails가 추가되었습니다.
-패키지:
PackageName의 일부 인터페이스를 추가하거나 개선합니다.
- PackageAccessTracking, PackageAccessTrackingOps 및 PackagePath와 같은 모듈 논리가 추가되었습니다.
-직렬화:
직렬화된 BulkData, ArchiveUObject, Json 읽기 및 쓰기 및 기타 기본 모듈을 수정했습니다.
로딩, 비동기 로딩, 문서 작성 등 다수의 모듈이 수정되었습니다.
FilePackageStore, MappedName 및 기타 모듈이 추가되었습니다.
기본적으로 모든 측면을 포괄하는 핵심 및 기본 모듈에서 많은 변경과 재구성이 이루어졌음을 알 수 있습니다.
6.3.2 RHI 및 RHICore
- RHI:
모바일 측의 여러 기능을 포함하여 일부 렌더링 기능에 대한 태그 및 지원이 추가되었습니다.
MSAA, BufferUsageFlags, RHIPipeline, EResourceTransitionFlags 및 Transition과 같은 개념과 인터페이스가 추가되었습니다.
FRHIVertexBuffer, FRHIIndexBuffer, FRHIStructuredBuffer 유형을 삭제하고 FRHIBuffer로 통합한 후 관련 인터페이스를 수정합니다.
Texture2DArray 관련 인터페이스를 추가하고, 기존 RHI의 다양한 리소스 생성 인터페이스를 삭제하고, RHICreate를 균일하게 사용합니다.
RayTraceDispatchIndirect, IsRayTracingShaderFrequency 등과 같은 레이 트레이싱 유형 및 인터페이스를 개선합니다.
GlobalUniformBuffer가 StaticUniformBuffer로 변경되었습니다.
ERHIFeatureLevel은 플랫폼 관련 감지 또는 데이터 인터페이스를 개선하기 위해 SM6를 추가합니다.
PCD3D_SM6의 ShaderPlatform을 추가하고 PS4, XBOXONE_D3D12, OPENGL_ES31_EXT 등과 같은 일부 ShaderPlatform을 삭제했습니다.
FGenericDataDrivenShaderPlatformInfo는 많은 태그와 해당 획득 인터페이스를 추가합니다.
EShaderCodeResourceBindingType, EUniformBufferBindingFlags, FRHITextureCreateInfo, ERHITextureMetaDataAccess, ERHITextureSRVOverrideSR GBType, FRHITextureSRVCreateInfo, FRHITextureUAVCreateInfo, FRHIBufferCreateInfo, FRHIBufferSRVCreateInfo, FRHIBufferUAVCreateInfo 및 기타 유형과 인터페이스가 추가되었습니다.
FRHIGraphicsPipelineState, FRHITextureReference, RHIValidation 및 기타 유형의 인터페이스를 개선합니다.
RHITransientResourceAllocator 및 RHIValidationTransientResourceAllocator와 같은 리소스 변환 모듈이 추가되었습니다.
FTransientState, FGlobalUniformBuffers, FPipelineState 및 기타 유형을 수정하거나 추가합니다.
RHI코어:
새로운 모듈에는 RHICoreShader, RHICoreTransientResourceAllocator, RHIPoolAllocator 및 기타 모듈이 포함됩니다.
6.3.3 렌더러
인스턴스화 절단 모듈 추가: FInstanceCullingRdgParams, EInstanceCullingMode, FInstanceCullingContext, FInstanceCullingManager 및 주로 Nanite 기술에 사용되는 기타 유형을 포함하는 InstanceCulling, InstanceCullingManager 등.
가상 텍스처는 데이터 읽기 및 쓰기와 FVirtualTextureFeedbackBuffer, RenderPages, RenderPagesStandAlone 등과 같은 인터페이스를 추가하거나 향상시킵니다.
전역 거리 필드 데이터(FGlobalDistanceFieldParameterData)는 Mipmap 및 VT 데이터와 인터페이스를 추가합니다.
HairStrand는 EHairBindingType, EHairInterpolationType, FHairStrandsInstance 및 기타 유형을 추가합니다.
FVertexFactoryShaderPermutationParameters 유형을 추가하거나 개선합니다.
MeshPass프로세서:
EMeshPass는 VSMShadowDepth, LumenCardCapture 및 EditorLevelInstance와 같은 전용 채널을 추가합니다.
EFVisibleMeshDrawCommandFlags, FCompareFMeshDrawCommands, FMeshPassProcessorRenderState 및 기타 유형이 추가되었습니다.
오버헤드를 줄이기 위해 그리기 명령의 병렬 처리가 필요하지 않은 상황을 처리하기 위해 FSimpleMeshDrawCommandPass 유형을 추가했습니다.
FVisibleMeshDrawCommand는 GPUCull을 위해 EFVisibleMeshDrawCommandFlags 및 FMeshDrawCommandSortKey, InRunArray 등을 추가합니다.
NewVisibleMeshDrawCommand.Setup 스테이지가 FDynamicPassMeshDrawListContext의 FinalizeCommand 스테이지에 추가되었습니다.
SetInstancedViewUniformBuffer, SetPassUniformBuffer, GetInstancedViewUniformBuffer 및 기타 균일버퍼 인터페이스를 버리세요.
Nanite 모듈이 추가되었습니다.
NaniteRender 추가: FNaniteCommandInfo, ENaniteMeshPass, FNaniteDrawListContext, FCullingContext, FRasterContext, FRasterResults, FNaniteShader, F NaniteMaterialVS, FNaniteMeshProcessor, FNaniteMaterialTables, ERasterTechnique, ERasterScheduling, EOutputBufferMode, FPackedView 등 유형 및 처리 인터페이스.
FPrimitiveFlagsCompact는 bIsNaniteMesh 플래그를 추가합니다.
FPrimitiveSceneInfo는 NaniteCommandInfos, NaniteMaterialIds, LumenPrimitiveIndex 및 CachedRayTracingMeshCommandsHashPerLOD, bRegisteredWithVelocityData, InstanceDataOffset, NumInstanceDataEntries 인스턴스화 및 광 추적 관련 데이터 및 처리 인터페이스를 추가합니다.
새로운 루멘 모듈:
DiffuseIndirect, Scene, HardwareRayTracing, Mesh, Probe, Radiance, Radiosity, TranslucencyVolume, Voxel, 데이터 구조, 도구 상자 등을 포함하여 많은 모듈이 관련되어 있습니다.
SHADER_PARAMETER_TEXTURE를 SHADER_PARAMETER_RDG_TEXTURE로 바꾸는 등 기존 셰이더 바인딩 인터페이스를 RDG로 바꿉니다.
RuntimeVirtualTextureProducer, FSceneTextureExtracts, EMobileSceneTextureSetupMode 및 Strata 계층을 추가하거나 개선합니다.
TSR(Temporal Super Resolution) 추가 등 포스트 프로세스 파이프라인가 향상되었습니다.
RTGI, RTAO, RTR, RTShadow, RTSkyLight 등과 같은 향상된 조명 추적 모듈
DeferredShadingRenderer:
Lumen, IndirectLightRender, SSRT, TranslucencyLightingVolume 및 기타 관련 모듈, 유형 및 단계가 추가되었습니다.
FLumenCardRenderer, TPipelineState, EDiffuseIndirectMethod, EAmbientOcclusionMethod, EReflectionsMethod, FPerViewPipelineState, FFamilyPipelineState 및 기타 유형이 추가되었습니다.
BasePass, DepthPass, SDF, GPUScene, GlobalDistanceField, BVH, generateConservativeDepthBuffer, LightRendering, InlectLightRendering, MeshDrawCommands, Shader, ScreenSpace, Shadow, MobileRender 등과 같은 렌더링 모듈이 추가되거나 향상되었습니다.
UE4.26부터 5.0EA까지 중요하고 기본적인 렌더링 모듈이 다양한 크기로 수정되었습니다.
6.3.4 엔진
IRenderCaptureProvider, NaniteResources, NaniteStreamingManager 및 기타 모듈이 추가되었습니다.
레벨 인스턴스화의 데이터 읽기 및 쓰기, 패키징, 렌더링, 편집기 등을 처리하는 새로운 LevelInstance 모듈을 추가했습니다.
다양한 리소스 컴파일, 인스턴스화, 버퍼와 관련된 ActorReferencesUtils, AssetCompilingManager, AsyncCompilationHelpers, CanvasRender, ComputeKernelCollection, DerivedMeshDataTaskUtils, InstancedStaticMeshDelegates, In nstanceUniformShaderParameters, MeshCardRepresentation, NaniteSceneProxy, ObjectCacheContext, StaticMeshCompiler, TextureCompiler 및 기타 모듈을 추가했습니다. 메쉬,나나이트 등
PrimitiveSceneProxy:
LevelInstance, bLevelInstanceEditingState, TranslucencySortDistanceOffset 및 MeshCardRepresentation과 같은 데이터 및 인터페이스를 추가했습니다.
supportNaniteRendering, IsNaniteMesh, bSupportsMeshCardRepresentation, IsAlwaysVisible, GetPrimitiveInstances, RayTracingGroupId, RayTracingGroupCullingPriority 및 기타 데이터 및 인터페이스가 추가되었습니다.
SceneInterface 및 SceneManagement:
FInstanceCullingManagerResources 같은 유형을 추가하고 FPrimitiveUniformShaderParameters 대신 FGPUScenePrimitiveCollector를 사용하세요.
HairStrand, PhysicsField, LumenSceneCard, ComputeFramework 및 기타 작업 인터페이스가 추가되었습니다.
장면뷰:
ComputeNearPlane 및 GetScreenPositionScaleBias와 같은 인터페이스가 추가되었습니다.
FViewShaderParameters, GeneralPurposeTweak2, PrecomputedIndirectLightingColorScale, GlobalDistanceField, VirtualTexture, PhysicsField, Lumen, Instance, Page 및 기타 셰이더 바인딩이 추가되었습니다.
강화된 거리 필드, GPUSkinCache, Material, StaticMesh, SkeletalMesh, Texture 및 기타 유형과 인터페이스.
6.3.5 셰이더
- 추가된 나나이트:
ClusterCulling, Culling, HZBCull, InstanceCulling, MaterialCulling, Shadow, GBuffer, Impsoter, DataDecode, DataPacked, Rasterizer, WritePixel 등이 추가되었습니다.
- 추가된 루멘:
C++와 마찬가지로 많은 모듈이 관련되어 있습니다. 요약하면 DiffuseIndirect, Scene, HardwareRayTracing, Mesh, Probe, Radiance, Radiosity, TranslucencyVolume, Voxel, 데이터 구조, 도구 상자 등이 포함됩니다.
LumenProbe의 계층 구조, 폐색, 인덱스, 샘플 등을 처리하기 위해 FinalGather가 추가되었습니다.
새로운 지층:
DeferredLighting, EnvironmentLighting, Evaluation, ForwardLighting, Material 및 기타 모듈.
- VirtualShadowMap을 추가했습니다:
각 페이지 그리기 명령을 빌드하기 위한 새 모듈을 추가했습니다.
캐시 관리가 추가되었습니다.
페이지, 프로젝션, SMRT 및 기타 모듈이 추가되었습니다.
InstanceCulling을 추가했습니다.
BuildInstanceDrawCommands 모듈은 GPUScene에 Compute Shader의 동적 자르기 및 그리기 지침 생성 기능을 제공합니다.
CullInstances 모듈, 시야 외부 또는 컴퓨트 셰이더에서 가려진 프리미티브를 클리핑합니다.
InstanceCullingCommon 모듈은 기본 유형과 인터페이스를 정의합니다.
HairCards, HairScatter, BsdfPlot, ClusterCulling, Shadow, DeepShadow, EnvironmentLighting, GBuffer, Material, Visibility, Voxel 및 기타 모듈과 같은 향상된 HairStrands 모듈.
향상된 PathTracing, RayTracing, SSD, SSRT, TAA 및 기타 모듈.
LPV 모듈을 제거했습니다.
AnisotropyPass, BasePass, MobileBasePass, BRDF, BurleySSS, CapsuleLigh, RectLight, TranslucentLighting, ClusteredDeferredShading, LightGrid, ForwardLighting, DeferredLight, DiffuseIndirect, DistanceFieldAO, DistanceFiel dLighting, DistanceFieldShadow, GlobalDistanceField, Math, 데칼과 같은 기본, 재질, 음영, 조명, 그림자, 특수 패스 및 기타 모듈을 개선합니다. GpuSkin, Halton, MonteCarlo, HZB, LocalVertexFactory, MaterialTemplate, Particle, PlanarReflection, PostProcess, Reflection, SceneData, ShadingCommon, ShadingModels, Shadow, Volumetric 등
6.3.6 UE5 렌더링 시스템 요약
이전 섹션에서 볼 수 있듯이 가장 큰 변화는 Nanite, Lumen, VSM, InstanceCull, LevelInstance 및 기타 모듈과 기술이 추가되고 엔진 및 렌더 모듈의 관련 유형과 인터페이스가 수정된다는 것입니다.
RHI 레이어에 대한 변경 사항은 주로 다양한 버텍스 및 인덱스 버퍼를 FRHIBuffer로 통합하는 것입니다.
렌더러 레이어는 레이 트레이싱, 특히 화면 공간 레이 트레이싱을 향상시켜 LPV를 제거하는 동시에 거리 필드의 다양한 적용을 향상시킵니다.
엔진 레이어는 주로 RHI 및 렌더러 레이어의 변경 사항을 중심으로 수정 및 조정되었습니다.
6.4 나나이트
이번 장에서는 UE5의 Nanite 가상 마이크로폴리곤의 전처리, 렌더링, 최적화 기술에 대해 설명합니다.
UE5 EA 소스 코드 프로젝트에서 "Nanite"라는 단어를 검색하면 3026개의 일치 항목에 대해 195개의 파일이 발견되었습니다.

적용 범위가 너무 넓기 때문에 모든 세부 사항을 설명하는 것은 물론 불가능합니다. 심사 후 저자는 다음 모듈의 Nanite 소스 코드를 분석하는 데 중점을 둘 것입니다.
편집자의 Nanite Mesh 구축 과정.
엔진 모듈은 나나이트 자원의 관리, 로딩, 조립 등에 관한 모듈입니다.
Renderer 모듈은 Nanite의 렌더링 프로세스 및 최적화 기술에 관한 것입니다.
Shader의 Nanite 렌더링 단계 및 알고리즘.
6.4.1 나나이트 기본
본 절에서는 나나이트와 관련된 기본 개념, 종류, 기본 지식을 중심으로 설명합니다.
6.4.1.1 FMeshNaniteSettings
// Engine\Source\Runtime\Engine\Classes\Engine\EngineTypes.h
// 그림자(Shadow)메서드 .
namespace EShadowMapMethod
{
enum Type
{
// 의 그림자(Shadow). 클리핑/클램핑(Clipping), 의 성능 .
ShadowMaps UMETA(DisplayName = "Shadow Maps"),
// 로 그림자(Shadow)렌더링(Render)까지 뎁스 , 을(를) 활용하여 설정(Set)품질 의 회 투영/프로젝션 . 및 Nanite을(를) 활용하여 ,구현 클리핑/클램핑(Clipping).
VirtualShadowMaps UMETA(DisplayName = "Virtual Shadow Maps (Beta)")
};
}
// 적용 Nanite데이터 의 구성(Configuration).
struct FMeshNaniteSettings
{
// 는 활성화(Enable)Nanite.
uint8 bEnabled : 1;
// 위치 정밀도 . 로 2^(-PositionPrecision) cm. MIN_int32테이블 설정(Set).
int32 PositionPrecision;
// 로부터 LOD0의 . 1.0테이블 , 0.0테이블 .
float PercentTriangles;
FMeshNaniteSettings(): bEnabled(false), PositionPrecision(MIN_int32), PercentTriangles(0.0f){}
FMeshNaniteSettings(const FMeshNaniteSettings& Other);
bool operator==(const FMeshNaniteSettings& Other) const;
bool operator!=(const FMeshNaniteSettings& Other) const;
};6.4.1.2 정적 메시
// Engine\Source\Runtime\Engine\Classes\Engine\StaticMesh.h
class UStaticMesh : public UStreamableRenderAsset, (......)
{
(......)
public:
// 정적 의 Nanite구성(Configuration)데이터 .
FMeshNaniteSettings NaniteSettings;
// 만약 유효(Valid)의 Nanite렌더링(Render)데이터 이면 반환(Return)true.
bool HasValidNaniteData() const
{
if (const FStaticMeshRenderData* SMRenderData = GetRenderData())
{
return SMRenderData->NaniteResources.PageStreamingStates.Num() > 0;
}
return false;
}
(......)
// 해상도 의 모델 의 인터페이스 .
FStaticMeshSourceModel& GetHiResSourceModel();
const FStaticMeshSourceModel& GetHiResSourceModel() const;
FStaticMeshSourceModel&& MoveHiResSourceModel();
void SetHiResSourceModel(FStaticMeshSourceModel&& SourceModel);
bool LoadHiResMeshDescription(FMeshDescription& OutMeshDescription) const;
bool CloneHiResMeshDescription(FMeshDescription& OutMeshDescription) const;
FMeshDescription* CreateHiResMeshDescription();
FMeshDescription* CreateHiResMeshDescription(FMeshDescription MeshDescription);
FMeshDescription* GetHiResMeshDescription() const;
bool IsHiResMeshDescriptionValid() const;
void CommitHiResMeshDescription(const FCommitMeshDescriptionParams& Params);
void ClearHiResMeshDescription();
(......)
private:
// 해상도 의 모델 .
FStaticMeshSourceModel HiResSourceModel;
(......)
};
// Engine\Source\Runtime\Engine\Public\StaticMeshResources.h
// 정적 의 렌더링(Render)데이터 .
class FStaticMeshRenderData
{
public:
(......)
// Nanite렌더링(Render)리소스 .
Nanite::FResources NaniteResources;
(......)
};6.4.1.3 NaniteResource
// Engine\Source\Runtime\Engine\Public\Rendering\NaniteResources.h
// 개수 의 상수(Constant).
#define MAX_STREAMING_REQUESTS ( 128u * 1024u )
#define MAX_CLUSTER_TRIANGLES 128
#define MAX_CLUSTER_VERTICES 256
#define MAX_CLUSTER_INDICES ( MAX_CLUSTER_TRIANGLES * 3 )
#define MAX_NANITE_UVS 4
#define NUM_ROOT_PAGES 1u
// 는 을(를) 활용하여 .
#define USE_STRIP_INDICES 1
// CLUSTER상수(Constant).
#define CLUSTER_PAGE_GPU_SIZE_BITS 17
#define CLUSTER_PAGE_GPU_SIZE ( 1 << CLUSTER_PAGE_GPU_SIZE_BITS )
#define CLUSTER_PAGE_DISK_SIZE ( CLUSTER_PAGE_GPU_SIZE * 2 )
#define MAX_CLUSTERS_PER_PAGE_BITS 10
#define MAX_CLUSTERS_PER_PAGE_MASK ( ( 1 << MAX_CLUSTERS_PER_PAGE_BITS ) - 1 )
#define MAX_CLUSTERS_PER_PAGE ( 1 << MAX_CLUSTERS_PER_PAGE_BITS )
#define MAX_CLUSTERS_PER_GROUP_BITS 9
#define MAX_CLUSTERS_PER_GROUP_MASK ( ( 1 << MAX_CLUSTERS_PER_GROUP_BITS ) - 1 )
#define MAX_CLUSTERS_PER_GROUP ( ( 1 << MAX_CLUSTERS_PER_GROUP_BITS ) - 1 )
#define MAX_CLUSTERS_PER_GROUP_TARGET 128
// 레벨 단계 , GPU, 인스턴스 , 의 상수(Constant).
#define MAX_HIERACHY_CHILDREN_BITS 6
#define MAX_HIERACHY_CHILDREN ( 1 << MAX_HIERACHY_CHILDREN_BITS )
#define MAX_GPU_PAGES_BITS 14
#define MAX_GPU_PAGES ( 1 << MAX_GPU_PAGES_BITS )
#define MAX_INSTANCES_BITS 24
#define MAX_INSTANCES ( 1 << MAX_INSTANCES_BITS )
#define MAX_NODES_PER_PRIMITIVE_BITS 16
#define MAX_RESOURCE_PAGES_BITS 20
#define MAX_RESOURCE_PAGES (1 << MAX_RESOURCE_PAGES_BITS)
#define MAX_GROUP_PARTS_BITS 3
#define MAX_GROUP_PARTS_MASK ((1 << MAX_GROUP_PARTS_BITS) - 1)
#define MAX_GROUP_PARTS (1 << MAX_GROUP_PARTS_BITS)
#define PERSISTENT_CLUSTER_CULLING_GROUP_SIZE 64
// BVH
#define MAX_BVH_NODE_FANOUT_BITS 3
#define MAX_BVH_NODE_FANOUT (1 << MAX_BVH_NODE_FANOUT_BITS)
#define MAX_BVH_NODES_PER_GROUP (PERSISTENT_CLUSTER_CULLING_GROUP_SIZE / MAX_BVH_NODE_FANOUT)
#define NUM_CULLING_FLAG_BITS 3
#define NUM_PACKED_CLUSTER_FLOAT4S 8
#define MAX_POSITION_QUANTIZATION_BITS 21 // (21*3 = 63) < 64
#define NORMAL_QUANTIZATION_BITS 9
#define MAX_TEXCOORD_QUANTIZATION_BITS 15
#define MAX_COLOR_QUANTIZATION_BITS 8
#define NUM_STREAMING_PRIORITY_CATEGORY_BITS 2
#define STREAMING_PRIORITY_CATEGORY_MASK ((1u << NUM_STREAMING_PRIORITY_CATEGORY_BITS) - 1u)
#define VIEW_FLAG_HZBTEST 0x1
#define MAX_TRANSCODE_GROUPS_PER_PAGE 128
#define VERTEX_COLOR_MODE_WHITE 0
#define VERTEX_COLOR_MODE_CONSTANT 1
#define VERTEX_COLOR_MODE_VARIABLE 2
#define NANITE_USE_SCRATCH_BUFFERS 1
#define NANITE_CLUSTER_FLAG_LEAF 0x1
namespace Nanite
{
// 벡터 .
struct FUIntVector
{
uint32 X, Y, Z;
bool operator==(const FUIntVector& V) const;
FORCEINLINE friend FArchive& operator<<(FArchive& Ar, FUIntVector& V);
};
// 의 레벨 단계 포인트 .
struct FPackedHierarchyNode
{
FSphere LODBounds[MAX_BVH_NODE_FANOUT]; // 을(를) 활용하여 LOD바운딩 박스(AABB).
struct
{
FVector BoxBoundsCenter;
uint32 MinLODError_MaxParentLODError;
} Misc0[MAX_BVH_NODE_FANOUT];
struct
{
FVector BoxBoundsExtent;
uint32 ChildStartReference;
} Misc1[MAX_BVH_NODE_FANOUT];
struct
{
uint32 ResourcePageIndex_NumPages_GroupPartSize;
} Misc2[MAX_BVH_NODE_FANOUT];
};
// 머티리얼(Material).
struct FMaterialTriangle
{
uint32 Index0;
uint32 Index1;
uint32 Index2;
uint32 MaterialIndex;
uint32 RangeCount;
};
// 로부터 Value 내에서 페치/가져오기(Fetch) 및 의 .
uint32 GetBits(uint32 Value, uint32 NumBits, uint32 Offset)
{
uint32 Mask = (1u << NumBits) - 1u;
return (Value >> Offset) & Mask;
}
// 및 의 까지 Value 내에서 .
void SetBits(uint32& Value, uint32 Bits, uint32 NumBits, uint32 Offset)
{
uint32 Mask = (1u << NumBits) - 1u;
Mask <<= Offset;
Value = (Value & ~Mask) | (Bits << Offset);
}
// GPU을(를) 활용하여 의 의 Cluster.
struct FPackedCluster
{
// 래스터라이제이션 의 데이터 .
FIntVector QuantizedPosStart;
uint32 NumVerts_PositionOffset; // NumVerts:9, PositionOffset:23
FVector MeshBoundsMin;
uint32 NumTris_IndexOffset; // NumTris:8, IndexOffset: 24
FVector MeshBoundsDelta;
uint32 BitsPerIndex_QuantizedPosShift_PosBits; // BitsPerIndex:4, QuantizedPosShift:6, QuantizedPosBits:5.5.5
// 클리핑/클램핑(Clipping)의 데이터 .
FSphere LODBounds;
FVector BoxBoundsCenter;
uint32 LODErrorAndEdgeLength;
FVector BoxBoundsExtent;
uint32 Flags;
// 머티리얼(Material)의 데이터 .
uint32 AttributeOffset_BitsPerAttribute; // AttributeOffset: 22, BitsPerAttribute: 10
uint32 DecodeInfoOffset_NumUVs_ColorMode; // DecodeInfoOffset: 22, NumUVs: 3, ColorMode: 2
uint32 UV_Prec; // U0:4, V0:4, U1:4, V1:4, U2:4, V2:4, U3:4, V3:4
uint32 PackedMaterialInfo;
uint32 ColorMin;
uint32 ColorBits; // R:4, G:4, B:4, A:4
uint32 GroupIndex; // Debug only
uint32 Pad0;
uint32 GetNumVerts() const { return GetBits(NumVerts_PositionOffset, 9, 0); }
uint32 GetPositionOffset() const { return GetBits(NumVerts_PositionOffset, 23, 9); }
uint32 GetNumTris() const { return GetBits(NumTris_IndexOffset, 8, 0); }
uint32 GetIndexOffset() const { return GetBits(NumTris_IndexOffset, 24, 8); }
uint32 GetBitsPerIndex() const { return GetBits(BitsPerIndex_QuantizedPosShift_PosBits, 4, 0); }
uint32 GetQuantizedPosShift() const { return GetBits(BitsPerIndex_QuantizedPosShift_PosBits, 6, 4); }
uint32 GetPosBitsX() const { return GetBits(BitsPerIndex_QuantizedPosShift_PosBits, 5, 10); }
uint32 GetPosBitsY() const { return GetBits(BitsPerIndex_QuantizedPosShift_PosBits, 5, 15); }
uint32 GetPosBitsZ() const { return GetBits(BitsPerIndex_QuantizedPosShift_PosBits, 5, 20); }
uint32 GetAttributeOffset() const { return GetBits(AttributeOffset_BitsPerAttribute, 22, 0); }
uint32 GetBitsPerAttribute() const { return GetBits(AttributeOffset_BitsPerAttribute, 10, 22); }
void SetNumVerts(uint32 NumVerts) { SetBits(NumVerts_PositionOffset, NumVerts, 9, 0); }
void SetPositionOffset(uint32 Offset) { SetBits(NumVerts_PositionOffset, Offset, 23, 9); }
void SetNumTris(uint32 NumTris) { SetBits(NumTris_IndexOffset, NumTris, 8, 0); }
void SetIndexOffset(uint32 Offset) { SetBits(NumTris_IndexOffset, Offset, 24, 8); }
void SetBitsPerIndex(uint32 BitsPerIndex) { SetBits(BitsPerIndex_QuantizedPosShift_PosBits, BitsPerIndex, 4, 0); }
void SetQuantizedPosShift(uint32 PosShift) { SetBits(BitsPerIndex_QuantizedPosShift_PosBits, PosShift, 6, 4); }
void SetPosBitsX(uint32 NumBits) { SetBits(BitsPerIndex_QuantizedPosShift_PosBits, NumBits, 5, 10); }
void SetPosBitsY(uint32 NumBits) { SetBits(BitsPerIndex_QuantizedPosShift_PosBits, NumBits, 5, 15); }
void SetPosBitsZ(uint32 NumBits) { SetBits(BitsPerIndex_QuantizedPosShift_PosBits, NumBits, 5, 20); }
void SetAttributeOffset(uint32 Offset) { SetBits(AttributeOffset_BitsPerAttribute, Offset, 22, 0); }
void SetBitsPerAttribute(uint32 Bits) { SetBits(AttributeOffset_BitsPerAttribute, Bits, 10, 22); }
void SetDecodeInfoOffset(uint32 Offset) { SetBits(DecodeInfoOffset_NumUVs_ColorMode, Offset, 22, 0); }
void SetNumUVs(uint32 Num) { SetBits(DecodeInfoOffset_NumUVs_ColorMode, Num, 3, 22); }
void SetColorMode(uint32 Mode) { SetBits(DecodeInfoOffset_NumUVs_ColorMode, Mode, 2, 22+3); }
};
// 상태 .
struct FPageStreamingState
{
uint32 BulkOffset;
uint32 BulkSize;
uint32 PageUncompressedSize;
uint32 DependenciesStart;
uint32 DependenciesNum;
};
// 레벨 단계 .
class FHierarchyFixup
{
public:
FHierarchyFixup() {}
FHierarchyFixup( uint32 InPageIndex, uint32 NodeIndex, uint32 ChildIndex, uint32 InClusterGroupPartStartIndex, uint32 PageDependencyStart, uint32 PageDependencyNum )
{
PageIndex = InPageIndex;
HierarchyNodeAndChildIndex = ( NodeIndex << MAX_HIERACHY_CHILDREN_BITS ) | ChildIndex;
ClusterGroupPartStartIndex = InClusterGroupPartStartIndex;
PageDependencyStartAndNum = (PageDependencyStart << MAX_GROUP_PARTS_BITS) | PageDependencyNum;
}
uint32 GetPageIndex() const { return PageIndex; }
uint32 GetNodeIndex() const { return HierarchyNodeAndChildIndex >> MAX_HIERACHY_CHILDREN_BITS; }
uint32 GetChildIndex() const { return HierarchyNodeAndChildIndex & ( MAX_HIERACHY_CHILDREN - 1 ); }
uint32 GetClusterGroupPartStartIndex() const { return ClusterGroupPartStartIndex; }
uint32 GetPageDependencyStart() const { return PageDependencyStartAndNum >> MAX_GROUP_PARTS_BITS; }
uint32 GetPageDependencyNum() const { return PageDependencyStartAndNum & MAX_GROUP_PARTS_MASK; }
uint32 PageIndex;
uint32 HierarchyNodeAndChildIndex;
uint32 ClusterGroupPartStartIndex;
uint32 PageDependencyStartAndNum;
};
// Cluster.
class FClusterFixup
{
public:
FClusterFixup() {}
FClusterFixup( uint32 PageIndex, uint32 ClusterIndex, uint32 PageDependencyStart, uint32 PageDependencyNum )
{
PageAndClusterIndex = ( PageIndex << MAX_CLUSTERS_PER_PAGE_BITS ) | ClusterIndex;
PageDependencyStartAndNum = (PageDependencyStart << MAX_GROUP_PARTS_BITS) | PageDependencyNum;
}
uint32 GetPageIndex() const { return PageAndClusterIndex >> MAX_CLUSTERS_PER_PAGE_BITS; }
uint32 GetClusterIndex() const { return PageAndClusterIndex & (MAX_CLUSTERS_PER_PAGE - 1u); }
uint32 GetPageDependencyStart() const { return PageDependencyStartAndNum >> MAX_GROUP_PARTS_BITS; }
uint32 GetPageDependencyNum() const { return PageDependencyStartAndNum & MAX_GROUP_PARTS_MASK; }
uint32 PageAndClusterIndex;
uint32 PageDependencyStartAndNum;
};
// .
struct FPageDiskHeader
{
uint32 GpuSize;
uint32 NumClusters;
uint32 NumRawFloat4s;
uint32 NumTexCoords;
uint32 DecodeInfoOffset;
uint32 StripBitmaskOffset;
uint32 VertexRefBitmaskOffset;
};
// Cluster.
struct FClusterDiskHeader
{
uint32 IndexDataOffset;
uint32 VertexRefDataOffset;
uint32 PositionDataOffset;
uint32 AttributeDataOffset;
uint32 NumPrevRefVerticesBeforeDwords;
uint32 NumPrevNewVerticesBeforeDwords;
};
// Chunk.
class FFixupChunk //TODO: rename to something else
{
public:
struct FHeader
{
uint16 NumClusters = 0;
uint16 NumHierachyFixups = 0;
uint16 NumClusterFixups = 0;
uint16 Pad = 0;
} Header;
uint8 Data[ sizeof(FHierarchyFixup) * MAX_CLUSTERS_PER_PAGE + sizeof( FClusterFixup ) * MAX_CLUSTERS_PER_PAGE ];
FClusterFixup& GetClusterFixup( uint32 Index ) const { check( Index < Header.NumClusterFixups ); return ( (FClusterFixup*)( Data + Header.NumHierachyFixups * sizeof( FHierarchyFixup ) ) )[ Index ]; }
FHierarchyFixup& GetHierarchyFixup( uint32 Index ) const { check( Index < Header.NumHierachyFixups ); return ((FHierarchyFixup*)Data)[ Index ]; }
uint32 GetSize() const { return sizeof( Header ) + Header.NumHierachyFixups * sizeof( FHierarchyFixup ) + Header.NumClusterFixups * sizeof( FClusterFixup ); }
};
// 인스턴스 드로우/렌더(Draw)파라미터 .
struct FInstanceDraw
{
uint32 InstanceId;
uint32 ViewId;
};
// Nanite렌더링(Render)리소스 .
struct FResources
{
// 상태 .
TArray< uint8 > RootClusterPage; // Root page is loaded on resource load, so we always have something to draw.
FByteBulkData StreamableClusterPages; // Remaining pages are streamed on demand.
TArray< uint16 > ImposterAtlas;
TArray< FPackedHierarchyNode > HierarchyNodes;
TArray< uint32 > HierarchyRootOffsets;
TArray< FPageStreamingState > PageStreamingStates;
TArray< uint32 > PageDependencies;
int32 PositionPrecision = 0;
bool bLZCompressed = false;
// 상태 .
uint32 RuntimeResourceID = 0xFFFFFFFFu;
int32 HierarchyOffset = INDEX_NONE;
int32 RootPageIndex = INDEX_NONE;
uint32 NumHierarchyNodes = 0;
(......)
ENGINE_API void InitResources();
ENGINE_API bool ReleaseResources();
ENGINE_API void Serialize(FArchive& Ar, UObject* Owner);
};
// GPUBuffer, Nanite리소스 데이터 .
class FGlobalResources : public FRenderResource
{
public:
struct PassBuffers
{
// 의 (즉, 클리핑/클램핑(Clipping)의 )포인트 및 Cluster버퍼 .
TRefCountPtr<FRDGPooledBuffer> CandidateNodesAndClustersBuffer;
TRefCountPtr<FRDGPooledBuffer> StatsRasterizeArgsSWHWBuffer;
};
uint32 StatsRenderFlags = 0;
uint32 StatsDebugFlags = 0;
public:
virtual void InitRHI() override;
virtual void ReleaseRHI() override;
ENGINE_API void Update(FRDGBuilder& GraphBuilder); // Called once per frame before any Nanite rendering has occurred.
ENGINE_API static uint32 GetMaxCandidateClusters();
ENGINE_API static uint32 GetMaxVisibleClusters();
ENGINE_API static uint32 GetMaxNodes();
(......)
private:
PassBuffers MainPassBuffers;
PassBuffers PostPassBuffers;
class FVertexFactory* VertexFactory = nullptr;
TRefCountPtr<FRDGPooledBuffer> StatsBuffer;
// Dummy structured buffer with stride8
TRefCountPtr<FRDGPooledBuffer> StructureBufferStride8;
#if NANITE_USE_SCRATCH_BUFFERS
TRefCountPtr<FRDGPooledBuffer> PrimaryVisibleClustersBuffer;
// Used for scratch memory (transient only)
TRefCountPtr<FRDGPooledBuffer> ScratchVisibleClustersBuffer;
TRefCountPtr<FRDGPooledBuffer> ScratchOccludedInstancesBuffer;
#endif
};
extern ENGINE_API TGlobalResource< FGlobalResources > GGlobalResources;
} // namespace Nanite6.4.1.4 클러스터, 클러스터 그룹, 페이지
Nanite 데이터를 구성하는 데에는 많은 개념이 관련되어 있으므로 여기서는 이에 중점을 두겠습니다.
Nanite에 관련된 핵심이자 가장 기본적인 개념은 Cluster입니다. 클러스터는 인접한 삼각형의 집합입니다.

위: 일반 렌더링; 중간: 삼각형 시각화; 하단: 클러스터 시각화.
클러스터는 인접한 클러스터 또는 인접한 LOD 클러스터와 동적으로 일괄 처리될 수 있으므로 이미지가 서로 충돌하지 않고 명백한 점프가 생성되지 않습니다. 구체적으로 이 동영상은 다음과 같습니다.
클러스터 기술은 UE에만 고유한 것은 아니지만 이전에는 Ubisoft 및 Frostbite Engine에서 사용되었습니다. 자세한 내용은 GPU 기반 렌더링 파이프라인 및 컴퓨팅을 통한 그래픽 파이프라인 최적화 문서를 참조하세요.
다음은 Cluster 및 기타 기본 유형의 정의입니다.
// Engine\Source\Developer\NaniteBuilder\Private\Cluster.h
// , 모델 로 개 .
class FCluster
{
public:
FCluster();
FCluster( FCluster& SrcCluster, uint32 TriBegin, uint32 TriEnd, const TArray< uint32 >& TriIndexes );
FCluster( const TArray< const FCluster*, TInlineAllocator<16> >& MergeList );
FCluster(const TArray< FStaticMeshBuildVertex >& InVerts,const TArrayView< const uint32 >& InIndexes,
const TArrayView< const int32 >& InMaterialIndexes,const TBitArray<>& InBoundaryEdges,uint32 TriBegin, uint32 TriEnd, const TArray< uint32 >& TriIndexes, uint32 NumTexCoords, bool bHasColors );
// Cluster, 로써 의 개수 .
float Simplify( uint32 NumTris );
// Cluster.
void Split( FGraphPartitioner& Partitioner ) const;
(......)
static const uint32 ClusterSize = 128;
// .
uint32 NumVerts = 0;
uint32 NumTris = 0;
uint32 NumTexCoords = 0;
bool bHasColors = false;
// 데이터 .
TArray< float > Verts; // 포인트
TArray< uint32 > Indexes; //
TArray< int32 > MaterialIndexes; // 머티리얼(Material).
TBitArray<> BoundaryEdges; // .
TBitArray<> ExternalEdges; // 확장 .
uint32 NumExternalEdges; // 확장 개수 .
TMap< uint32, uint32 > AdjacentClusters; // 인접(Neighbor)의 Cluster.
// 바운딩 박스(AABB)데이터 .
FBounds Bounds; // 바운딩 박스(AABB).
FSphere SphereBounds;
FSphere LODBounds;
FVector MeshBoundsMin; //바운딩 박스(AABB).
FVector MeshBoundsDelta;
float SurfaceArea = 0.0f;
uint32 GUID = 0;
int32 MipLevel = 0;
// 양자화 위치 의 데이터 .
TArray<FIntVector> QuantizedPositions;
FIntVector QuantizedPosStart = { 0u, 0u, 0u };
uint32 QuantizedPosShift = 0u;
FIntVector QuantizedPosBits = {};
float EdgeLength = 0.0f;
float LODError = 0.0f;
// 의 Group데이터 .
uint32 GroupIndex = MAX_uint32;
uint32 GroupPartIndex = MAX_uint32;
uint32 GeneratingGroupIndex= MAX_uint32;
// 머티리얼(Material)범위 .
TArray<FMaterialRange, TInlineAllocator<4>> MaterialRanges;
// 데이터 .
FStripDesc StripDesc;
TArray<uint8> StripIndexData;
};
// Engine\Source\Developer\NaniteBuilder\Private\ClusterDAG.h
// , 개 Cluster.
struct FClusterGroup
{
// 바운딩 박스(AABB).
FSphere Bounds;
FSphere LODBounds;
// .
float MinLODError;
float MaxParentLODError;
// 레벨 단계 및 .
int32 MipLevel;
uint32 MeshIndex;
// 테이블 .
uint32 PageIndexStart;
uint32 PageIndexNum;
// 포인트 .
TArray< uint32 > Children;
friend FArchive& operator<<(FArchive& Ar, FClusterGroup& Group);
};
// Engine\Source\Developer\NaniteBuilder\Private\NaniteEncode.cpp
// FClusterGroup의 또는 .
struct FClusterGroupPart
{
TArray<uint32> Clusters; // 할당(Allocate)정렬 ,저장(Store)개 리스트 。
FBounds Bounds; // 바운딩 박스(AABB).
uint32 PageIndex; // 테이블 .
uint32 GroupIndex; // 의 Group.
uint32 HierarchyNodeIndex; // 레벨 회 구조체 포인트 .
uint32 HierarchyChildIndex; // 레벨 회 구조체 포인트 .
uint32 PageClusterOffset; // 테이블 Cluster리스트 .
};
// 테이블 의 .
struct FPageSections
{
uint32 Cluster = 0;
uint32 MaterialTable = 0;
uint32 DecodeInfo = 0;
uint32 Index = 0;
uint32 Position = 0;
uint32 Attribute = 0;
uint32 GetMaterialTableSize() const { return Align(MaterialTable, 16); }
uint32 GetClusterOffset() const { return 0; }
uint32 GetMaterialTableOffset() const { return Cluster; }
uint32 GetDecodeInfoOffset() const { return Cluster + GetMaterialTableSize(); }
uint32 GetIndexOffset() const { return Cluster + GetMaterialTableSize() + DecodeInfo; }
uint32 GetPositionOffset() const { return Cluster + GetMaterialTableSize() + DecodeInfo + Index; }
uint32 GetAttributeOffset() const { return Cluster + GetMaterialTableSize() + DecodeInfo + Index + Position; }
uint32 GetTotal() const { return Cluster + GetMaterialTableSize() + DecodeInfo + Index + Position + Attribute; }
FPageSections GetOffsets() const
{
return FPageSections{ GetClusterOffset(), GetMaterialTableOffset(), GetDecodeInfoOffset(), GetIndexOffset(), GetPositionOffset(), GetAttributeOffset() };
}
void operator+=(const FPageSections& Other)
{
Cluster += Other.Cluster;
MaterialTable += Other.MaterialTable;
DecodeInfo += Other.DecodeInfo;
Index += Other.Index;
Position += Other.Position;
Attribute += Other.Attribute;
}
};
// Clsuter테이블 .
struct FPage
{
uint32 PartsStartIndex = 0; // FClusterGroupPart.
uint32 PartsNum = 0; // FClusterGroupPart개수 .
uint32 NumClusters = 0; // Cluster개수 .
FPageSections GpuSizes; // GPU해상도/크기 .
};
// 정보 .
struct FEncodingInfo
{
uint32 BitsPerIndex; // 개 의 .
uint32 BitsPerAttribute; // 개 의 .
uint32 UVPrec; // UV정밀도 .
uint32 ColorMode; // 컬러 .
FIntVector4 ColorMin; // 컬러 .
FIntVector4 ColorBits; // 컬러 .
FPageSections GpuSizes; // GPU해상도/크기 .
// UV정보 .
FGeometryEncodingUVInfo UVInfos[MAX_NANITE_UVS];
};
// Cluster Hierarchy의 내에서 포인트 , 을(를) 활용하여 Hierarchy.
struct FIntermediateNode
{
uint32 PartIndex = MAX_uint32; // FClusterGroupPart.
uint32 MipLevel = MAX_int32; // Mip레벨 단계 .
bool bLeaf = false; // 는 포인트 .
FBounds Bound; // 바운딩 박스(AABB).
TArray< uint32 > Children; // 포인트 리스트 .
};
// Engine\Source\Developer\NaniteBuilder\Private\ImposterAtlas.h
// Cluster래스터라이제이션 의 .
class FImposterAtlas
{
public:
static constexpr uint32 AtlasSize = 12;
static constexpr uint32 TileSize = 12;
FImposterAtlas( TArray< uint16 >& InPixels, const FBounds& MeshBounds );
// 래스터라이제이션 Cluster의 까지 FImposterAtlas.
void Rasterize( const FIntPoint& TilePos, const FCluster& Cluster, uint32 ClusterIndex );
private:
TArray< uint16 >& Pixels;
FVector BoundsCenter;
FVector BoundsExtent;
FMatrix GetLocalToImposter( const FIntPoint& TilePos ) const;
};6.4.2 나나이트 데이터 구축
이 섹션에서는 Nanite 정적 데이터의 구성 및 호출 프로세스 등을 포함하여 렌더링 전에 Nanite에서 수행하는 전처리에 대해 주로 설명합니다.
6.4.2.1 BuildNaniteFromHiResSourceModel
Nanite는 FStaticMeshBuilder::Build() 인터페이스와 유사하지만 표면 감소 프로세스를 무시하는 BuildNaniteFromHiResSourceModel 인터페이스를 통해 최고 해상도 모델에서 필요한 데이터를 구축합니다. 이 프로세스를 Nanite-fractional-cut이라고 합니다. 구체적인 과정은 다음과 같습니다.
// Engine\Source\Developer\MeshBuilder\Private\StaticMeshBuilder.cpp
static bool BuildNaniteFromHiResSourceModel(
UStaticMesh* StaticMesh,
const FMeshNaniteSettings NaniteSettings,
FBoxSphereBounds& HiResBoundsOut,
Nanite::FResources& NaniteResourcesOut)
{
// 무시/스킵(Ignore)해상도 의 정적 .
if (ensure(StaticMesh->IsHiResMeshDescriptionValid()) == false)
{
return false;
}
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::BuildNaniteFromHiResSourceModel);
// 페치/가져오기(Fetch)모델 데이터
FMeshDescription HiResMeshDescription = *StaticMesh->GetHiResMeshDescription();
FStaticMeshSourceModel& HiResSrcModel = StaticMesh->GetHiResSourceModel();
FMeshBuildSettings& HiResBuildSettings = HiResSrcModel.BuildSettings;
// 계산/산출(Calculate), 라이팅 UV.
FMeshDescriptionHelper MeshDescriptionHelper(&HiResBuildSettings);
MeshDescriptionHelper.SetupRenderMeshDescription(StaticMesh, HiResMeshDescription);
// 의 RenderData데이터 , 로써 까지 의 Nanite스테이지 .
FStaticMeshRenderData HiResTempRenderData;
HiResTempRenderData.AllocateLODResources(1);
// ※ 주의: 페치/가져오기(Fetch)의 는 로 0의 LOD데이터 (즉, 해상도 의 데이터 ).
FStaticMeshLODResources& HiResStaticMeshLOD = HiResTempRenderData.LODResources[0];
HiResStaticMeshLOD.MaxDeviation = 0.0f;
// PerSectionIndices배열 , 로써 최적화 GPU의 버퍼 .
TArray<TArray<uint32>> PerSectionIndices;
PerSectionIndices.AddDefaulted(HiResMeshDescription.PolygonGroups().Num());
HiResStaticMeshLOD.Sections.Empty(HiResMeshDescription.PolygonGroups().Num());
// 포인트 및 버퍼 . WedgeMap 또는 RemapVerts
TArray<int32> WedgeMap, RemapVerts;
TArray<FStaticMeshBuildVertex> StaticMeshBuildVertices;
BuildVertexBuffer(StaticMesh, HiResMeshDescription, HiResBuildSettings, WedgeMap, HiResStaticMeshLOD.Sections, PerSectionIndices, StaticMeshBuildVertices, MeshDescriptionHelper.GetOverlappingCorners(), RemapVerts);
WedgeMap.Empty();
const uint32 NumTextureCoord = HiResMeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate).GetNumChannels();
// 오직 렌더링(Render)데이터 및 포인트 데이터 을(를) 활용하여 , 따라서 로써 MeshDescription.
HiResMeshDescription.Empty();
// section의 버퍼 .
TArray<uint32> CombinedIndices;
bool bNeeds32BitIndices = false;
BuildCombinedSectionIndices(PerSectionIndices, HiResStaticMeshLOD, CombinedIndices, bNeeds32BitIndices);
// Nanite로부터 해상도 계산/산출(Calculate)바운딩 박스(AABB), 이므로 수정 StaticMeshBuildVertices.
ComputeBoundsFromVertexList(StaticMeshBuildVertices, HiResBoundsOut.Origin, HiResBoundsOut.BoxExtent, HiResBoundsOut.SphereRadius);
// Nanitesection머티리얼(Material)로부터 SectionInfoMap 내에서 , 이므로 FMaterialTriangles.
for (int32 SectionIndex = 0; SectionIndex < HiResStaticMeshLOD.Sections.Num(); SectionIndex++)
{
HiResStaticMeshLOD.Sections[SectionIndex].MaterialIndex = StaticMesh->GetSectionInfoMap().Get(0, SectionIndex).MaterialIndex;
}
// Nanite.
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::BuildNaniteFromHiResSourceModel::Nanite);
Nanite::IBuilderModule& NaniteBuilderModule = Nanite::IBuilderModule::Get();
if (!NaniteBuilderModule.Build(NaniteResourcesOut, StaticMeshBuildVertices, CombinedIndices, HiResStaticMeshLOD.Sections, NumTextureCoord, NaniteSettings))
{
UE_LOG(LogStaticMesh, Error, TEXT("Failed to build Nanite for HiRes static mesh. See previous line(s) for details."));
return false;
}
}
return true;
}위의 코드에는 몇 가지 중요한 인터페이스가 포함되어 있습니다. 아래에서 분석해 보겠습니다.
// Engine\Source\Runtime\Engine\Private\StaticMesh.cpp
// 는 유효(Valid)의 해상도 .
bool UStaticMesh::IsHiResMeshDescriptionValid() const
{
const FStaticMeshSourceModel& SourceModel = GetHiResSourceModel();
return SourceModel.IsMeshDescriptionValid();
}
// Engine\Source\Developer\MeshBuilder\Private\MeshDescriptionHelper.cpp
void FMeshDescriptionHelper::SetupRenderMeshDescription(UObject* Owner, FMeshDescription& RenderMeshDescription)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshDescriptionHelper::GetRenderMeshDescription);
UStaticMesh* StaticMesh = Cast<UStaticMesh>(Owner);
const bool bNaniteBuildEnabled = StaticMesh->NaniteSettings.bEnabled;
float ComparisonThreshold = (BuildSettings->bRemoveDegenerates && !bNaniteBuildEnabled) ? THRESH_POINTS_ARE_SAME : 0.0f;
// 노멀(Normal),,노멀(Normal)계산/산출(Calculate), 로부터 render mesh description의 .
FStaticMeshOperations::ComputeTriangleTangentsAndNormals(RenderMeshDescription, ComparisonThreshold);
FVertexInstanceArray& VertexInstanceArray = RenderMeshDescription.VertexInstances();
FStaticMeshAttributes Attributes(RenderMeshDescription);
TVertexInstanceAttributesRef<FVector> Normals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesRef<FVector> Tangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesRef<float> BinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
// 까지 오버랩 의 포인트 ,가속 。
FStaticMeshOperations::FindOverlappingCorners(OverlappingCorners, RenderMeshDescription, ComparisonThreshold);
// 정적 는 블렌딩(Blending)오버랩 의 노멀(Normal).
EComputeNTBsFlags ComputeNTBsOptions = EComputeNTBsFlags::BlendOverlappingNormals;
ComputeNTBsOptions |= BuildSettings->bComputeWeightedNormals ? EComputeNTBsFlags::WeightedNTBs : EComputeNTBsFlags::None;
ComputeNTBsOptions |= BuildSettings->bRecomputeNormals ? EComputeNTBsFlags::Normals : EComputeNTBsFlags::None;
ComputeNTBsOptions |= BuildSettings->bUseMikkTSpace ? EComputeNTBsFlags::UseMikkTSpace : EComputeNTBsFlags::None;
// Nanite계산/산출(Calculate)데이터 .
if (!bNaniteBuildEnabled)
{
ComputeNTBsOptions |= BuildSettings->bRemoveDegenerates ? EComputeNTBsFlags::IgnoreDegenerateTriangles : EComputeNTBsFlags::None;
ComputeNTBsOptions |= BuildSettings->bRecomputeTangents ? EComputeNTBsFlags::Tangents : EComputeNTBsFlags::None;
}
// 계산/산출(Calculate)의 노멀(Normal) 또는 .
FStaticMeshOperations::ComputeTangentsAndNormals(RenderMeshDescription, ComputeNTBsOptions);
// 라이팅 UV.
if (BuildSettings->bGenerateLightmapUVs && VertexInstanceArray.Num() > 0)
{
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
int32 NumIndices = VertexInstanceUVs.GetNumChannels();
//Verify the src light map channel
if (BuildSettings->SrcLightmapIndex >= NumIndices)
{
BuildSettings->SrcLightmapIndex = 0;
}
//Verify the destination light map channel
if (BuildSettings->DstLightmapIndex >= NumIndices)
{
//Make sure we do not add illegal UV Channel index
if (BuildSettings->DstLightmapIndex >= MAX_MESH_TEXTURE_COORDS_MD)
{
BuildSettings->DstLightmapIndex = MAX_MESH_TEXTURE_COORDS_MD - 1;
}
//Add some unused UVChannel to the mesh description for the lightmapUVs
VertexInstanceUVs.SetNumChannels(BuildSettings->DstLightmapIndex + 1);
BuildSettings->DstLightmapIndex = NumIndices;
}
FStaticMeshOperations::CreateLightMapUVLayout(RenderMeshDescription,
BuildSettings->SrcLightmapIndex,
BuildSettings->DstLightmapIndex,
BuildSettings->MinLightmapResolution,
(ELightmapUVVersion)StaticMesh->GetLightmapUVVersion(),
OverlappingCorners);
}
}
// Engine\Source\Developer\MeshBuilder\Private\StaticMeshBuilder.cpp
// 포인트 버퍼 .
void BuildVertexBuffer(
UStaticMesh *StaticMesh
, const FMeshDescription& MeshDescription
, const FMeshBuildSettings& BuildSettings
, TArray<int32>& OutWedgeMap
, FStaticMeshSectionArray& OutSections
, TArray<TArray<uint32> >& OutPerSectionIndices
, TArray< FStaticMeshBuildVertex >& StaticMeshBuildVertices
, const FOverlappingCorners& OverlappingCorners
, TArray<int32>& RemapVerts)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BuildVertexBuffer);
TArray<int32> RemapVertexInstanceID;
// 설정(Set)포인트 버퍼 .
const int32 NumVertexInstances = MeshDescription.VertexInstances().GetArraySize();
StaticMeshBuildVertices.Reserve(NumVertexInstances);
FStaticMeshConstAttributes Attributes(MeshDescription);
TPolygonGroupAttributesConstRef<FName> PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
TVertexAttributesConstRef<FVector> VertexPositions = Attributes.GetVertexPositions();
TVertexInstanceAttributesConstRef<FVector> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector4> VertexInstanceColors = Attributes.GetVertexInstanceColors();
TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
const bool bHasColors = VertexInstanceColors.IsValid();
const bool bIgnoreTangents = StaticMesh->NaniteSettings.bEnabled;
const uint32 NumTextureCoord = VertexInstanceUVs.GetNumChannels();
const FMatrix ScaleMatrix = FScaleMatrix(BuildSettings.BuildScale3D).Inverse().GetTransposed();
TMap<FPolygonGroupID, int32> PolygonGroupToSectionIndex;
for (const FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs())
{
int32& SectionIndex = PolygonGroupToSectionIndex.FindOrAdd(PolygonGroupID);
SectionIndex = OutSections.Add(FStaticMeshSection());
FStaticMeshSection& StaticMeshSection = OutSections[SectionIndex];
StaticMeshSection.MaterialIndex = StaticMesh->GetMaterialIndexFromImportedMaterialSlotName(PolygonGroupImportedMaterialSlotNames[PolygonGroupID]);
if (StaticMeshSection.MaterialIndex == INDEX_NONE)
{
StaticMeshSection.MaterialIndex = PolygonGroupID.GetValue();
}
}
int32 ReserveIndicesCount = MeshDescription.Triangles().Num() * 3;
// 패딩/채우기 매핑 배열 .
RemapVerts.AddZeroed(ReserveIndicesCount);
for (int32& RemapIndex : RemapVerts)
{
RemapIndex = INDEX_NONE;
}
// 초기화(Initialize)테이블 OutWedgeMap
OutWedgeMap.Reset();
OutWedgeMap.AddZeroed(ReserveIndicesCount);
float VertexComparisonThreshold = BuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
int32 WedgeIndex = 0;
for (const FTriangleID TriangleID : MeshDescription.Triangles().GetElementIDs())
{
const FPolygonGroupID PolygonGroupID = MeshDescription.GetTrianglePolygonGroup(TriangleID);
const int32 SectionIndex = PolygonGroupToSectionIndex[PolygonGroupID];
TArray<uint32>& SectionIndices = OutPerSectionIndices[SectionIndex];
TArrayView<const FVertexID> VertexIDs = MeshDescription.GetTriangleVertices(TriangleID);
FVector CornerPositions[3];
for (int32 TriVert = 0; TriVert < 3; ++TriVert)
{
CornerPositions[TriVert] = VertexPositions[VertexIDs[TriVert]];
}
FOverlappingThresholds OverlappingThresholds;
OverlappingThresholds.ThresholdPosition = VertexComparisonThreshold;
// 처리(Process)의 .
if (PointsEqual(CornerPositions[0], CornerPositions[1], OverlappingThresholds)
|| PointsEqual(CornerPositions[0], CornerPositions[2], OverlappingThresholds)
|| PointsEqual(CornerPositions[1], CornerPositions[2], OverlappingThresholds))
{
WedgeIndex += 3;
continue;
}
TArrayView<const FVertexInstanceID> VertexInstanceIDs = MeshDescription.GetTriangleVertexInstances(TriangleID);
for (int32 TriVert = 0; TriVert < 3; ++TriVert, ++WedgeIndex)
{
const FVertexInstanceID VertexInstanceID = VertexInstanceIDs[TriVert];
const FVector& VertexPosition = CornerPositions[TriVert];
const FVector& VertexInstanceNormal = VertexInstanceNormals[VertexInstanceID];
const FVector& VertexInstanceTangent = VertexInstanceTangents[VertexInstanceID];
const float VertexInstanceBinormalSign = VertexInstanceBinormalSigns[VertexInstanceID];
FStaticMeshBuildVertex StaticMeshVertex;
StaticMeshVertex.Position = VertexPosition * BuildSettings.BuildScale3D;
// 만약 는 Nanite, 의 및 .
if( bIgnoreTangents )
{
StaticMeshVertex.TangentX = FVector( 1.0f, 0.0f, 0.0f );
StaticMeshVertex.TangentY = FVector( 0.0f, 1.0f, 0.0f );
}
else
{
StaticMeshVertex.TangentX = ScaleMatrix.TransformVector(VertexInstanceTangent).GetSafeNormal();
StaticMeshVertex.TangentY = ScaleMatrix.TransformVector(FVector::CrossProduct(VertexInstanceNormal, VertexInstanceTangent) * VertexInstanceBinormalSign).GetSafeNormal();
}
StaticMeshVertex.TangentZ = ScaleMatrix.TransformVector(VertexInstanceNormal).GetSafeNormal();
if (bHasColors)
{
const FVector4& VertexInstanceColor = VertexInstanceColors[VertexInstanceID];
const FLinearColor LinearColor(VertexInstanceColor);
StaticMeshVertex.Color = LinearColor.ToFColor(true);
}
else
{
StaticMeshVertex.Color = FColor::White;
}
const uint32 MaxNumTexCoords = FMath::Min<int32>(MAX_MESH_TEXTURE_COORDS_MD, MAX_STATIC_TEXCOORDS);
for (uint32 UVIndex = 0; UVIndex < MaxNumTexCoords; ++UVIndex)
{
if(UVIndex < NumTextureCoord)
{
StaticMeshVertex.UVs[UVIndex] = VertexInstanceUVs.Get(VertexInstanceID, UVIndex);
}
else
{
StaticMeshVertex.UVs[UVIndex] = FVector2D(0.0f, 0.0f);
}
}
// 추가(Add)의 포인트 인스턴스 . 을(를) 활용하여 의 WedgeIndex
const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(WedgeIndex);
int32 Index = INDEX_NONE;
for (int32 k = 0; k < DupVerts.Num(); k++)
{
if (DupVerts[k] >= WedgeIndex)
{
break;
}
int32 Location = RemapVerts.IsValidIndex(DupVerts[k]) ? RemapVerts[DupVerts[k]] : INDEX_NONE;
if (Location != INDEX_NONE && AreVerticesEqual(StaticMeshVertex, StaticMeshBuildVertices[Location], VertexComparisonThreshold))
{
Index = Location;
break;
}
}
if (Index == INDEX_NONE)
{
Index = StaticMeshBuildVertices.Add(StaticMeshVertex);
}
RemapVerts[WedgeIndex] = Index;
OutWedgeMap[WedgeIndex] = Index;
SectionIndices.Add( Index );
}
}
// 설정(Set)버퍼 최적화 .
if (NumVertexInstances < 100000 * 3)
{
BuildOptimizationHelper::CacheOptimizeVertexAndIndexBuffer(StaticMeshBuildVertices, OutPerSectionIndices, OutWedgeMap);
}
}
// 의 Section.
static void BuildCombinedSectionIndices(
const TArray<TArray<uint32>>& PerSectionIndices,
FStaticMeshLODResources& StaticMeshLODInOut,
TArray<uint32>& CombinedIndicesOut,
bool& bNeeds32BitIndicesOut )
{
bNeeds32BitIndicesOut = false;
for (int32 SectionIndex = 0; SectionIndex < StaticMeshLODInOut.Sections.Num(); SectionIndex++)
{
FStaticMeshSection& Section = StaticMeshLODInOut.Sections[SectionIndex];
const TArray<uint32>& SectionIndices = PerSectionIndices[SectionIndex];
Section.FirstIndex = 0;
Section.NumTriangles = 0;
Section.MinVertexIndex = 0;
Section.MaxVertexIndex = 0;
if (SectionIndices.Num())
{
Section.FirstIndex = CombinedIndicesOut.Num();
Section.NumTriangles = SectionIndices.Num() / 3;
CombinedIndicesOut.AddUninitialized(SectionIndices.Num());
uint32* DestPtr = &CombinedIndicesOut[Section.FirstIndex];
uint32 const* SrcPtr = SectionIndices.GetData();
Section.MinVertexIndex = *SrcPtr;
Section.MaxVertexIndex = *SrcPtr;
for (int32 Index = 0; Index < SectionIndices.Num(); Index++)
{
uint32 VertIndex = *SrcPtr++;
bNeeds32BitIndicesOut |= (VertIndex > MAX_uint16);
Section.MinVertexIndex = FMath::Min<uint32>(VertIndex, Section.MinVertexIndex);
Section.MaxVertexIndex = FMath::Max<uint32>(VertIndex, Section.MaxVertexIndex);
*DestPtr++ = VertIndex;
}
}
}
}
// 에 따라 포인트 계산/산출(Calculate)바운딩 박스(AABB) 및
static void ComputeBoundsFromVertexList(const TArray<FStaticMeshBuildVertex>& Vertices, FVector& OriginOut, FVector& ExtentOut, float& RadiusOut)
{
// 계산/산출(Calculate)바운딩 박스(AABB)
FBox BoundingBox(ForceInit);
for (int32 VertexIndex = 0; VertexIndex < Vertices.Num(); VertexIndex++)
{
BoundingBox += Vertices[VertexIndex].Position;
}
BoundingBox.GetCenterAndExtents(OriginOut, ExtentOut);
// 계산/산출(Calculate), 을(를) 활용하여 바운딩 박스(AABB)의 내에서 로써 내에서 .
RadiusOut = 0.0f;
for (int32 VertexIndex = 0; VertexIndex < Vertices.Num(); VertexIndex++)
{
RadiusOut = FMath::Max((Vertices[VertexIndex].Position-OriginOut).Size(), RadiusOut);
}
}위 로직의 대부분은 일반 정적 메쉬와 유사하지만 다음과 같은 차이점도 있습니다.
Nanite의 소스 모델은 초고해상도 모델인 HiResSourceModel에서 나옵니다.
Nanite 메시는 탄젠트 및 부접선 계산과 표면 축소 프로세스를 무시합니다.
마지막으로 Nanite::IBuilderModule::Build가 호출되어 Nanite 그리드 데이터를 실제로 빌드합니다. 자세한 내용은 다음 섹션을 참조하세요.
6.4.2.2 BuildNaniteData
이번 섹션에서는 나나이트 그리드의 구축 과정을 설명합니다.
// Engine\Source\Developer\NaniteBuilder\Private\NaniteBuilder.cpp
bool FBuilderModule::Build(
FResources& Resources,
TArray< FStaticMeshBuildVertex>& Vertices,
TArray< uint32 >& TriangleIndices,
TArray< FStaticMeshSection, TInlineAllocator<1>>& Sections,
uint32 NumTexCoords,
const FMeshNaniteSettings& Settings)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build);
check(Sections.Num() > 0 && Sections.Num() <= 64);
// 및 머티리얼(Material)의 배열 。
TArray<int32> MaterialIndices;
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::BuildSections);
// 머티리얼(Material)의 개수 및 개수 .
MaterialIndices.Reserve(TriangleIndices.Num() / 3);
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); SectionIndex++)
{
FStaticMeshSection& Section = Sections[SectionIndex];
check(Section.MaterialIndex != INDEX_NONE);
for (uint32 i = 0; i < Section.NumTriangles; ++i)
{
MaterialIndices.Add(Section.MaterialIndex);
}
}
}
TArray<uint32> MeshTriangleCounts;
MeshTriangleCounts.Add(TriangleIndices.Num() / 3);
// 개 개 머티리얼(Material).
check(MaterialIndices.Num() * 3 == TriangleIndices.Num());
// Nanite데이터 .
return BuildNaniteData(
Resources,
Vertices,
TriangleIndices,
MaterialIndices,
MeshTriangleCounts,
Sections,
NumTexCoords,
Settings
);
}
// Nanite데이터 .
static bool BuildNaniteData(
FResources& Resources,
TArray< FStaticMeshBuildVertex >& Verts, // TODO: Do not require this vertex type for all users of Nanite
TArray< uint32 >& Indexes,
TArray< int32 >& MaterialIndexes,
TArray<uint32>& MeshTriangleCounts,
TArray< FStaticMeshSection, TInlineAllocator<1> >& Sections,
uint32 NumTexCoords,
const FMeshNaniteSettings& Settings
)
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::BuildData);
if (NumTexCoords > MAX_NANITE_UVS) NumTexCoords = MAX_NANITE_UVS;
FBounds VertexBounds;
uint32 Channel = 255; // 을(를) 활용하여 검사/감지(Detect)는 유효(Valid)의 포인트 데이터 .
for( auto& Vert : Verts )
{
VertexBounds += Vert.Position;
Channel &= Vert.Color.R;
Channel &= Vert.Color.G;
Channel &= Vert.Color.B;
Channel &= Vert.Color.A;
}
const uint32 NumMeshes = MeshTriangleCounts.Num();
// 오직 컬러 데이터 .
bool bHasColors = Channel != 255;
TArray< uint32 > ClusterCountPerMesh;
TArray< FCluster > Clusters;
{
uint32 BaseTriangle = 0;
// 순회(Traverse/Iterate)Section, 개 Section개 또는 개 Cluster.
for (uint32 NumTriangles : MeshTriangleCounts)
{
uint32 NumClustersBefore = Clusters.Num();
if (NumTriangles)
{
// 로 개 Section1 또는 개 Cluster. 을(를) 활용하여 TArrayView을(를) 활용하여 데이터 의 배열 .
// ClusterTriangles의 .
ClusterTriangles(Verts, TArrayView< const uint32 >( &Indexes[BaseTriangle * 3], NumTriangles * 3 ),
TArrayView< const int32 >( &MaterialIndexes[BaseTriangle], NumTriangles ),
Clusters, VertexBounds, NumTexCoords, bHasColors);
}
// 개 Section의 Cluster개수 .
ClusterCountPerMesh.Add(Clusters.Num() - NumClustersBefore);
BaseTriangle += NumTriangles;
}
}
const int32 OldTriangleCount = Indexes.Num() / 3;
const int32 MinTriCount = 2000;
// 을(를) 활용하여 거침(Rough)테이블 (coarse representation)의 정적 데이터 。
const bool bUseCoarseRepresentation = Settings.PercentTriangles < 1.0f && OldTriangleCount > MinTriCount;
// 만약 을(를) 활용하여 거침(Rough)테이블 (coarse representation)의 포인트 버퍼 , 의 데이터 .
// 까지 cluster representation 내에서 , 의 DAG스테이지 실행(Execute),로써 메모리 。
// 병렬 개 의 Nanite,포인트 로 。
if (bUseCoarseRepresentation)
{
check(MeshTriangleCounts.Num() == 1);
Verts.Empty();
Indexes.Empty();
MaterialIndexes.Empty();
}
uint32 Time0 = FPlatformTime::Cycles();
FBounds MeshBounds;
TArray<FClusterGroup> Groups; // Cluster리스트 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::DAG.Reduce);
uint32 ClusterStart = 0;
for (uint32 MeshIndex = 0; MeshIndex < NumMeshes; MeshIndex++)
{
uint32 NumClusters = ClusterCountPerMesh[MeshIndex];
// DAG(Directed Acyclic Graph,루프 ),로써 , Cluster 및 Group까지 배열 내에서 .
BuildDAG( Groups, Clusters, ClusterStart, NumClusters, MeshIndex, MeshBounds );
ClusterStart += NumClusters;
}
}
uint32 ReduceTime = FPlatformTime::Cycles();
UE_LOG(LogStaticMesh, Log, TEXT("Reduce [%.2fs]"), FPlatformTime::ToMilliseconds(ReduceTime - Time0) / 1000.0f);
// 을(를) 활용하여 거침(Rough)테이블 .
if (bUseCoarseRepresentation)
{
const uint32 CoarseStartTime = FPlatformTime::Cycles();
int32 CoarseTriCount = FMath::Max(MinTriCount, int32((float(OldTriangleCount) * Settings.PercentTriangles)));
TArray<FStaticMeshSection, TInlineAllocator<1>> CoarseSections = Sections;
// 거침(Rough)테이블 .
BuildCoarseRepresentation(Groups, Clusters, Verts, Indexes, CoarseSections, NumTexCoords, CoarseTriCount);
// 을(를) 활용하여 거침(Rough)범위 section정보 , 및 머티리얼(Material).
// 로써 의 (에 의해 )。
for (FStaticMeshSection& Section : Sections)
{
// 개 section의 정보 , 내에서 까지 개 매칭 의 개 。
const FStaticMeshSection* CoarseSection = CoarseSections.FindByPredicate(
[&Section](const FStaticMeshSection& CoarseSectionIter)
{
return CoarseSectionIter.MaterialIndex == Section.MaterialIndex;
});
// 까지 매칭 의 개
if (CoarseSection != nullptr)
{
Section.FirstIndex = CoarseSection->FirstIndex;
Section.NumTriangles = CoarseSection->NumTriangles;
Section.MinVertexIndex = CoarseSection->MinVertexIndex;
Section.MaxVertexIndex = CoarseSection->MaxVertexIndex;
}
// 까지 매칭 의 개 .
else
{
// 에 의해 의 ,설정(Set)개
Section.FirstIndex = 0;
Section.NumTriangles = 0;
Section.MinVertexIndex = 0;
Section.MaxVertexIndex = 0;
}
}
const uint32 CoarseEndTime = FPlatformTime::Cycles();
UE_LOG(LogStaticMesh, Log, TEXT("Coarse [%.2fs], original tris: %d, coarse tris: %d"), FPlatformTime::ToMilliseconds(CoarseEndTime - CoarseStartTime) / 1000.0f, OldTriangleCount, CoarseTriCount);
}
uint32 EncodeTime0 = FPlatformTime::Cycles();
// Nanite.
Encode( Resources, Settings, Clusters, Groups, MeshBounds, NumMeshes, NumTexCoords, bHasColors );
uint32 EncodeTime1 = FPlatformTime::Cycles();
UE_LOG( LogStaticMesh, Log, TEXT("Encode [%.2fs]"), FPlatformTime::ToMilliseconds( EncodeTime1 - EncodeTime0 ) / 1000.0f );
// 오직 개 Imposter.
const bool bGenerateImposter = (NumMeshes == 1);
if (bGenerateImposter)
{
uint32 ImposterStartTime = FPlatformTime::Cycles();
auto& RootChildren = Groups.Last().Children;
// Resources의 ImposterAtlas.
FImposterAtlas ImposterAtlas( Resources.ImposterAtlas, MeshBounds );
// 병렬 Imposter.
ParallelFor(FMath::Square(FImposterAtlas::AtlasSize),
[&](int32 TileIndex)
{
FIntPoint TilePos(
TileIndex % FImposterAtlas::AtlasSize,
TileIndex / FImposterAtlas::AtlasSize);
// 순회(Traverse/Iterate)Cluster, 래스터라이제이션 까지 ImposterAtlas.
for (int32 ClusterIndex = 0; ClusterIndex < RootChildren.Num(); ClusterIndex++)
{
ImposterAtlas.Rasterize(TilePos, Clusters[RootChildren[ClusterIndex]], ClusterIndex);
}
});
UE_LOG(LogStaticMesh, Log, TEXT("Imposter [%.2fs]"), FPlatformTime::ToMilliseconds(FPlatformTime::Cycles() - ImposterStartTime ) / 1000.0f);
}
uint32 Time1 = FPlatformTime::Cycles();
UE_LOG( LogStaticMesh, Log, TEXT("Nanite build [%.2fs]\n"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) / 1000.0f );
return true;
}6.4.2.3 ClusterTriangles
// 로 개 Section1 또는 개 Cluster.
static void ClusterTriangles(
const TArray< FStaticMeshBuildVertex >& Verts,
const TArrayView< const uint32 >& Indexes,
const TArrayView< const int32 >& MaterialIndexes,
TArray< FCluster >& Clusters, // Append
const FBounds& MeshBounds,
uint32 NumTexCoords,
bool bHasColors )
{
uint32 Time0 = FPlatformTime::Cycles();
LOG_CRC( Verts );
LOG_CRC( Indexes );
uint32 NumTriangles = Indexes.Num() / 3;
//
TArray< uint32 > SharedEdges;
SharedEdges.AddUninitialized( Indexes.Num() );
//
TBitArray<> BoundaryEdges;
BoundaryEdges.Init( false, Indexes.Num() );
//
FHashTable EdgeHash( 1 << FMath::FloorLog2( Indexes.Num() ), Indexes.Num() );
// 병렬 처리(Process).
ParallelFor( Indexes.Num(),
[&]( int32 EdgeIndex )
{
uint32 VertIndex0 = Indexes[ EdgeIndex ];
uint32 VertIndex1 = Indexes[ Cycle3( EdgeIndex ) ];
const FVector& Position0 = Verts[ VertIndex0 ].Position;
const FVector& Position1 = Verts[ VertIndex1 ].Position;
uint32 Hash0 = HashPosition( Position0 );
uint32 Hash1 = HashPosition( Position1 );
uint32 Hash = Murmur32( { Hash0, Hash1 } );
// ※ 주의: 추가(Add)을(를) 활용하여 의 는 동시성 Add_Concurrent.
EdgeHash.Add_Concurrent( Hash, EdgeIndex );
});
const int32 NumDwords = FMath::DivideAndRoundUp( BoundaryEdges.Num(), NumBitsPerDWORD );
ParallelFor( NumDwords,
[&]( int32 DwordIndex )
{
const int32 NumIndexes = Indexes.Num();
const int32 NumBits = FMath::Min( NumBitsPerDWORD, NumIndexes - DwordIndex * NumBitsPerDWORD );
uint32 Mask = 1;
uint32 Dword = 0;
for( int32 BitIndex = 0; BitIndex < NumBits; BitIndex++, Mask <<= 1 )
{
// 계산/산출(Calculate).
int32 EdgeIndex = DwordIndex * NumBitsPerDWORD + BitIndex;
uint32 VertIndex0 = Indexes[ EdgeIndex ];
uint32 VertIndex1 = Indexes[ Cycle3( EdgeIndex ) ];
const FVector& Position0 = Verts[ VertIndex0 ].Position;
const FVector& Position1 = Verts[ VertIndex1 ].Position;
uint32 Hash0 = HashPosition( Position0 );
uint32 Hash1 = HashPosition( Position1 );
uint32 Hash = Murmur32( { Hash1, Hash0 } );
// 까지 개 포인트 의 .
/*
/\
/ \
o-<<-o
o->>-o
\ /
\/
*/
uint32 FoundEdge = ~0u;
for( uint32 OtherEdgeIndex = EdgeHash.First( Hash ); EdgeHash.IsValid( OtherEdgeIndex ); OtherEdgeIndex = EdgeHash.Next( OtherEdgeIndex ) )
{
uint32 OtherVertIndex0 = Indexes[ OtherEdgeIndex ];
uint32 OtherVertIndex1 = Indexes[ Cycle3( OtherEdgeIndex ) ];
if( Position0 == Verts[ OtherVertIndex1 ].Position &&
Position1 == Verts[ OtherVertIndex0 ].Position )
{
// 까지 매칭 의 .
// 테이블 는 의 。까지 의 매칭 ,는 개 。
FoundEdge = FMath::Min( FoundEdge, OtherEdgeIndex );
}
}
SharedEdges[ EdgeIndex ] = FoundEdge;
if( FoundEdge == ~0u )
{
Dword |= Mask;
}
}
if( Dword )
{
BoundaryEdges.GetData()[ DwordIndex ] = Dword;
}
});
// 의 .
FDisjointSet DisjointSet( NumTriangles );
for( uint32 EdgeIndex = 0, Num = SharedEdges.Num(); EdgeIndex < Num; EdgeIndex++ )
{
uint32 OtherEdgeIndex = SharedEdges[ EdgeIndex ];
if( OtherEdgeIndex != ~0u )
{
// OtherEdgeIndex는 매칭 EdgeIndex의 .
// ThisEdgeIndex는 매칭 OtherEdgeIndex의 .
uint32 ThisEdgeIndex = SharedEdges[ OtherEdgeIndex ];
check( ThisEdgeIndex != ~0u );
check( ThisEdgeIndex <= EdgeIndex );
if( EdgeIndex > ThisEdgeIndex )
{
// 개 OtherEdgeIndex
SharedEdges[ EdgeIndex ] = ~0u;
}
else if( EdgeIndex > OtherEdgeIndex )
{
// 회 검사/감지(Detect).
DisjointSet.UnionSequential( EdgeIndex / 3, OtherEdgeIndex / 3 );
}
}
}
uint32 BoundaryTime = FPlatformTime::Cycles();
UE_LOG( LogStaticMesh, Log, TEXT("Boundary [%.2fs], tris: %i, UVs %i%s"), FPlatformTime::ToMilliseconds( BoundaryTime - Time0 ) / 1000.0f, Indexes.Num() / 3, NumTexCoords, bHasColors ? TEXT(", Color") : TEXT("") );
LOG_CRC( SharedEdges );
// .
FGraphPartitioner Partitioner( NumTriangles );
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::PartitionGraph);
// 페치/가져오기(Fetch)의 내에서 .
auto GetCenter = [ &Verts, &Indexes ]( uint32 TriIndex )
{
FVector Center;
Center = Verts[ Indexes[ TriIndex * 3 + 0 ] ].Position;
Center += Verts[ Indexes[ TriIndex * 3 + 1 ] ].Position;
Center += Verts[ Indexes[ TriIndex * 3 + 2 ] ].Position;
return Center * (1.0f / 3.0f);
};
// 위치 .
Partitioner.BuildLocalityLinks( DisjointSet, MeshBounds, GetCenter );
auto* RESTRICT Graph = Partitioner.NewGraph( NumTriangles * 3 );
// 처리(Process)데이터 .
for( uint32 i = 0; i < NumTriangles; i++ )
{
Graph->AdjacencyOffset[i] = Graph->Adjacency.Num();
uint32 TriIndex = Partitioner.Indexes[i];
for( int k = 0; k < 3; k++ )
{
uint32 EdgeIndex = SharedEdges[ 3 * TriIndex + k ];
// 추가(Add).
if( EdgeIndex != ~0u )
{
Partitioner.AddAdjacency( Graph, EdgeIndex / 3, 4 * 65 );
}
}
// 추가(Add)위치 .
Partitioner.AddLocalityLinks( Graph, TriIndex, 1 );
}
Graph->AdjacencyOffset[ NumTriangles ] = Graph->Adjacency.Num();
// Cluster.
Partitioner.PartitionStrict( Graph, FCluster::ClusterSize - 4, FCluster::ClusterSize, true );
check( Partitioner.Ranges.Num() );
LOG_CRC( Partitioner.Ranges );
}
// 계산/산출(Calculate)의 Cluster개수 .
const uint32 OptimalNumClusters = FMath::DivideAndRoundUp< int32 >( Indexes.Num(), FCluster::ClusterSize * 3 );
uint32 ClusterTime = FPlatformTime::Cycles();
UE_LOG( LogStaticMesh, Log, TEXT("Clustering [%.2fs]. Ratio: %f"), FPlatformTime::ToMilliseconds( ClusterTime - BoundaryTime ) / 1000.0f, (float)Partitioner.Ranges.Num() / OptimalNumClusters );
const uint32 BaseCluster = Clusters.Num();
Clusters.AddDefaulted( Partitioner.Ranges.Num() );
// : 32을(를) 활용하여 스레드(Thread)? 는 ?
const bool bSingleThreaded = Partitioner.Ranges.Num() > 32;
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::BuildClusters);
// 병렬 Cluster.
ParallelFor( Partitioner.Ranges.Num(),
[&]( int32 Index )
{
auto& Range = Partitioner.Ranges[ Index ];
// 생성(Create)개 Cluster인스턴스 .
Clusters[ BaseCluster + Index ] = FCluster( Verts,
Indexes,
MaterialIndexes,
BoundaryEdges, Range.Begin, Range.End, Partitioner.Indexes, NumTexCoords, bHasColors );
// 는 개 .
Clusters[ BaseCluster + Index ].EdgeLength *= -1.0f;
}, bSingleThreaded);
}
uint32 LeavesTime = FPlatformTime::Cycles();
UE_LOG( LogStaticMesh, Log, TEXT("Leaves [%.2fs]"), FPlatformTime::ToMilliseconds( LeavesTime - ClusterTime ) / 1000.0f );
}6.4.2.4 FGraphPartitioner
이전 섹션의 코드는 Cluster를 처리할 때 FGraphPartitioner를 사용합니다. 코드 분석을 입력해 보겠습니다.
// Engine\Source\Developer\NaniteBuilder\Private\GraphPartitioner.h
(......)
// 을(를) 활용하여 metis.
#include "metis.h"
(......)
// Cluster
class FGraphPartitioner
{
public:
// 데이터 .
struct FGraphData
{
int32 Offset; // .
int32 Num; // 개수 .
TArray< idx_t > Adjacency; // 리스트
TArray< idx_t > AdjacencyCost; // 가중치(Weight)리스트
TArray< idx_t > AdjacencyOffset; // 리스트
};
// 범위 는 [Begin, End]
struct FRange
{
uint32 Begin;
uint32 End;
bool operator<( const FRange& Other) const { return Begin < Other.Begin; }
};
TArray< FRange > Ranges;
TArray< uint32 > Indexes;
public:
FGraphPartitioner( uint32 InNumElements );
// 의 데이터 인스턴스 .
FGraphData* NewGraph( uint32 NumAdjacency ) const;
// 추가(Add).
void AddAdjacency( FGraphData* Graph, uint32 AdjIndex, idx_t Cost );
// 추가(Add)위치 .
void AddLocalityLinks( FGraphData* Graph, uint32 Index, idx_t Cost );
// 위치 .
template< typename FGetCenter >
void BuildLocalityLinks( FDisjointSet& DisjointSet, const FBounds& Bounds, FGetCenter& GetCenter );
// Cluster.
void Partition( FGraphData* Graph, int32 InMinPartitionSize, int32 InMaxPartitionSize );
// Cluster.
void PartitionStrict( FGraphData* Graph, int32 InMinPartitionSize, int32 InMaxPartitionSize, bool bThreaded );
private:
// .
void BisectGraph( FGraphData* Graph, FGraphData* ChildGraphs[2] );
// .
void RecursiveBisectGraph( FGraphData* Graph );
uint32 NumElements;
int32 MinPartitionSize = 0;
int32 MaxPartitionSize = 0;
// Cluster개수 . 을(를) 활용하여 원자적 , 로써 지원 스레드(Thread).
TAtomic< uint32 > NumPartitions;
TArray< idx_t > PartitionIDs;
TArray< int32 > SwappedWith;
TArray< uint32 > SortedTo;
// 위치 .
TMultiMap< uint32, uint32 > LocalityLinks;
};
(......)
// Engine\Source\Developer\NaniteBuilder\Private\GraphPartitioner.cpp
(......)
// .
void FGraphPartitioner::BisectGraph( FGraphData* Graph, FGraphData* ChildGraphs[2] )
{
ChildGraphs[0] = nullptr;
ChildGraphs[1] = nullptr;
// 추가(Add).
auto AddPartition =
[ this ]( int32 Offset, int32 Num )
{
FRange& Range = Ranges[ NumPartitions++ ];
Range.Begin = Offset;
Range.End = Offset + Num;
};
// 만약 Graph의 개수 , 이면 추가(Add)까지 this 내에서 .
if( Graph->Num <= MaxPartitionSize )
{
AddPartition( Graph->Offset, Graph->Num );
return;
}
// 계산/산출(Calculate)의 해상도/크기 .
const int32 TargetPartitionSize = ( MinPartitionSize + MaxPartitionSize ) / 2;
const int32 TargetNumPartitions = FMath::Max( 2, FMath::DivideAndRoundNearest( Graph->Num, TargetPartitionSize ) );
check( Graph->AdjacencyOffset.Num() == Graph->Num + 1 );
idx_t NumConstraints = 1;
idx_t NumParts = 2;
idx_t EdgesCut = 0;
real_t PartitionWeights[] = {
float( TargetNumPartitions / 2 ) / TargetNumPartitions,
1.0f - float( TargetNumPartitions / 2 ) / TargetNumPartitions
};
// 설정(Set)Metis의 기본값(Default)파라미터 .
idx_t Options[ METIS_NOPTIONS ];
METIS_SetDefaultOptions( Options );
// 레벨 단계 의 , 의 크기 。
bool bLoose = TargetNumPartitions >= 128 || MaxPartitionSize / MinPartitionSize > 1;
bool bSlow = Graph->Num < 4096;
Options[ METIS_OPTION_UFACTOR ] = bLoose ? 200 : 1;
//Options[ METIS_OPTION_NCUTS ] = Graph->Num < 1024 ? 8 : ( Graph->Num < 4096 ? 4 : 1 );
//Options[ METIS_OPTION_NCUTS ] = bSlow ? 4 : 1;
//Options[ METIS_OPTION_NITER ] = bSlow ? 20 : 10;
//Options[ METIS_OPTION_IPTYPE ] = METIS_IPTYPE_RANDOM;
//Options[ METIS_OPTION_MINCONN ] = 1;
// 호출(Call)Metis의 .
int r = METIS_PartGraphRecursive(
&Graph->Num,
&NumConstraints, // number of balancing constraints
Graph->AdjacencyOffset.GetData(),
Graph->Adjacency.GetData(),
NULL, // Vert weights
NULL, // Vert sizes for computing the total communication volume
Graph->AdjacencyCost.GetData(), // Edge weights
&NumParts,
PartitionWeights, // Target partition weight
NULL, // Allowed load imbalance tolerance
Options,
&EdgesCut,
PartitionIDs.GetData() + Graph->Offset
);
// Metis의 결과 유효(Valid).
if( ensureAlways( r == METIS_OK ) )
{
// 의 위치 배열 .
// 정렬 ,그러나 는 의 .
int32 Front = Graph->Offset;
int32 Back = Graph->Offset + Graph->Num - 1;
while( Front <= Back )
{
while( Front <= Back && PartitionIDs[ Front ] == 0 )
{
SwappedWith[ Front ] = Front;
Front++;
}
while( Front <= Back && PartitionIDs[ Back ] == 1 )
{
SwappedWith[ Back ] = Back;
Back--;
}
if( Front < Back )
{
Swap( Indexes[ Front ], Indexes[ Back ] );
SwappedWith[ Front ] = Back;
SwappedWith[ Back ] = Front;
Front++;
Back--;
}
}
int32 Split = Front;
int32 Num[2];
Num[0] = Split - Graph->Offset;
Num[1] = Graph->Offset + Graph->Num - Split;
check( Num[0] > 1 );
check( Num[1] > 1 );
// 만약 개 포인트 의 해상도/크기 , 이면 추가(Add).
if( Num[0] <= MaxPartitionSize && Num[1] <= MaxPartitionSize )
{
AddPartition( Graph->Offset, Num[0] );
AddPartition( Split, Num[1] );
}
else
{
// 생성(Create)개 포인트 인스턴스 .
for( int32 i = 0; i < 2; i++ )
{
ChildGraphs[i] = new FGraphData;
ChildGraphs[i]->Adjacency.Reserve( Graph->Adjacency.Num() >> 1 );
ChildGraphs[i]->AdjacencyCost.Reserve( Graph->Adjacency.Num() >> 1 );
ChildGraphs[i]->AdjacencyOffset.Reserve( Num[i] + 1 );
ChildGraphs[i]->Num = Num[i];
}
ChildGraphs[0]->Offset = Graph->Offset;
ChildGraphs[1]->Offset = Split;
// 순회(Traverse/Iterate), Graph의 추가(Add)까지 ChildGraphs[0] 또는 ChildGraphs[1]
for( int32 i = 0; i < Graph->Num; i++ )
{
// 코드 포인트 trick: i<=ChildGraphs[0]->Num이면 페치/가져오기(Fetch)ChildGraphs[0], 이면 페치/가져오기(Fetch)ChildGraphs[1].
FGraphData* ChildGraph = ChildGraphs[ i >= ChildGraphs[0]->Num ];
ChildGraph->AdjacencyOffset.Add( ChildGraph->Adjacency.Num() );
int32 OrgIndex = SwappedWith[ Graph->Offset + i ] - Graph->Offset;
for( idx_t AdjIndex = Graph->AdjacencyOffset[ OrgIndex ]; AdjIndex < Graph->AdjacencyOffset[ OrgIndex + 1 ]; AdjIndex++ )
{
idx_t Adj = Graph->Adjacency[ AdjIndex ];
idx_t AdjCost = Graph->AdjacencyCost[ AdjIndex ];
// Remap to child
Adj = SwappedWith[ Graph->Offset + Adj ] - ChildGraph->Offset;
// Edge connects to node in this graph
if( 0 <= Adj && Adj < ChildGraph->Num )
{
ChildGraph->Adjacency.Add( Adj );
ChildGraph->AdjacencyCost.Add( AdjCost );
}
}
}
ChildGraphs[0]->AdjacencyOffset.Add( ChildGraphs[0]->Adjacency.Num() );
ChildGraphs[1]->AdjacencyOffset.Add( ChildGraphs[1]->Adjacency.Num() );
}
}
}
//
void FGraphPartitioner::PartitionStrict( FGraphData* Graph, int32 InMinPartitionSize, int32 InMaxPartitionSize, bool bThreaded )
{
MinPartitionSize = InMinPartitionSize;
MaxPartitionSize = InMaxPartitionSize;
PartitionIDs.AddUninitialized( NumElements );
SwappedWith.AddUninitialized( NumElements );
// Adding to atomically so size big enough to not need to grow.
int32 NumPartitionsExpected = FMath::DivideAndRoundUp( Graph->Num, MinPartitionSize );
Ranges.AddUninitialized( NumPartitionsExpected * 2 );
NumPartitions = 0;
// 을(를) 활용하여 스레드(Thread).
if( bThreaded && NumPartitionsExpected > 4 )
{
extern CORE_API int32 GUseNewTaskBackend;
// 을(를) 활용하여 스레드(Thread).
if (GUseNewTaskBackend)
{
// 로컬
TLocalWorkQueue<FGraphData> LocalWork(Graph);
// 의 SelfLambda함수 .
LocalWork.Run(MakeYCombinator([this, &LocalWork](auto Self, FGraphData* Graph) -> void
{
FGraphData* ChildGraphs[2];
// .
BisectGraph( Graph, ChildGraphs );
delete Graph;
if( ChildGraphs[0] && ChildGraphs[1] )
{
// 처리(Process)1개 포인트
// 오직 의 추가(Add)의 스레드(Thread)
if (ChildGraphs[0]->Num > 256)
{
LocalWork.AddTask(ChildGraphs[0]);
LocalWork.AddWorkers(1);
}
else // 이면 호출(Call).
{
Self(ChildGraphs[0]);
}
// 처리(Process)2개 포인트
Self(ChildGraphs[1]);
}
}));
}
// 스레드(Thread). 을(를) 활용하여 의 TaskGraph.
else
{
const ENamedThreads::Type DesiredThread = IsInGameThread() ? ENamedThreads::AnyThread : ENamedThreads::AnyBackgroundThreadNormalTask;
// .
class FBuildTask
{
public:
FBuildTask( FGraphPartitioner* InPartitioner, FGraphData* InGraph, ENamedThreads::Type InDesiredThread)
: Partitioner( InPartitioner )
, Graph( InGraph )
, DesiredThread( InDesiredThread )
{}
void DoTask( ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionEvent )
{
FGraphData* ChildGraphs[2];
Partitioner->BisectGraph( Graph, ChildGraphs );
delete Graph;
if( ChildGraphs[0] && ChildGraphs[1] )
{
if( ChildGraphs[0]->Num > 256 )
{
FGraphEventRef Task = TGraphTask< FBuildTask >::CreateTask().ConstructAndDispatchWhenReady( Partitioner, ChildGraphs[0], DesiredThread);
MyCompletionEvent->DontCompleteUntil( Task );
}
else
{
FBuildTask( Partitioner, ChildGraphs[0], DesiredThread).DoTask( CurrentThread, MyCompletionEvent );
}
FBuildTask( Partitioner, ChildGraphs[1], DesiredThread).DoTask( CurrentThread, MyCompletionEvent );
}
}
static FORCEINLINE TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FBuildTask, STATGROUP_ThreadPoolAsyncTasks);
}
static FORCEINLINE ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
FORCEINLINE ENamedThreads::Type GetDesiredThread() const
{
return DesiredThread;
}
private:
FGraphPartitioner* Partitioner;
FGraphData* Graph;
ENamedThreads::Type DesiredThread;
};
FGraphEventRef BuildTask = TGraphTask< FBuildTask >::CreateTask( nullptr ).ConstructAndDispatchWhenReady( this, Graph, DesiredThread);
FTaskGraphInterface::Get().WaitUntilTaskCompletes( BuildTask );
}
}
else
{
RecursiveBisectGraph( Graph );
}
Ranges.SetNum( NumPartitions );
if( bThreaded )
{
// Force a deterministic order
Ranges.Sort();
}
PartitionIDs.Empty();
SwappedWith.Empty();
}Nanite의 메싱과 관련하여 다음 지침을 따르세요.
Nanite 데이터 구축 시간을 단축하기 위해 에지 해시 처리, 공유 에지 및 경계 에지 감지, 클러스터 구축, 메시 분할, 임포스터 생성 등을 포함하되 이에 국한되지 않는 Nanite 구축 시 광범위한 병렬 처리가 사용되었습니다.
그리드를 분할할 때 GUseNewTaskBackend에 따라 새로운 백그라운드 작업 병렬 처리를 활성화할지 기존 TaskGraph를 활성화할지 결정합니다. 새로운 백그라운드 작업 시스템은 UE5에만 추가된 기능으로, 더욱 가볍고 간결해졌습니다.
그리드를 균등하게 분할할 때 타사 오픈 소스 라이브러리 METIS의 여러 주요 인터페이스(METIS_SetDefaultOptions, METIS_PartGraphKway, METIS_PartGraphRecursive)가 사용됩니다.
METIS는 그래프 분할, 유한 요소 메시 분할, 희소 행렬의 채우기 감소 순서 생성을 위한 일련의 직렬 프로그램입니다. METIS에 구현된 알고리즘은 Karypis Laboratory에서 개발한 다중 레벨 재귀 이분법, 다중 레벨 k-way 및 다중 제약 분할 방식을 기반으로 합니다. 주요 기능은 다음과 같습니다.
**고품질의 파티셔닝을 제공합니다.**METIS에서 생성된 파티션은 널리 사용되는 다른 알고리즘에서 생성된 파티션보다 지속적으로 성능이 뛰어납니다. METIS에서 생성된 파티션은 스펙트럼 분할 알고리즘으로 생성된 파티션보다 지속적으로 10%~50% 더 우수합니다.
**매우 빠른 처리 속도.**광범위한 실습을 통해 METIS는 널리 사용되는 다른 분할 알고리즘보다 1~2배 빠른 것으로 나타났습니다. 현재 워크스테이션과 PC에서는 수백만 개의 정점이 있는 그래프를 몇 초 만에 256개 부분으로 나눌 수 있습니다.
**유효노출률이 낮은 결과를 생성합니다.**METIS가 생성한 채우기 감소 정렬은 다중 최소 각도를 포함하여 널리 사용되는 다른 알고리즘보다 훨씬 뛰어난 성능을 발휘합니다. METIS는 과학 컴퓨팅 및 선형 계획법에서 발생하는 여러 종류의 문제에 대해 희소 행렬 분해의 저장 및 계산 요구 사항을 몇 배로 줄일 수 있습니다. METIS에서 생성된 제거 트리는 다중 최소 차수 방법과 달리 병렬 직접 분해에 적합합니다. 또한 METIS는 이러한 정렬을 매우 빠르게 계산할 수 있습니다. 현재 워크스테이션과 PC에서는 수백만 개의 행이 있는 행렬을 몇 초 만에 재정렬할 수 있습니다.
또한 병렬 버전 ParMETIS도 있습니다. 자세한 내용은 그래프 제품군 및 하이퍼그래프 파티셔닝 소프트웨어의 공식 지침을 참조하세요.
- 메싱의 단계, 세부 사항, 논리는 상대적으로 복잡하지만 저자는 Nanite의 메싱 아이디어와 의도, 원고인 METIS Three Phases Coarsening Partitioning Uncoarsening과 Learning Boundary Edges for 3D-Mesh Segmentation 논문이 상대적으로 유사하다고 생각하므로 이에 따라 메싱 알고리즘과 프로세스를 설명하겠습니다.
모델을 여러 부분으로 나누어야 하는 경우 프린스턴 세분화 벤치마크를 사용하여 수동으로 나누거나 일부 알고리즘을 사용하여 자동으로 나눌 수 있습니다(아래).

위 그림에는 여러 쌍의 그래프 세트가 있습니다. 각 페어링 그래프의 왼쪽은 프린스턴 분할 원리에 따라 수동으로 분할되고(어두운 선은 수동으로 분할된 모서리를 나타냄), 페어링된 그래프의 오른쪽은 알고리즘에 의해 자동으로 분할됩니다(빨간색은 경계). 자동 파티셔닝 알고리즘이 수동 파티셔닝과 거의 일치할 수 있음을 알 수 있습니다.
자동 파티셔닝 알고리즘에는 딥러닝과 비전을 결합한 방법뿐만 아니라 METIS와 같은 수학을 기반으로 하는 기존 알고리즘도 포함됩니다. METIS 파티셔닝 알고리즘은 정리(Coarsening), 파티셔닝(Partitioning), 비대화(Uncoarsening)의 세 단계로 구성됩니다.

조대화 단계에서는 일치를 최대화합니다. 공통 정점이 없는 가장자리 집합에서는 검색 복잡성에 NP-완전 문제가 있습니다.

대화는 최대화 모서리를 일치시킬 때 NP-완전 문제가 있습니다. 예를 들어, 그룹 a는 공유되지 않은 버텍스 가장자리의 수가 가장 많지 않지만 그룹 b는 그렇습니다.
파티셔닝 단계에서는 두 단계가 필요합니다. 첫 번째 단계는 루트를 무작위로 선택하는 것이고, 두 번째 단계는 더 적은 수의 접선을 얻을 수 있는 정점을 포함하는 BFS(Breadth First Search)입니다.

Uncoarsening 단계의 핵심 아이디어: 각 상위 노드에는 일련의 하위 노드가 포함되어 있어 정점을 한 파티션에서 다른 파티션으로 이동하여 가장자리 절단을 줄입니다.

6.4.2.5 BuildDAG
// Engine\Source\Developer\NaniteBuilder\Private\ClusterDAG.cpp
// Cluster의 루프 .
void BuildDAG( TArray< FClusterGroup >& Groups, TArray< FCluster >& Clusters, uint32 ClusterRangeStart, uint32 ClusterRangeNum, uint32 MeshIndex, FBounds& MeshBounds )
{
uint32 LevelOffset = ClusterRangeStart;
TAtomic< uint32 > NumClusters( Clusters.Num() );
uint32 NumExternalEdges = 0;
bool bFirstLevel = true;
while( true )
{
TArrayView< FCluster > LevelClusters( &Clusters[LevelOffset], bFirstLevel ? ClusterRangeNum : (Clusters.Num() - LevelOffset) );
bFirstLevel = false;
for( FCluster& Cluster : LevelClusters )
{
NumExternalEdges += Cluster.NumExternalEdges;
MeshBounds += Cluster.Bounds;
}
if( LevelClusters.Num() < 2 )
break;
// 만약 단계 의 Cluster개 의 개수 , 추가(Add)까지 리스트 .
if( LevelClusters.Num() <= MaxGroupSize )
{
TArray< uint32, TInlineAllocator< MaxGroupSize > > Children;
uint32 MaxParents = 0;
for( FCluster& Cluster : LevelClusters )
{
MaxParents += FMath::DivideAndRoundUp< uint32 >( Cluster.Indexes.Num(), FCluster::ClusterSize * 6 );
Children.Add( LevelOffset++ );
}
LevelOffset = Clusters.Num();
Clusters.AddDefaulted( MaxParents );
Groups.AddDefaulted( 1 );
// 을(를) 활용하여 DAG포인트 추가(Add)까지 .
DAGReduce( Groups, Clusters, NumClusters, Children, Groups.Num() - 1, MeshIndex );
// Correct num to atomic count
Clusters.SetNum( NumClusters, false );
continue;
}
// 단계 의 Cluster개수 MaxGroupSize, 을(를) 활용하여 FGraphPartitioner.
// 엣지(Edge)구조체
struct FExternalEdge
{
uint32 ClusterIndex;
uint32 EdgeIndex;
};
// 엣지(Edge)리스트 .
TArray< FExternalEdge > ExternalEdges;
FHashTable ExternalEdgeHash;
TAtomic< uint32 > ExternalEdgeOffset(0);
// NumExternalEdges의 ,따라서 로써 할당(Allocate)개 의 테이블 。
ExternalEdges.AddUninitialized( NumExternalEdges );
ExternalEdgeHash.Clear( 1 << FMath::FloorLog2( NumExternalEdges ), NumExternalEdges );
NumExternalEdges = 0;
// 병렬 추가(Add)엣지(Edge)까지 테이블 .
ParallelFor( LevelClusters.Num(),
[&]( uint32 ClusterIndex )
{
FCluster& Cluster = LevelClusters[ ClusterIndex ];
for( TConstSetBitIterator<> SetBit( Cluster.ExternalEdges ); SetBit; ++SetBit )
{
uint32 EdgeIndex = SetBit.GetIndex();
uint32 VertIndex0 = Cluster.Indexes[ EdgeIndex ];
uint32 VertIndex1 = Cluster.Indexes[ Cycle3( EdgeIndex ) ];
const FVector& Position0 = Cluster.GetPosition( VertIndex0 );
const FVector& Position1 = Cluster.GetPosition( VertIndex1 );
uint32 Hash0 = HashPosition( Position0 );
uint32 Hash1 = HashPosition( Position1 );
uint32 Hash = Murmur32( { Hash0, Hash1 } );
uint32 ExternalEdgeIndex = ExternalEdgeOffset++;
ExternalEdges[ ExternalEdgeIndex ] = { ClusterIndex, EdgeIndex };
ExternalEdgeHash.Add_Concurrent( Hash, ExternalEdgeIndex );
}
});
check( ExternalEdgeOffset == ExternalEdges.Num() );
TAtomic< uint32 > NumAdjacency(0);
// 병렬 Cluster탐색/조회(Find/Lookup)매칭 엣지(Edge).
ParallelFor( LevelClusters.Num(),
[&]( uint32 ClusterIndex )
{
FCluster& Cluster = LevelClusters[ ClusterIndex ];
for( TConstSetBitIterator<> SetBit( Cluster.ExternalEdges ); SetBit; ++SetBit )
{
uint32 EdgeIndex = SetBit.GetIndex();
uint32 VertIndex0 = Cluster.Indexes[ EdgeIndex ];
uint32 VertIndex1 = Cluster.Indexes[ Cycle3( EdgeIndex ) ];
const FVector& Position0 = Cluster.GetPosition( VertIndex0 );
const FVector& Position1 = Cluster.GetPosition( VertIndex1 );
uint32 Hash0 = HashPosition( Position0 );
uint32 Hash1 = HashPosition( Position1 );
uint32 Hash = Murmur32( { Hash1, Hash0 } );
for( uint32 ExternalEdgeIndex = ExternalEdgeHash.First( Hash ); ExternalEdgeHash.IsValid( ExternalEdgeIndex ); ExternalEdgeIndex = ExternalEdgeHash.Next( ExternalEdgeIndex ) )
{
FExternalEdge ExternalEdge = ExternalEdges[ ExternalEdgeIndex ];
FCluster& OtherCluster = LevelClusters[ ExternalEdge.ClusterIndex ];
if( OtherCluster.ExternalEdges[ ExternalEdge.EdgeIndex ] )
{
uint32 OtherVertIndex0 = OtherCluster.Indexes[ ExternalEdge.EdgeIndex ];
uint32 OtherVertIndex1 = OtherCluster.Indexes[ Cycle3( ExternalEdge.EdgeIndex ) ];
if( Position0 == OtherCluster.GetPosition( OtherVertIndex1 ) &&
Position1 == OtherCluster.GetPosition( OtherVertIndex0 ) )
{
// 까지 매칭 엣지(Edge), 추가(Add).
Cluster.AdjacentClusters.FindOrAdd( ExternalEdge.ClusterIndex, 0 )++;
// Can't break or a triple edge might be non-deterministically connected.
// Need to find all matching, not just first.
}
}
}
}
NumAdjacency += Cluster.AdjacentClusters.Num();
// 의 。
Cluster.AdjacentClusters.KeySort(
[ &LevelClusters ]( uint32 A, uint32 B )
{
return LevelClusters[A].GUID < LevelClusters[B].GUID;
} );
});
// 연속 의 Cluster의 .
FDisjointSet DisjointSet( LevelClusters.Num() );
for( uint32 ClusterIndex = 0; ClusterIndex < (uint32)LevelClusters.Num(); ClusterIndex++ )
{
for( auto& Pair : LevelClusters[ ClusterIndex ].AdjacentClusters )
{
uint32 OtherClusterIndex = Pair.Key;
uint32 Count = LevelClusters[ OtherClusterIndex ].AdjacentClusters.FindChecked( ClusterIndex );
check( Count == Pair.Value );
if( ClusterIndex > OtherClusterIndex )
{
DisjointSet.UnionSequential( ClusterIndex, OtherClusterIndex );
}
}
}
// .
FGraphPartitioner Partitioner( LevelClusters.Num() );
// 정렬 로써 。
{
TArray< uint32 > SortedIndexes;
SortedIndexes.AddUninitialized( Partitioner.Indexes.Num() );
RadixSort32( SortedIndexes.GetData(), Partitioner.Indexes.GetData(), Partitioner.Indexes.Num(),
[&]( uint32 Index )
{
return LevelClusters[ Index ].GUID;
} );
Swap( Partitioner.Indexes, SortedIndexes );
}
auto GetCenter = [&]( uint32 Index )
{
FBounds& Bounds = LevelClusters[ Index ].Bounds;
return 0.5f * ( Bounds.Min + Bounds.Max );
};
// 위치 .
Partitioner.BuildLocalityLinks( DisjointSet, MeshBounds, GetCenter );
auto* RESTRICT Graph = Partitioner.NewGraph( NumAdjacency );
// 순회(Traverse/Iterate)레벨 단계 의 Cluster, 순회(Traverse/Iterate)개 레벨 단계 의 Cluster, 추가(Add) 및 위치 .
for( int32 i = 0; i < LevelClusters.Num(); i++ )
{
Graph->AdjacencyOffset[i] = Graph->Adjacency.Num();
uint32 ClusterIndex = Partitioner.Indexes[i];
for( auto& Pair : LevelClusters[ ClusterIndex ].AdjacentClusters )
{
uint32 OtherClusterIndex = Pair.Key;
uint32 NumSharedEdges = Pair.Value;
const auto& Cluster0 = Clusters[ LevelOffset + ClusterIndex ];
const auto& Cluster1 = Clusters[ LevelOffset + OtherClusterIndex ];
bool bSiblings = Cluster0.GroupIndex != MAX_uint32 && Cluster0.GroupIndex == Cluster1.GroupIndex;
Partitioner.AddAdjacency( Graph, OtherClusterIndex, NumSharedEdges * ( bSiblings ? 1 : 16 ) + 4 );
}
Partitioner.AddLocalityLinks( Graph, ClusterIndex, 1 );
}
Graph->AdjacencyOffset[ Graph->Num ] = Graph->Adjacency.Num();
LOG_CRC( Graph->Adjacency );
LOG_CRC( Graph->AdjacencyCost );
LOG_CRC( Graph->AdjacencyOffset );
// .
Partitioner.PartitionStrict( Graph, MinGroupSize, MaxGroupSize, true );
LOG_CRC( Partitioner.Ranges );
// 계산/산출(Calculate)개수 .
uint32 MaxParents = 0;
for( auto& Range : Partitioner.Ranges )
{
uint32 NumParentIndexes = 0;
for( uint32 i = Range.Begin; i < Range.End; i++ )
{
// Global indexing is needed in Reduce()
Partitioner.Indexes[i] += LevelOffset;
NumParentIndexes += Clusters[ Partitioner.Indexes[i] ].Indexes.Num();
}
MaxParents += FMath::DivideAndRoundUp( NumParentIndexes, FCluster::ClusterSize * 6 );
}
LevelOffset = Clusters.Num();
Clusters.AddDefaulted( MaxParents );
Groups.AddDefaulted( Partitioner.Ranges.Num() );
// 병렬 실행(Execute)DAG.
ParallelFor( Partitioner.Ranges.Num(),
[&]( int32 PartitionIndex )
{
auto& Range = Partitioner.Ranges[ PartitionIndex ];
TArrayView< uint32 > Children( &Partitioner.Indexes[ Range.Begin ], Range.End - Range.Begin );
uint32 ClusterGroupIndex = PartitionIndex + Groups.Num() - Partitioner.Ranges.Num();
DAGReduce( Groups, Clusters, NumClusters, Children, ClusterGroupIndex, MeshIndex );
});
// Correct num to atomic count
Clusters.SetNum( NumClusters, false );
}
// 출력포인트 .
uint32 RootIndex = LevelOffset;
FClusterGroup RootClusterGroup;
RootClusterGroup.Children.Add( RootIndex );
RootClusterGroup.Bounds = Clusters[ RootIndex ].SphereBounds;
RootClusterGroup.LODBounds = FSphere( 0 );
RootClusterGroup.MaxParentLODError = 1e10f;
RootClusterGroup.MinLODError = -1.0f;
RootClusterGroup.MipLevel = Clusters[RootIndex].MipLevel + 1;
RootClusterGroup.MeshIndex = MeshIndex;
Clusters[ RootIndex ].GroupIndex = Groups.Num();
Groups.Add( RootClusterGroup );
}DAGReduce는 위에서 여러 번 실행되었으며 그 구현이 간략하게 분석되었습니다.
static void DAGReduce( TArray< FClusterGroup >& Groups, TArray< FCluster >& Clusters, TAtomic< uint32 >& NumClusters, TArrayView< uint32 > Children, int32 GroupIndex, uint32 MeshIndex )
{
check( GroupIndex >= 0 );
// Cluster.
TArray< const FCluster*, TInlineAllocator<16> > MergeList;
for( int32 Child : Children )
{
MergeList.Add( &Clusters[ Child ] );
}
// 。
MergeList.Sort(
[]( const FCluster& A, const FCluster& B )
{
return A.GUID < B.GUID;
} );
FCluster Merged( MergeList );
int32 NumParents = FMath::DivideAndRoundUp< int32 >( Merged.Indexes.Num(), FCluster::ClusterSize * 6 );
int32 ParentStart = 0;
int32 ParentEnd = 0;
float ParentMaxLODError = 0.0f;
// ※ 주의: TargetClusterSize의 -2.
for( int32 TargetClusterSize = FCluster::ClusterSize - 2; TargetClusterSize > FCluster::ClusterSize / 2; TargetClusterSize -= 2 )
{
int32 TargetNumTris = NumParents * TargetClusterSize;
// , 반환(Return)포인트 LOD.
ParentMaxLODError = Merged.Simplify( TargetNumTris );
//
if( NumParents == 1 )
{
ParentEnd = ( NumClusters += NumParents );
ParentStart = ParentEnd - NumParents;
Clusters[ ParentStart ] = Merged;
Clusters[ ParentStart ].Bound();
break;
}
else
{
FGraphPartitioner Partitioner( Merged.Indexes.Num() / 3 );
Merged.Split( Partitioner );
if( Partitioner.Ranges.Num() <= NumParents )
{
NumParents = Partitioner.Ranges.Num();
ParentEnd = ( NumClusters += NumParents );
ParentStart = ParentEnd - NumParents;
int32 Parent = ParentStart;
for( auto& Range : Partitioner.Ranges )
{
Clusters[ Parent ] = FCluster( Merged, Range.Begin, Range.End, Partitioner.Indexes );
Parent++;
}
break;
}
}
}
TArray< FSphere, TInlineAllocator<32> > Children_LODBounds;
TArray< FSphere, TInlineAllocator<32> > Children_SphereBounds;
// (monotonic nesting).
float ChildMinLODError = MAX_flt;
for( int32 Child : Children )
{
bool bLeaf = Clusters[ Child ].EdgeLength < 0.0f;
float LODError = Clusters[ Child ].LODError;
Children_LODBounds.Add( Clusters[ Child ].LODBounds );
Children_SphereBounds.Add( Clusters[ Child ].SphereBounds );
ChildMinLODError = FMath::Min( ChildMinLODError, bLeaf ? -1.0f : LODError );
ParentMaxLODError = FMath::Max( ParentMaxLODError, LODError );
Clusters[ Child ].GroupIndex = GroupIndex;
Groups[ GroupIndex ].Children.Add( Child );
check( Groups[ GroupIndex ].Children.Num() <= MAX_CLUSTERS_PER_GROUP_TARGET );
}
FSphere ParentLODBounds( Children_LODBounds.GetData(), Children_LODBounds.Num() );
FSphere ParentBounds( Children_SphereBounds.GetData(), Children_SphereBounds.Num() );
// 포인트 의 LOD데이터 , .
for( int32 Parent = ParentStart; Parent < ParentEnd; Parent++ )
{
Clusters[ Parent ].LODBounds = ParentLODBounds;
Clusters[ Parent ].LODError = ParentMaxLODError;
Clusters[ Parent ].GeneratingGroupIndex = GroupIndex;
}
Groups[ GroupIndex ].Bounds = ParentBounds;
Groups[ GroupIndex ].LODBounds = ParentLODBounds;
Groups[ GroupIndex ].MinLODError = ChildMinLODError;
Groups[ GroupIndex ].MaxParentLODError = ParentMaxLODError;
Groups[ GroupIndex ].MipLevel = Merged.MipLevel - 1;
Groups[ GroupIndex ].MeshIndex = MeshIndex;
}6.4.2.6 BuildCoarseRepresentation
BuildCoarseRepresentation은 입력 클러스터 목록 및 클러스터 그룹 목록을 기반으로 그리드의 대략적인 표현을 작성하고 해당 버텍스, 인덱스, 섹션 및 기타 데이터를 출력합니다.
static void BuildCoarseRepresentation(
const TArray<FClusterGroup>& Groups,
const TArray<FCluster>& Clusters,
TArray<FStaticMeshBuildVertex>& Verts,
TArray<uint32>& Indexes,
TArray<FStaticMeshSection, TInlineAllocator<1>>& Sections,
uint32& NumTexCoords,
uint32 TargetNumTris
)
{
FCluster CoarseRepresentation = FindDAGCut(Groups, Clusters, TargetNumTris + 4096);
CoarseRepresentation.Simplify(TargetNumTris);
TArray< FStaticMeshSection, TInlineAllocator<1> > OldSections = Sections;
// 갱신(Update)거침(Rough)테이블 의 UV로써 매칭 의 데이터 。
NumTexCoords = CoarseRepresentation.NumTexCoords;
// 포인트 데이터 。
Verts.Empty(CoarseRepresentation.NumVerts);
for (uint32 Iter = 0, Num = CoarseRepresentation.NumVerts; Iter < Num; ++Iter)
{
FStaticMeshBuildVertex Vertex = {};
Vertex.Position = CoarseRepresentation.GetPosition(Iter);
Vertex.TangentX = FVector::ZeroVector;
Vertex.TangentY = FVector::ZeroVector;
Vertex.TangentZ = CoarseRepresentation.GetNormal(Iter);
const FVector2D* UVs = CoarseRepresentation.GetUVs(Iter);
for (uint32 UVIndex = 0; UVIndex < NumTexCoords; ++UVIndex)
{
Vertex.UVs[UVIndex] = UVs[UVIndex].ContainsNaN() ? FVector2D::ZeroVector : UVs[UVIndex];
}
if (CoarseRepresentation.bHasColors)
{
Vertex.Color = CoarseRepresentation.GetColor(Iter).ToFColor(false /* sRGB */);
}
Verts.Add(Vertex);
}
TArray<FMaterialTriangle, TInlineAllocator<128>> CoarseMaterialTris;
TArray<FMaterialRange, TInlineAllocator<4>> CoarseMaterialRanges;
// 계산/산출(Calculate)거침(Rough)테이블 의 머티리얼(Material)범위 .
BuildMaterialRanges(
CoarseRepresentation.Indexes,
CoarseRepresentation.MaterialIndexes,
CoarseMaterialTris,
CoarseMaterialRanges);
check(CoarseMaterialRanges.Num() <= OldSections.Num());
// section데이터 .
Sections.Reset(CoarseMaterialRanges.Num());
for (const FStaticMeshSection& OldSection : OldSections)
{
// 에 따라 계산/산출(Calculate)의 머티리얼(Material)범위 추가(Add)의 section.
// 머티리얼(Material) 및 OldSections.
const FMaterialRange* FoundRange = CoarseMaterialRanges.FindByPredicate([&OldSection](const FMaterialRange& Range) { return Range.MaterialIndex == OldSection.MaterialIndex; });
// 만약 의 데이터 의 ,로써 로부터 거침(Rough) 내에서 .
if (FoundRange)
{
// 로부터 section。
FStaticMeshSection Section(OldSection);
// 렌더링(Render)section을(를) 활용하여 의 포인트 및 의 범위 .
Section.FirstIndex = FoundRange->RangeStart * 3;
Section.NumTriangles = FoundRange->RangeLength;
Section.MinVertexIndex = TNumericLimits<uint32>::Max();
Section.MaxVertexIndex = TNumericLimits<uint32>::Min();
for (uint32 TriangleIndex = 0; TriangleIndex < (FoundRange->RangeStart + FoundRange->RangeLength); ++TriangleIndex)
{
const FMaterialTriangle& Triangle = CoarseMaterialTris[TriangleIndex];
// 갱신(Update)포인트 .
Section.MinVertexIndex = FMath::Min(Section.MinVertexIndex, Triangle.Index0);
Section.MinVertexIndex = FMath::Min(Section.MinVertexIndex, Triangle.Index1);
Section.MinVertexIndex = FMath::Min(Section.MinVertexIndex, Triangle.Index2);
// 갱신(Update)포인트 .
Section.MaxVertexIndex = FMath::Max(Section.MaxVertexIndex, Triangle.Index0);
Section.MaxVertexIndex = FMath::Max(Section.MaxVertexIndex, Triangle.Index1);
Section.MaxVertexIndex = FMath::Max(Section.MaxVertexIndex, Triangle.Index2);
}
Sections.Add(Section);
}
}
// 데이터 .
Indexes.Reset();
for (const FMaterialTriangle& Triangle : CoarseMaterialTris)
{
Indexes.Add(Triangle.Index0);
Indexes.Add(Triangle.Index1);
Indexes.Add(Triangle.Index2);
}
// 계산/산출(Calculate).
CalcTangents(Verts, Indexes);
}6.4.2.7 NaniteEncode
인코딩은 FMeshNaniteSettings에 따라 Nanite 리소스를 클러스터 및 클러스터 그룹으로 인코딩합니다.
// Engine\Source\Developer\NaniteBuilder\Private\NaniteEncode.cpp
void Encode(
FResources& Resources,
const FMeshNaniteSettings& Settings,
TArray< FCluster >& Clusters,
TArray< FClusterGroup >& Groups,
const FBounds& MeshBounds,
uint32 NumMeshes,
uint32 NumTexCoords,
bool bHasColors )
{
// 의 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::RemoveDegenerateTriangles);
RemoveDegenerateTriangles( Clusters );
}
// 머티리얼(Material)범위 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::BuildMaterialRanges);
BuildMaterialRanges( Clusters );
}
// Cluster.
#if USE_CONSTRAINED_CLUSTERS
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::ConstrainClusters);
ConstrainClusters( Groups, Clusters );
}
(......)
#endif
// 계산/산출(Calculate)양자화 의 위치 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::CalculateQuantizedPositions);
// cluster 및 트리거 。
Resources.PositionPrecision = CalculateQuantizedPositionsUniformGrid( Clusters, MeshBounds, Settings );
}
// 출력머티리얼(Material)범위 정보 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::PrintMaterialRangeStats);
PrintMaterialRangeStats( Clusters );
}
TArray<FPage> Pages;
TArray<FClusterGroupPart> GroupParts;
TArray<FEncodingInfo> EncodingInfos;
// 계산/산출(Calculate)정보 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::CalculateEncodingInfos);
CalculateEncodingInfos(EncodingInfos, Clusters, bHasColors, NumTexCoords);
}
// 할당(Allocate)Cluster까지 Page테이블 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::AssignClustersToPages);
AssignClustersToPages(Groups, Clusters, EncodingInfos, Pages, GroupParts);
}
// Cluster의 레벨 단계 포인트 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::BuildHierarchyNodes);
BuildHierarchies(Resources, Groups, GroupParts, NumMeshes);
}
// Cluster 및 Cluster의 정보 기록/쓰기(Write)Page테이블 .
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::WritePages);
WritePages(Resources, Pages, Groups, GroupParts, Clusters, EncodingInfos, NumTexCoords);
}
}위의 코딩 프로세스에는 많은 중요한 인터페이스가 포함됩니다. 하나씩 분석해 보겠습니다.
// Engine\Source\Developer\NaniteBuilder\Private\NaniteEncode.cpp
static void RemoveDegenerateTriangles(TArray<FCluster>& Clusters)
{
// 병렬 Cluster리스트 의 .
ParallelFor( Clusters.Num(),
[&]( uint32 ClusterIndex )
{
RemoveDegenerateTriangles( Clusters[ ClusterIndex ] );
} );
}
// 개 Cluster의 .
static void RemoveDegenerateTriangles(FCluster& Cluster)
{
uint32 NumOldTriangles = Cluster.NumTris;
uint32 NumNewTriangles = 0;
for (uint32 OldTriangleIndex = 0; OldTriangleIndex < NumOldTriangles; OldTriangleIndex++)
{
uint32 i0 = Cluster.Indexes[OldTriangleIndex * 3 + 0];
uint32 i1 = Cluster.Indexes[OldTriangleIndex * 3 + 1];
uint32 i2 = Cluster.Indexes[OldTriangleIndex * 3 + 2];
uint32 mi = Cluster.MaterialIndexes[OldTriangleIndex];
// 만약 는 , 이면 3개 포인트 의 데이터 .
// : 로써 최적화 , 개 의 개 포인트 의 거리개 임계값(Threshold)(0.01f).
if (i0 != i1 && i0 != i2 && i1 != i2)
{
Cluster.Indexes[NumNewTriangles * 3 + 0] = i0;
Cluster.Indexes[NumNewTriangles * 3 + 1] = i1;
Cluster.Indexes[NumNewTriangles * 3 + 2] = i2;
Cluster.MaterialIndexes[NumNewTriangles] = mi;
NumNewTriangles++;
}
}
Cluster.NumTris = NumNewTriangles;
Cluster.Indexes.SetNum(NumNewTriangles * 3);
Cluster.MaterialIndexes.SetNum(NumNewTriangles);
}
// Cluster까지 머티리얼(Material)범위 , 추가(Add)머티리얼(Material)범위 까지 Cluster。
static void BuildMaterialRanges( TArray<FCluster>& Clusters )
{
// 병렬 처리(Process).
ParallelFor( Clusters.Num(),
[&]( uint32 ClusterIndex )
{
BuildMaterialRanges( Clusters[ ClusterIndex ] );
} );
}
static void BuildMaterialRanges(FCluster& Cluster)
{
TArray<FMaterialTriangle, TInlineAllocator<128>> MaterialTris;
// 개 Cluster의 머티리얼(Material)범위 .
BuildMaterialRanges(
Cluster.Indexes,
Cluster.MaterialIndexes,
MaterialTris,
Cluster.MaterialRanges);
// 까지 Cluster.
for (uint32 Triangle = 0; Triangle < Cluster.NumTris; ++Triangle)
{
Cluster.Indexes[Triangle * 3 + 0] = MaterialTris[Triangle].Index0;
Cluster.Indexes[Triangle * 3 + 1] = MaterialTris[Triangle].Index1;
Cluster.Indexes[Triangle * 3 + 2] = MaterialTris[Triangle].Index2;
Cluster.MaterialIndexes[Triangle] = MaterialTris[Triangle].MaterialIndex;
}
}
// Cluster.
static void ConstrainClusters( TArray< FClusterGroup >& ClusterGroups, TArray< FCluster >& Clusters )
{
// 계산/산출(Calculate)정보 .
uint32 TotalOldTriangles = 0;
uint32 TotalOldVertices = 0;
for( const FCluster& Cluster : Clusters )
{
TotalOldTriangles += Cluster.NumTris;
TotalOldVertices += Cluster.NumVerts;
}
// 병렬 Cluster, 는 을(를) 활용하여 .
ParallelFor( Clusters.Num(),
[&]( uint32 i )
{
#if USE_STRIP_INDICES // 을(를) 활용하여 .
FStripifier Stripifier;
Stripifier.ConstrainAndStripifyCluster(Clusters[i]);
#else // 을(를) 활용하여 .
ConstrainClusterFIFO(Clusters[i]);
#endif
} );
uint32 TotalNewTriangles = 0;
uint32 TotalNewVertices = 0;
// cluster.
const uint32 NumOldClusters = Clusters.Num();
for( uint32 i = 0; i < NumOldClusters; i++ )
{
TotalNewTriangles += Clusters[ i ].NumTris;
TotalNewVertices += Clusters[ i ].NumVerts;
// 만약 Cluster포인트 (256개 ), 이면 .
if( Clusters[ i ].NumVerts > 256 )
{
FCluster ClusterA, ClusterB;
uint32 NumTrianglesA = Clusters[ i ].NumTris / 2;
uint32 NumTrianglesB = Clusters[ i ].NumTris - NumTrianglesA;
BuildClusterFromClusterTriangleRange( Clusters[ i ], ClusterA, 0, NumTrianglesA );
BuildClusterFromClusterTriangleRange( Clusters[ i ], ClusterB, NumTrianglesA, NumTrianglesB );
Clusters[ i ] = ClusterA;
ClusterGroups[ ClusterB.GroupIndex ].Children.Add( Clusters.Num() );
Clusters.Add( ClusterB );
}
}
// 계산/산출(Calculate)정보 .
uint32 TotalNewTrianglesWithSplits = 0;
uint32 TotalNewVerticesWithSplits = 0;
for( const FCluster& Cluster : Clusters )
{
TotalNewTrianglesWithSplits += Cluster.NumTris;
TotalNewVerticesWithSplits += Cluster.NumVerts;
}
(......)
}
// 계산/산출(Calculate)양자화 위치 의 .
static int32 CalculateQuantizedPositionsUniformGrid(TArray< FCluster >& Clusters, const FBounds& MeshBounds, const FMeshNaniteSettings& Settings)
{
// 로 EA글로벌 의 양자화 .
const int32 MaxPositionQuantizedValue = (1 << MAX_POSITION_QUANTIZATION_BITS) - 1;
int32 PositionPrecision = Settings.PositionPrecision;
if (PositionPrecision == MIN_int32)
{
// : 로부터 레벨 단계 의 의 정밀도 .
const float MaxSize = MeshBounds.GetExtent().GetMax();
// : 만약 ,의 해상도 .
// 을(를) 활용하여 cluster크기 의 평균값(Mean)로써 의 .
// : 정밀도 는 cluster의 평균값(Mean).
// 크기 의 cluster,의 결과 및 의 양자화 코드 .
double TotalLogSize = 0.0;
int32 TotalNum = 0;
for (const FCluster& Cluster : Clusters)
{
if (Cluster.MipLevel == 0)
{
float ExtentSize = Cluster.Bounds.GetExtent().Size();
if (ExtentSize > 0.0)
{
TotalLogSize += FMath::Log2(ExtentSize);
TotalNum++;
}
}
}
double AvgLogSize = TotalNum > 0 ? TotalLogSize / TotalNum : 0.0;
PositionPrecision = 7 - FMath::RoundToInt(AvgLogSize);
// 정밀도 . 을(를) 활용하여 정밀도 설정(Set).
// 설정(Set),크기 의 ( 내에서 로 0.4%), 따라서 .
// 예: :개 해상도 의 또는 ,상태 정밀도 , 그러나 개 내에서 의 정밀도 ,이므로 의 또는 .
const int32 AUTO_MIN_PRECISION = 4; // 정밀도 는 1/16cm.
PositionPrecision = FMath::Max(PositionPrecision, AUTO_MIN_PRECISION);
}
// 계산/산출(Calculate)양자화 .
float QuantizationScale = FMath::Exp2((float)PositionPrecision);
// cluster는 의 。개 의 cluster까지 21bpc의 。만약 ,,까지 로 。
for (const FCluster& Cluster : Clusters)
{
const FBounds& Bounds = Cluster.Bounds;
int32 Iterations = 0;
while (true)
{
float MinX = FMath::RoundToFloat(Bounds.Min.X * QuantizationScale);
float MinY = FMath::RoundToFloat(Bounds.Min.Y * QuantizationScale);
float MinZ = FMath::RoundToFloat(Bounds.Min.Z * QuantizationScale);
float MaxX = FMath::RoundToFloat(Bounds.Max.X * QuantizationScale);
float MaxY = FMath::RoundToFloat(Bounds.Max.Y * QuantizationScale);
float MaxZ = FMath::RoundToFloat(Bounds.Max.Z * QuantizationScale);
if (MinX >= (double)MIN_int32 && MinY >= (double)MIN_int32 && MinZ >= (double)MIN_int32 && // MIN_int32/MAX_int32 is not representable in float
MaxX <= (double)MAX_int32 && MaxY <= (double)MAX_int32 && MaxZ <= (double)MAX_int32 &&
((int32)MaxX - (int32)MinX) <= MaxPositionQuantizedValue && ((int32)MaxY - (int32)MinY) <= MaxPositionQuantizedValue && ((int32)MaxZ - (int32)MinZ) <= MaxPositionQuantizedValue)
{
break;
}
QuantizationScale *= 0.5f;
PositionPrecision--;
check(++Iterations < 100); // Endless loop?
}
}
const float RcpQuantizationScale = 1.0f / QuantizationScale;
// 병렬 처리(Process)위치 양자화 .
ParallelFor(Clusters.Num(), [&](uint32 ClusterIndex)
{
FCluster& Cluster = Clusters[ClusterIndex];
const uint32 NumClusterVerts = Cluster.NumVerts;
const uint32 ClusterShift = Cluster.QuantizedPosShift;
Cluster.QuantizedPositions.SetNumUninitialized(NumClusterVerts);
// 양자화 위치 .
FIntVector IntClusterMax = { MIN_int32, MIN_int32, MIN_int32 };
FIntVector IntClusterMin = { MAX_int32, MAX_int32, MAX_int32 };
for (uint32 i = 0; i < NumClusterVerts; i++)
{
const FVector Position = Cluster.GetPosition(i);
FIntVector& IntPosition = Cluster.QuantizedPositions[i];
float PosX = FMath::RoundToFloat(Position.X * QuantizationScale);
float PosY = FMath::RoundToFloat(Position.Y * QuantizationScale);
float PosZ = FMath::RoundToFloat(Position.Z * QuantizationScale);
IntPosition = FIntVector((int32)PosX, (int32)PosY, (int32)PosZ);
IntClusterMax.X = FMath::Max(IntClusterMax.X, IntPosition.X);
IntClusterMax.Y = FMath::Max(IntClusterMax.Y, IntPosition.Y);
IntClusterMax.Z = FMath::Max(IntClusterMax.Z, IntPosition.Z);
IntClusterMin.X = FMath::Min(IntClusterMin.X, IntPosition.X);
IntClusterMin.Y = FMath::Min(IntClusterMin.Y, IntPosition.Y);
IntClusterMin.Z = FMath::Min(IntClusterMin.Z, IntPosition.Z);
}
// 저장(Store).
const uint32 NumBitsX = FMath::CeilLogTwo(IntClusterMax.X - IntClusterMin.X + 1);
const uint32 NumBitsY = FMath::CeilLogTwo(IntClusterMax.Y - IntClusterMin.Y + 1);
const uint32 NumBitsZ = FMath::CeilLogTwo(IntClusterMax.Z - IntClusterMin.Z + 1);
check(NumBitsX <= MAX_POSITION_QUANTIZATION_BITS);
check(NumBitsY <= MAX_POSITION_QUANTIZATION_BITS);
check(NumBitsZ <= MAX_POSITION_QUANTIZATION_BITS);
for (uint32 i = 0; i < NumClusterVerts; i++)
{
FIntVector& IntPosition = Cluster.QuantizedPositions[i];
// 을(를) 활용하여 양자화 데이터 갱신(Update)포인트 위치 .
Cluster.GetPosition(i) = FVector(IntPosition.X * RcpQuantizationScale, IntPosition.Y * RcpQuantizationScale, IntPosition.Z * RcpQuantizationScale);
IntPosition.X -= IntClusterMin.X;
IntPosition.Y -= IntClusterMin.Y;
IntPosition.Z -= IntClusterMin.Z;
check(IntPosition.X >= 0 && IntPosition.X < (1 << NumBitsX));
check(IntPosition.Y >= 0 && IntPosition.Y < (1 << NumBitsY));
check(IntPosition.Z >= 0 && IntPosition.Z < (1 << NumBitsZ));
}
// 갱신(Update)바운딩 박스(AABB).
Cluster.Bounds.Min = FVector(IntClusterMin.X * RcpQuantizationScale, IntClusterMin.Y * RcpQuantizationScale, IntClusterMin.Z * RcpQuantizationScale);
Cluster.Bounds.Max = FVector(IntClusterMax.X * RcpQuantizationScale, IntClusterMax.Y * RcpQuantizationScale, IntClusterMax.Z * RcpQuantizationScale);
Cluster.MeshBoundsMin = FVector::ZeroVector;
Cluster.MeshBoundsDelta = FVector(RcpQuantizationScale);
Cluster.QuantizedPosBits = FIntVector(NumBitsX, NumBitsY, NumBitsZ);
Cluster.QuantizedPosStart = IntClusterMin;
Cluster.QuantizedPosShift = 0;
} );
return PositionPrecision;
}
// 계산/산출(Calculate)Cluster의 정보 .
static void CalculateEncodingInfos(TArray<FEncodingInfo>& EncodingInfos, const TArray<Nanite::FCluster>& Clusters, bool bHasColors, uint32 NumTexCoords)
{
uint32 NumClusters = Clusters.Num();
EncodingInfos.SetNumUninitialized(NumClusters);
for (uint32 i = 0; i < NumClusters; i++)
{
CalculateEncodingInfo(EncodingInfos[i], Clusters[i], bHasColors, NumTexCoords);
}
}
// 계산/산출(Calculate)개 Cluster의 정보 .
static void CalculateEncodingInfo(FEncodingInfo& Info, const Nanite::FCluster& Cluster, bool bHasColors, uint32 NumTexCoords)
{
const uint32 NumClusterVerts = Cluster.NumVerts;
const uint32 NumClusterTris = Cluster.NumTris;
FMemory::Memzero(Info);
// 。저장(Store)개 의 내에서 ,개 을(를) 활용하여 ceil(log2(NumClusterVerices))。셰이딩 구현 정렬 의 로드/읽기(Read)지원 포인트 。
const uint32 BitsPerIndex = NumClusterVerts > 1 ? (FGenericPlatformMath::FloorLog2(NumClusterVerts - 1) + 1) : 0;
const uint32 BitsPerTriangle = BitsPerIndex + 2 * 5; // Base index + two 5-bit offsets
Info.BitsPerIndex = BitsPerIndex;
// 계산/산출(Calculate)정보 .
FPageSections& GpuSizes = Info.GpuSizes;
GpuSizes.Cluster = sizeof(FPackedCluster);
GpuSizes.MaterialTable = CalcMaterialTableSize(Cluster) * sizeof(uint32);
GpuSizes.DecodeInfo = NumTexCoords * sizeof(FUVRange);
GpuSizes.Index = (NumClusterTris * BitsPerTriangle + 31) / 32 * 4;
#if USE_UNCOMPRESSED_VERTEX_DATA // 을(를) 활용하여 압축/패킹(Packing)의 포인트 데이터 .
const uint32 AttribBytesPerVertex = (3 * sizeof(float) + sizeof(uint32) + NumTexCoords * 2 * sizeof(float));
Info.BitsPerAttribute = AttribBytesPerVertex * 8;
Info.ColorMin = FIntVector4(0, 0, 0, 0);
Info.ColorBits = FIntVector4(8, 8, 8, 8);
Info.ColorMode = VERTEX_COLOR_MODE_VARIABLE;
Info.UVPrec = 0;
GpuSizes.Position = NumClusterVerts * 3 * sizeof(float);
GpuSizes.Attribute = NumClusterVerts * AttribBytesPerVertex;
#else // 을(를) 활용하여 압축/패킹(Packing)의 포인트 데이터 .
Info.BitsPerAttribute = 2 * NORMAL_QUANTIZATION_BITS;
check(NumClusterVerts > 0);
const bool bIsLeaf = (Cluster.GeneratingGroupIndex == INVALID_GROUP_INDEX);
// 포인트 컬러 .
Info.ColorMode = VERTEX_COLOR_MODE_WHITE;
Info.ColorMin = FIntVector4(255, 255, 255, 255);
if (bHasColors)
{
FIntVector4 ColorMin = FIntVector4( 255, 255, 255, 255);
FIntVector4 ColorMax = FIntVector4( 0, 0, 0, 0);
for (uint32 i = 0; i < NumClusterVerts; i++)
{
FColor Color = Cluster.GetColor(i).ToFColor(false);
ColorMin.X = FMath::Min(ColorMin.X, (int32)Color.R);
ColorMin.Y = FMath::Min(ColorMin.Y, (int32)Color.G);
ColorMin.Z = FMath::Min(ColorMin.Z, (int32)Color.B);
ColorMin.W = FMath::Min(ColorMin.W, (int32)Color.A);
ColorMax.X = FMath::Max(ColorMax.X, (int32)Color.R);
ColorMax.Y = FMath::Max(ColorMax.Y, (int32)Color.G);
ColorMax.Z = FMath::Max(ColorMax.Z, (int32)Color.B);
ColorMax.W = FMath::Max(ColorMax.W, (int32)Color.A);
}
const FIntVector4 ColorDelta = ColorMax - ColorMin;
const int32 R_Bits = FMath::CeilLogTwo(ColorDelta.X + 1);
const int32 G_Bits = FMath::CeilLogTwo(ColorDelta.Y + 1);
const int32 B_Bits = FMath::CeilLogTwo(ColorDelta.Z + 1);
const int32 A_Bits = FMath::CeilLogTwo(ColorDelta.W + 1);
uint32 NumColorBits = R_Bits + G_Bits + B_Bits + A_Bits;
Info.BitsPerAttribute += NumColorBits;
Info.ColorMin = ColorMin;
Info.ColorBits = FIntVector4(R_Bits, G_Bits, B_Bits, A_Bits);
if (NumColorBits > 0)
{
Info.ColorMode = VERTEX_COLOR_MODE_VARIABLE;
}
else
{
if (ColorMin.X == 255 && ColorMin.Y == 255 && ColorMin.Z == 255 && ColorMin.W == 255)
Info.ColorMode = VERTEX_COLOR_MODE_WHITE;
else
Info.ColorMode = VERTEX_COLOR_MODE_CONSTANT;
}
}
for( uint32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++ )
{
FGeometryEncodingUVInfo& UVInfo = Info.UVInfos[UVIndex];
// 타일드(Tiled)압축/패킹(Packing)텍스처(Texture)좌표 .
// 텍스처(Texture)좌표 Cluster의 /UV좌표 저장(Store).
// UV의 희소 모멘트(Moment). 를 위해 포인트 ,의 U 및 V의 모멘트(Moment).
// 개 : UV += (UV >= GapStart) ? GapRange : 0;
// 의 U 및 V배열 .
TArray<float> UValues;
TArray<float> VValues;
UValues.AddUninitialized(NumClusterVerts);
VValues.AddUninitialized(NumClusterVerts);
for (uint32 i = 0; i < NumClusterVerts; i++)
{
const FVector2D& UV = Cluster.GetUVs(i)[ UVIndex ];
UValues[i] = UV.X;
VValues[i] = UV.Y;
}
UValues.Sort();
VValues.Sort();
// uv의
FVector2D LargestGapStart = FVector2D(UValues[0], VValues[0]);
FVector2D LargestGapEnd = FVector2D(UValues[0], VValues[0]);
for (uint32 i = 0; i < NumClusterVerts - 1; i++)
{
if (UValues[i + 1] - UValues[i] > LargestGapEnd.X - LargestGapStart.X)
{
LargestGapStart.X = UValues[i];
LargestGapEnd.X = UValues[i + 1];
}
if (VValues[i + 1] - VValues[i] > LargestGapEnd.Y - LargestGapStart.Y)
{
LargestGapStart.Y = VValues[i];
LargestGapEnd.Y = VValues[i + 1];
}
}
const FVector2D UVMin = FVector2D(UValues[0], VValues[0]);
const FVector2D UVMax = FVector2D(UValues[NumClusterVerts - 1], VValues[NumClusterVerts - 1]);
const FVector2D UVDelta = UVMax - UVMin;
const FVector2D UVRcpDelta = FVector2D( UVDelta.X > SMALL_NUMBER ? 1.0f / UVDelta.X : 0.0f,
UVDelta.Y > SMALL_NUMBER ? 1.0f / UVDelta.Y : 0.0f);
const FVector2D NonGapLength = FVector2D::Max(UVDelta - (LargestGapEnd - LargestGapStart), FVector2D(0.0f, 0.0f));
const FVector2D NormalizedGapStart = (LargestGapStart - UVMin) * UVRcpDelta;
const FVector2D NormalizedGapEnd = (LargestGapEnd - UVMin) * UVRcpDelta;
const FVector2D NormalizedNonGapLength = NonGapLength * UVRcpDelta;
#if 1
const float TexCoordUnitPrecision = (1 << 14); // TODO: Implement UI + 'Auto' mode that decides when this is necessary.
int32 TexCoordBitsU = 0;
if (UVDelta.X > 0)
{
// 즉, NonGapLength=0,UVDelta는 의 ,따라서 2개 (1bit) 및 。
int32 NumValues = FMath::Max(FMath::CeilToInt(NonGapLength.X * TexCoordUnitPrecision), 2);
// 12, 로부터 의 hack.
TexCoordBitsU = FMath::Min((int32)FMath::CeilLogTwo(NumValues), 12);
}
int32 TexCoordBitsV = 0;
if (UVDelta.Y > 0)
{
int32 NumValues = FMath::Max(FMath::CeilToInt(NonGapLength.Y * TexCoordUnitPrecision), 2);
TexCoordBitsV = FMath::Min((int32)FMath::CeilLogTwo(NumValues), 12);
}
#else
// hack로써 .
const int32 TexCoordBitsU = 12;
const int32 TexCoordBitsV = 12;
#endif
// 처리(Process)UV좌표 및 크기 .
Info.UVPrec |= ((TexCoordBitsV << 4) | TexCoordBitsU) << (UVIndex * 8);
const int32 TexCoordMaxValueU = (1 << TexCoordBitsU) - 1;
const int32 TexCoordMaxValueV = (1 << TexCoordBitsV) - 1;
const int32 NU = (int32)FMath::Clamp(NormalizedNonGapLength.X > SMALL_NUMBER ? (TexCoordMaxValueU - 2) / NormalizedNonGapLength.X : 0.0f, (float)TexCoordMaxValueU, (float)0xFFFF);
const int32 NV = (int32)FMath::Clamp(NormalizedNonGapLength.Y > SMALL_NUMBER ? (TexCoordMaxValueV - 2) / NormalizedNonGapLength.Y : 0.0f, (float)TexCoordMaxValueV, (float)0xFFFF);
int32 GapStartU = TexCoordMaxValueU + 1;
int32 GapStartV = TexCoordMaxValueV + 1;
int32 GapLengthU = 0;
int32 GapLengthV = 0;
if (NU > TexCoordMaxValueU)
{
GapStartU = int32(NormalizedGapStart.X * NU + 0.5f) + 1;
const int32 GapEndU = int32(NormalizedGapEnd.X * NU + 0.5f);
GapLengthU = FMath::Max(GapEndU - GapStartU, 0);
}
if (NV > TexCoordMaxValueV)
{
GapStartV = int32(NormalizedGapStart.Y * NV + 0.5f) + 1;
const int32 GapEndV = int32(NormalizedGapEnd.Y * NV + 0.5f);
GapLengthV = FMath::Max(GapEndV - GapStartV, 0);
}
UVInfo.UVRange.Min = UVMin;
UVInfo.UVRange.Scale = FVector2D(NU > 0 ? UVDelta.X / NU : 0.0f, NV > 0 ? UVDelta.Y / NV : 0.0f);
check(GapStartU >= 0);
check(GapStartV >= 0);
UVInfo.UVRange.GapStart[0] = GapStartU;
UVInfo.UVRange.GapStart[1] = GapStartV;
UVInfo.UVRange.GapLength[0] = GapLengthU;
UVInfo.UVRange.GapLength[1] = GapLengthV;
UVInfo.UVDelta = UVDelta;
UVInfo.UVRcpDelta = UVRcpDelta;
UVInfo.NU = NU;
UVInfo.NV = NV;
Info.BitsPerAttribute += TexCoordBitsU + TexCoordBitsV;
}
const uint32 PositionBitsPerVertex = Cluster.QuantizedPosBits.X + Cluster.QuantizedPosBits.Y + Cluster.QuantizedPosBits.Z;
GpuSizes.Position = (NumClusterVerts * PositionBitsPerVertex + 31) / 32 * 4;
GpuSizes.Attribute = (NumClusterVerts * Info.BitsPerAttribute + 31) / 32 * 4;
#endif
}
/*
Page
Page:
Fixup Chunk (로드 까지 CPU메모리 )
FPackedCluster
MaterialRangeTable
GeometryData
*/
static void AssignClustersToPages(
TArray< FClusterGroup >& ClusterGroups,
TArray< FCluster >& Clusters,
const TArray< FEncodingInfo >& EncodingInfos,
TArray<FPage>& Pages,
TArray<FClusterGroupPart>& Parts
)
{
check(Pages.Num() == 0);
check(Parts.Num() == 0);
const uint32 NumClusterGroups = ClusterGroups.Num();
Pages.AddDefaulted();
SortGroupClusters(ClusterGroups, Clusters);
TArray<uint32> ClusterGroupPermutation = CalculateClusterGroupPermutation(ClusterGroups);
for (uint32 i = 0; i < NumClusterGroups; i++)
{
// 의 개 Group.
uint32 GroupIndex = ClusterGroupPermutation[i];
FClusterGroup& Group = ClusterGroups[GroupIndex];
uint32 GroupStartPage = INVALID_PAGE_INDEX;
for (uint32 ClusterIndex : Group.Children)
{
// 의 개 Cluster.
FCluster& Cluster = Clusters[ClusterIndex];
const FEncodingInfo& EncodingInfo = EncodingInfos[ClusterIndex];
// 추가(Add)Page.
FPage* Page = &Pages.Top();
if (Page->GpuSizes.GetTotal() + EncodingInfo.GpuSizes.GetTotal() > CLUSTER_PAGE_GPU_SIZE || Page->NumClusters + 1 > MAX_CLUSTERS_PER_PAGE)
{
// Page, 개 .
Pages.AddDefaulted();
Page = &Pages.Top();
}
// 검사/감지(Detect)는 추가(Add)의 FClusterGroupPart.
if (Page->PartsNum == 0 || Parts[Page->PartsStartIndex + Page->PartsNum - 1].GroupIndex != GroupIndex)
{
if (Page->PartsNum == 0)
{
Page->PartsStartIndex = Parts.Num();
}
Page->PartsNum++;
FClusterGroupPart& Part = Parts.AddDefaulted_GetRef();
Part.GroupIndex = GroupIndex;
}
// 추가(Add)cluster까지 page.
uint32 PageIndex = Pages.Num() - 1;
uint32 PartIndex = Parts.Num() - 1;
FClusterGroupPart& Part = Parts.Last();
if (Part.Clusters.Num() == 0)
{
Part.PageClusterOffset = Page->NumClusters;
Part.PageIndex = PageIndex;
}
Part.Clusters.Add(ClusterIndex);
check(Part.Clusters.Num() <= MAX_CLUSTERS_PER_GROUP);
Cluster.GroupPartIndex = PartIndex;
if (GroupStartPage == INVALID_PAGE_INDEX)
{
GroupStartPage = PageIndex;
}
Page->GpuSizes += EncodingInfo.GpuSizes;
Page->NumClusters++;
}
Group.PageIndexStart = GroupStartPage;
Group.PageIndexNum = Pages.Num() - GroupStartPage;
check(Group.PageIndexNum >= 1);
check(Group.PageIndexNum <= MAX_GROUP_PARTS_MASK);
}
// 계산/산출(Calculate)group part의 바운딩 박스(AABB).
for (FClusterGroupPart& Part : Parts)
{
check(Part.Clusters.Num() <= MAX_CLUSTERS_PER_GROUP);
check(Part.PageIndex < (uint32)Pages.Num());
FBounds Bounds;
for (uint32 ClusterIndex : Part.Clusters)
{
Bounds += Clusters[ClusterIndex].Bounds;
}
Part.Bounds = Bounds;
}
}
// ClusterGroup레벨 단계 구조체 .
static void BuildHierarchies(FResources& Resources, const TArray<FClusterGroup>& Groups, TArray<FClusterGroupPart>& Parts, uint32 NumMeshes)
{
TArray<TArray<uint32>> PartsByMesh;
PartsByMesh.SetNum(NumMeshes);
// group part할당(Allocate)의 .
const uint32 NumTotalParts = Parts.Num();
for (uint32 PartIndex = 0; PartIndex < NumTotalParts; PartIndex++)
{
FClusterGroupPart& Part = Parts[PartIndex];
PartsByMesh[Groups[Part.GroupIndex].MeshIndex].Add(PartIndex);
}
for (uint32 MeshIndex = 0; MeshIndex < NumMeshes; MeshIndex++)
{
const TArray<uint32>& PartIndices = PartsByMesh[MeshIndex];
const uint32 NumParts = PartIndices.Num();
int32 MaxMipLevel = 0;
for (uint32 i = 0; i < NumParts; i++)
{
MaxMipLevel = FMath::Max(MaxMipLevel, Groups[Parts[PartIndices[i]].GroupIndex].MipLevel);
}
TArray< FIntermediateNode > Nodes;
Nodes.SetNum(NumParts);
// 로 개 의 LOD레벨 단계 포인트 .
TArray<TArray<uint32>> NodesByMip;
NodesByMip.SetNum(MaxMipLevel + 1);
for (uint32 i = 0; i < NumParts; i++)
{
const uint32 PartIndex = PartIndices[i];
const FClusterGroupPart& Part = Parts[PartIndex];
const FClusterGroup& Group = Groups[Part.GroupIndex];
const int32 MipLevel = Group.MipLevel;
FIntermediateNode& Node = Nodes[i];
Node.Bound = Part.Bounds;
Node.PartIndex = PartIndex;
Node.MipLevel = Group.MipLevel;
Node.bLeaf = true;
NodesByMip[Group.MipLevel].Add(i);
}
uint32 RootIndex = 0;
if (Nodes.Num() == 1)
{
// 오직 는 개 포인트 , 설정(Set), 이므로 포인트 는 개 포인트 。
FIntermediateNode& Node = Nodes.AddDefaulted_GetRef();
Node.Children.Add(0);
Node.Bound = Nodes[0].Bound;
RootIndex = 1;
}
else
{
// 레벨 회 구조체 (Hierarchy):
// NaniteLOD단계 의 Cluster데이터 . 레벨 단계 의 Cluster크기 로써 , 의 Hierarchy는 개 .
// 바운딩 박스(AABB),Hierarchy포인트 의 LOD。
// 오직 포인트 는 의 ,LOD의 ,순회(Traverse/Iterate)。
// 블렌딩(Blending)LOD의 Cluster,필수: ,이므로 의 Cluster바운딩 박스(AABB) 및 의 。
// LOD블렌딩(Blending)메서드 ,그러나 ,로 개 LOD단계 의 Hierarchy,Hierarchy의 Hierarchy,로써 까지 의 、의 결과 。
TArray<uint32> LevelRoots;
for (int32 MipLevel = 0; MipLevel <= MaxMipLevel; MipLevel++)
{
if (NodesByMip[MipLevel].Num() > 0)
{
// 로 mip레벨 단계 개 hierarchy, 을(를) 활용하여 .
uint32 NodeIndex = BuildHierarchyTopDown(Nodes, NodesByMip[MipLevel], true);
if (Nodes[NodeIndex].bLeaf || Nodes[NodeIndex].Children.Num() == MAX_BVH_NODE_FANOUT)
{
// 또는 패딩/채우기 포인트 , 추가(Add).
LevelRoots.Add(NodeIndex);
}
else
{
// 의 포인트 。,포인트 추가(Add)로 포인트 .
LevelRoots.Append(Nodes[NodeIndex].Children);
}
}
}
// 레벨 hierarchy, 는 MIP hierarchies의 hierarchy.
RootIndex = BuildHierarchyTopDown(Nodes, LevelRoots, false);
}
check(Nodes.Num() > 0);
#if BVH_BUILD_WRITE_GRAPHVIZ
WriteDotGraph(Nodes);
#endif
TArray< FHierarchyNode > HierarchyNodes;
BuildHierarchyRecursive(HierarchyNodes, Nodes, Groups, Parts, RootIndex);
// 변환(Transform/Convert)hierarchy압축/패킹(Packing).
const uint32 NumHierarchyNodes = HierarchyNodes.Num();
const uint32 PackedBaseIndex = Resources.HierarchyNodes.Num();
Resources.HierarchyRootOffsets.Add(PackedBaseIndex);
Resources.HierarchyNodes.AddDefaulted(NumHierarchyNodes);
for (uint32 i = 0; i < NumHierarchyNodes; i++)
{
// 압축/패킹(Packing)Hierarchy포인트 .
PackHierarchyNode(Resources.HierarchyNodes[PackedBaseIndex + i], HierarchyNodes[i], Groups, Parts);
}
}
}
// 기록/쓰기(Write)테이블 .
static void WritePages( FResources& Resources,
TArray<FPage>& Pages,
const TArray<FClusterGroup>& Groups,
const TArray<FClusterGroupPart>& Parts,
const TArray<FCluster>& Clusters,
const TArray<FEncodingInfo>& EncodingInfos,
uint32 NumTexCoords)
{
check(Resources.PageStreamingStates.Num() == 0);
const bool bLZCompress = true;
TArray< uint8 > StreamableBulkData;
const uint32 NumPages = Pages.Num();
const uint32 NumClusters = Clusters.Num();
Resources.PageStreamingStates.SetNum(NumPages);
// 처리(Process)FixupChunk.
uint32 TotalGPUSize = 0;
TArray<FFixupChunk> FixupChunks;
FixupChunks.SetNum(NumPages);
for (uint32 PageIndex = 0; PageIndex < NumPages; PageIndex++)
{
const FPage& Page = Pages[PageIndex];
FFixupChunk& FixupChunk = FixupChunks[PageIndex];
FixupChunk.Header.NumClusters = Page.NumClusters;
uint32 NumHierarchyFixups = 0;
for (uint32 i = 0; i < Page.PartsNum; i++)
{
const FClusterGroupPart& Part = Parts[Page.PartsStartIndex + i];
NumHierarchyFixups += Groups[Part.GroupIndex].PageIndexNum;
}
FixupChunk.Header.NumHierachyFixups = NumHierarchyFixups; // NumHierarchyFixups must be set before writing cluster fixups
TotalGPUSize += Page.GpuSizes.GetTotal();
}
// Page추가(Add)의 .
for (const FClusterGroupPart& Part : Parts)
{
check(Part.PageIndex < NumPages);
const FClusterGroup& Group = Groups[Part.GroupIndex];
for (uint32 ClusterPositionInPart = 0; ClusterPositionInPart < (uint32)Part.Clusters.Num(); ClusterPositionInPart++)
{
const FCluster& Cluster = Clusters[Part.Clusters[ClusterPositionInPart]];
if (Cluster.GeneratingGroupIndex != INVALID_GROUP_INDEX)
{
const FClusterGroup& GeneratingGroup = Groups[Cluster.GeneratingGroupIndex];
check(GeneratingGroup.PageIndexNum >= 1);
if (GeneratingGroup.PageIndexStart == Part.PageIndex && GeneratingGroup.PageIndexNum == 1)
continue; // Dependencies already met by current page. Fixup directly instead.
uint32 PageDependencyStart = GeneratingGroup.PageIndexStart;
uint32 PageDependencyNum = GeneratingGroup.PageIndexNum;
RemoveRootPagesFromRange(PageDependencyStart, PageDependencyNum); // Root page should never be a dependency
const FClusterFixup ClusterFixup = FClusterFixup(Part.PageIndex, Part.PageClusterOffset + ClusterPositionInPart, PageDependencyStart, PageDependencyNum);
for (uint32 i = 0; i < GeneratingGroup.PageIndexNum; i++)
{
FFixupChunk& FixupChunk = FixupChunks[GeneratingGroup.PageIndexStart + i];
FixupChunk.GetClusterFixup(FixupChunk.Header.NumClusterFixups++) = ClusterFixup;
}
}
}
}
// page.
for (uint32 PageIndex = 0; PageIndex < NumPages; PageIndex++)
{
const FFixupChunk& FixupChunk = FixupChunks[PageIndex];
FPageStreamingState& PageStreamingState = Resources.PageStreamingStates[PageIndex];
PageStreamingState.DependenciesStart = Resources.PageDependencies.Num();
for (uint32 i = 0; i < FixupChunk.Header.NumClusterFixups; i++)
{
uint32 FixupPageIndex = FixupChunk.GetClusterFixup(i).GetPageIndex();
check(FixupPageIndex < NumPages);
if (IsRootPage(FixupPageIndex) || FixupPageIndex == PageIndex) // Never emit dependencies to ourselves or a root page.
continue;
// 추가(Add).
// O(n^2), 그러나 개수 비교 .
bool bFound = false;
for (uint32 j = PageStreamingState.DependenciesStart; j < (uint32)Resources.PageDependencies.Num(); j++)
{
if (Resources.PageDependencies[j] == FixupPageIndex)
{
bFound = true;
break;
}
}
if (bFound)
continue;
Resources.PageDependencies.Add(FixupPageIndex);
}
PageStreamingState.DependenciesNum = Resources.PageDependencies.Num() - PageStreamingState.DependenciesStart;
}
// 처리(Process)page.
struct FPageResult
{
TArray<uint8> Data;
uint32 UncompressedSize;
};
TArray< FPageResult > PageResults;
PageResults.SetNum(NumPages);
// 병렬 처리(Process)
ParallelFor(NumPages, [&Resources, &Pages, &Groups, &Parts, &Clusters, &EncodingInfos, &FixupChunks, &PageResults, NumTexCoords, bLZCompress](int32 PageIndex)
{
const FPage& Page = Pages[PageIndex];
FFixupChunk& FixupChunk = FixupChunks[PageIndex];
// 추가(Add)hierarchy.
{
// Parts include the hierarchy fixups for all the other parts of the same group.
uint32 NumHierarchyFixups = 0;
for (uint32 i = 0; i < Page.PartsNum; i++)
{
const FClusterGroupPart& Part = Parts[Page.PartsStartIndex + i];
const FClusterGroup& Group = Groups[Part.GroupIndex];
const uint32 HierarchyRootOffset = Resources.HierarchyRootOffsets[Group.MeshIndex];
uint32 PageDependencyStart = Group.PageIndexStart;
uint32 PageDependencyNum = Group.PageIndexNum;
RemoveRootPagesFromRange(PageDependencyStart, PageDependencyNum);
// Add fixups to all parts of the group
for (uint32 j = 0; j < Group.PageIndexNum; j++)
{
const FPage& Page2 = Pages[Group.PageIndexStart + j];
for (uint32 k = 0; k < Page2.PartsNum; k++)
{
const FClusterGroupPart& Part2 = Parts[Page2.PartsStartIndex + k];
if (Part2.GroupIndex == Part.GroupIndex)
{
const uint32 GlobalHierarchyNodeIndex = HierarchyRootOffset + Part2.HierarchyNodeIndex;
FixupChunk.GetHierarchyFixup(NumHierarchyFixups++) = FHierarchyFixup(Part2.PageIndex, GlobalHierarchyNodeIndex, Part2.HierarchyChildIndex, Part2.PageClusterOffset, PageDependencyStart, PageDependencyNum);
break;
}
}
}
}
check(NumHierarchyFixups == FixupChunk.Header.NumHierachyFixups);
}
// Pack clusters and generate material range data
TArray<uint32> CombinedStripBitmaskData;
TArray<uint32> CombinedVertexRefBitmaskData;
TArray<uint32> CombinedVertexRefData;
TArray<uint8> CombinedIndexData;
TArray<uint8> CombinedPositionData;
TArray<uint8> CombinedAttributeData;
TArray<uint32> MaterialRangeData;
TArray<uint16> CodedVerticesPerCluster;
TArray<uint32> NumVertexBytesPerCluster;
TArray<FPackedCluster> PackedClusters;
PackedClusters.SetNumUninitialized(Page.NumClusters);
CodedVerticesPerCluster.SetNumUninitialized(Page.NumClusters);
NumVertexBytesPerCluster.SetNumUninitialized(Page.NumClusters);
const uint32 NumPackedClusterDwords = Page.NumClusters * sizeof(FPackedCluster) / sizeof(uint32);
FPageSections GpuSectionOffsets = Page.GpuSizes.GetOffsets();
TMap<FVariableVertex, uint32> UniqueVertices;
for (uint32 i = 0; i < Page.PartsNum; i++)
{
const FClusterGroupPart& Part = Parts[Page.PartsStartIndex + i];
for (uint32 j = 0; j < (uint32)Part.Clusters.Num(); j++)
{
const uint32 ClusterIndex = Part.Clusters[j];
const FCluster& Cluster = Clusters[ClusterIndex];
const FEncodingInfo& EncodingInfo = EncodingInfos[ClusterIndex];
const uint32 LocalClusterIndex = Part.PageClusterOffset + j;
FPackedCluster& PackedCluster = PackedClusters[LocalClusterIndex];
PackCluster(PackedCluster, Cluster, EncodingInfos[ClusterIndex], NumTexCoords);
PackedCluster.PackedMaterialInfo = PackMaterialInfo(Cluster, MaterialRangeData, NumPackedClusterDwords);
check((GpuSectionOffsets.Index & 3) == 0);
check((GpuSectionOffsets.Position & 3) == 0);
check((GpuSectionOffsets.Attribute & 3) == 0);
PackedCluster.SetIndexOffset(GpuSectionOffsets.Index);
PackedCluster.SetPositionOffset(GpuSectionOffsets.Position);
PackedCluster.SetAttributeOffset(GpuSectionOffsets.Attribute);
PackedCluster.SetDecodeInfoOffset(GpuSectionOffsets.DecodeInfo);
GpuSectionOffsets += EncodingInfo.GpuSizes;
const uint32 PrevVertexBytes = CombinedPositionData.Num();
uint32 NumCodedVertices = 0;
EncodeGeometryData( LocalClusterIndex, Cluster, EncodingInfo, NumTexCoords,
CombinedStripBitmaskData, CombinedIndexData,
CombinedVertexRefBitmaskData, CombinedVertexRefData, CombinedPositionData, CombinedAttributeData,
UniqueVertices, NumCodedVertices);
NumVertexBytesPerCluster[LocalClusterIndex] = CombinedPositionData.Num() - PrevVertexBytes;
CodedVerticesPerCluster[LocalClusterIndex] = NumCodedVertices;
}
}
check(GpuSectionOffsets.Cluster == Page.GpuSizes.GetMaterialTableOffset());
check(Align(GpuSectionOffsets.MaterialTable, 16) == Page.GpuSizes.GetDecodeInfoOffset());
check(GpuSectionOffsets.DecodeInfo == Page.GpuSizes.GetIndexOffset());
check(GpuSectionOffsets.Index == Page.GpuSizes.GetPositionOffset());
check(GpuSectionOffsets.Position == Page.GpuSizes.GetAttributeOffset());
check(GpuSectionOffsets.Attribute == Page.GpuSizes.GetTotal());
// Dword정렬 데이터 .
CombinedIndexData.SetNumZeroed((CombinedIndexData.Num() + 3) & -4);
// packkedclusters실행(Execute)보정/수정(Fix).
for (uint32 LocalPartIndex = 0; LocalPartIndex < Page.PartsNum; LocalPartIndex++)
{
const FClusterGroupPart& Part = Parts[Page.PartsStartIndex + LocalPartIndex];
const FClusterGroup& Group = Groups[Part.GroupIndex];
uint32 GeneratingGroupIndex = MAX_uint32;
for (uint32 ClusterPositionInPart = 0; ClusterPositionInPart < (uint32)Part.Clusters.Num(); ClusterPositionInPart++)
{
const FCluster& Cluster = Clusters[Part.Clusters[ClusterPositionInPart]];
if (Cluster.GeneratingGroupIndex != INVALID_GROUP_INDEX)
{
const FClusterGroup& GeneratingGroup = Groups[Cluster.GeneratingGroupIndex];
uint32 PageDependencyStart = Group.PageIndexStart;
uint32 PageDependencyNum = Group.PageIndexNum;
RemoveRootPagesFromRange(PageDependencyStart, PageDependencyNum);
if (GeneratingGroup.PageIndexStart == PageIndex && GeneratingGroup.PageIndexNum == 1)
{
// 현재(Current)Page의 , .
PackedClusters[Part.PageClusterOffset + ClusterPositionInPart].Flags &= ~NANITE_CLUSTER_FLAG_LEAF; // Mark parent as no longer leaf
}
}
}
}
// page
FPageResult& PageResult = PageResults[PageIndex];
PageResult.Data.SetNum(CLUSTER_PAGE_DISK_SIZE);
FBlockPointer PagePointer(PageResult.Data.GetData(), PageResult.Data.Num());
// 정보 .
FPageDiskHeader* PageDiskHeader = PagePointer.Advance<FPageDiskHeader>(1);
// 16정렬 머티리얼(Material)범위 데이터 ,GPU.
MaterialRangeData.SetNum(Align(MaterialRangeData.Num(), 4));
static_assert(sizeof(FUVRange) % 16 == 0, "sizeof(FUVRange) must be a multiple of 16");
static_assert(sizeof(FPackedCluster) % 16 == 0, "sizeof(FPackedCluster) must be a multiple of 16");
PageDiskHeader->NumClusters = Page.NumClusters;
PageDiskHeader->GpuSize = Page.GpuSizes.GetTotal();
PageDiskHeader->NumRawFloat4s = Page.NumClusters * (sizeof(FPackedCluster) + NumTexCoords * sizeof(FUVRange)) / 16 + MaterialRangeData.Num() / 4;
PageDiskHeader->NumTexCoords = NumTexCoords;
// Cluster정보 .
FClusterDiskHeader* ClusterDiskHeaders = PagePointer.Advance<FClusterDiskHeader>(Page.NumClusters);
// 을(를) 활용하여 SOA(Structure-of-Arrays)메모리 기록/쓰기(Write)cluster.
{
const uint32 NumClusterFloat4Propeties = sizeof(FPackedCluster) / 16;
for (uint32 float4Index = 0; float4Index < NumClusterFloat4Propeties; float4Index++)
{
for (const FPackedCluster& PackedCluster : PackedClusters)
{
uint8* Dst = PagePointer.Advance<uint8>(16);
FMemory::Memcpy(Dst, (uint8*)&PackedCluster + float4Index * 16, 16);
}
}
}
// 머티리얼(Material)테이블 .
uint32 MaterialTableSize = MaterialRangeData.Num() * MaterialRangeData.GetTypeSize();
uint8* MaterialTable = PagePointer.Advance<uint8>(MaterialTableSize);
FMemory::Memcpy(MaterialTable, MaterialRangeData.GetData(), MaterialTableSize);
check(MaterialTableSize == Page.GpuSizes.GetMaterialTableSize());
// 정보 .
PageDiskHeader->DecodeInfoOffset = PagePointer.Offset();
for (uint32 i = 0; i < Page.PartsNum; i++)
{
const FClusterGroupPart& Part = Parts[Page.PartsStartIndex + i];
for (uint32 j = 0; j < (uint32)Part.Clusters.Num(); j++)
{
const uint32 ClusterIndex = Part.Clusters[j];
FUVRange* DecodeInfo = PagePointer.Advance<FUVRange>(NumTexCoords);
for (uint32 k = 0; k < NumTexCoords; k++)
{
DecodeInfo[k] = EncodingInfos[ClusterIndex].UVInfos[k].UVRange;
}
}
}
// 데이터 .
{
uint8* IndexData = PagePointer.GetPtr<uint8>();
#if USE_STRIP_INDICES
for (uint32 i = 0; i < Page.PartsNum; i++)
{
const FClusterGroupPart& Part = Parts[Page.PartsStartIndex + i];
for (uint32 j = 0; j < (uint32)Part.Clusters.Num(); j++)
{
const uint32 LocalClusterIndex = Part.PageClusterOffset + j;
const uint32 ClusterIndex = Part.Clusters[j];
const FCluster& Cluster = Clusters[ClusterIndex];
ClusterDiskHeaders[LocalClusterIndex].IndexDataOffset = PagePointer.Offset();
ClusterDiskHeaders[LocalClusterIndex].NumPrevNewVerticesBeforeDwords = Cluster.StripDesc.NumPrevNewVerticesBeforeDwords;
ClusterDiskHeaders[LocalClusterIndex].NumPrevRefVerticesBeforeDwords = Cluster.StripDesc.NumPrevRefVerticesBeforeDwords;
PagePointer.Advance<uint8>(Cluster.StripIndexData.Num());
}
}
uint32 IndexDataSize = CombinedIndexData.Num() * CombinedIndexData.GetTypeSize();
FMemory::Memcpy(IndexData, CombinedIndexData.GetData(), IndexDataSize);
PagePointer.Align(sizeof(uint32));
PageDiskHeader->StripBitmaskOffset = PagePointer.Offset();
uint32 StripBitmaskDataSize = CombinedStripBitmaskData.Num() * CombinedStripBitmaskData.GetTypeSize();
uint8* StripBitmaskData = PagePointer.Advance<uint8>(StripBitmaskDataSize);
FMemory::Memcpy(StripBitmaskData, CombinedStripBitmaskData.GetData(), StripBitmaskDataSize);
#else
for (uint32 i = 0; i < Page.NumClusters; i++)
{
ClusterDiskHeaders[i].IndexDataOffset = PagePointer.Offset();
PagePointer.Advance<uint8>(PackedClusters[i].GetNumTris() * 3);
}
PagePointer.Align(sizeof(uint32));
uint32 IndexDataSize = CombinedIndexData.Num() * CombinedIndexData.GetTypeSize();
FMemory::Memcpy(IndexData, CombinedIndexData.GetData(), IndexDataSize);
#endif
}
// 기록/쓰기(Write)포인트 을(를) 활용하여 의 .
{
PageDiskHeader->VertexRefBitmaskOffset = PagePointer.Offset();
const uint32 VertexRefBitmaskSize = Page.NumClusters * (MAX_CLUSTER_VERTICES / 8);
uint8* VertexRefBitmask = PagePointer.Advance<uint8>(VertexRefBitmaskSize);
FMemory::Memcpy(VertexRefBitmask, CombinedVertexRefBitmaskData.GetData(), VertexRefBitmaskSize);
check(CombinedVertexRefBitmaskData.Num() * CombinedVertexRefBitmaskData.GetTypeSize() == VertexRefBitmaskSize);
}
// 기록/쓰기(Write)포인트 을(를) 활용하여 .
{
uint8* VertexRefs = PagePointer.GetPtr<uint8>();
for (uint32 i = 0; i < Page.NumClusters; i++)
{
ClusterDiskHeaders[i].VertexRefDataOffset = PagePointer.Offset();
uint32 NumVertexRefs = PackedClusters[i].GetNumVerts() - CodedVerticesPerCluster[i];
PagePointer.Advance<uint32>(NumVertexRefs);
}
FMemory::Memcpy(VertexRefs, CombinedVertexRefData.GetData(), CombinedVertexRefData.Num() * CombinedVertexRefData.GetTypeSize());
}
// 기록/쓰기(Write)위치 .
{
uint8* PositionData = PagePointer.GetPtr<uint8>();
for (uint32 i = 0; i < Page.NumClusters; i++)
{
ClusterDiskHeaders[i].PositionDataOffset = PagePointer.Offset();
PagePointer.Advance<uint8>(NumVertexBytesPerCluster[i]);
}
check( (PagePointer.GetPtr<uint8>() - PositionData) == CombinedPositionData.Num() * CombinedPositionData.GetTypeSize());
FMemory::Memcpy(PositionData, CombinedPositionData.GetData(), CombinedPositionData.Num() * CombinedPositionData.GetTypeSize());
}
// 기록/쓰기(Write).
{
uint8* AttribData = PagePointer.GetPtr<uint8>();
for (uint32 i = 0; i < Page.NumClusters; i++)
{
const uint32 BytesPerAttribute = (PackedClusters[i].GetBitsPerAttribute() + 7) / 8;
ClusterDiskHeaders[i].AttributeDataOffset = PagePointer.Offset();
PagePointer.Advance<uint8>(Align(CodedVerticesPerCluster[i] * BytesPerAttribute, 4));
}
check((uint32)(PagePointer.GetPtr<uint8>() - AttribData) == CombinedAttributeData.Num() * CombinedAttributeData.GetTypeSize());
FMemory::Memcpy(AttribData, CombinedAttributeData.GetData(), CombinedAttributeData.Num()* CombinedAttributeData.GetTypeSize());
}
// 을(를) 활용하여 Lempel-Ziv(LZ)압축/패킹(Packing)메모리 , LZ의 개 는 Lempel-Ziv-Welch(LZW).
// : http://athena.ecs.csus.edu/~wang/DLZW.pdf.
if (bLZCompress)
{
TArray<uint8> DataCopy(PageResult.Data.GetData(), PagePointer.Offset());
PageResult.UncompressedSize = DataCopy.Num();
int32 CompressedSize = PageResult.Data.Num();
verify(FCompression::CompressMemory(NAME_LZ4, PageResult.Data.GetData(), CompressedSize, DataCopy.GetData(), DataCopy.Num()));
PageResult.Data.SetNum(CompressedSize, false);
}
else // 을(를) 활용하여 압축/패킹(Packing).
{
PageResult.Data.SetNum(PagePointer.Offset(), false);
PageResult.UncompressedSize = PageResult.Data.Num();
}
});
// 기록/쓰기(Write)Page.
uint32 TotalUncompressedSize = 0;
uint32 TotalCompressedSize = 0;
uint32 TotalFixupSize = 0;
for (uint32 PageIndex = 0; PageIndex < NumPages; PageIndex++)
{
const FPage& Page = Pages[PageIndex];
FFixupChunk& FixupChunk = FixupChunks[PageIndex];
TArray<uint8>& BulkData = IsRootPage(PageIndex) ? Resources.RootClusterPage : StreamableBulkData;
FPageStreamingState& PageStreamingState = Resources.PageStreamingStates[PageIndex];
PageStreamingState.BulkOffset = BulkData.Num();
// 기록/쓰기(Write).
uint32 FixupChunkSize = FixupChunk.GetSize();
check(FixupChunk.Header.NumHierachyFixups < MAX_CLUSTERS_PER_PAGE);
check(FixupChunk.Header.NumClusterFixups < MAX_CLUSTERS_PER_PAGE);
BulkData.Append((uint8*)&FixupChunk, FixupChunkSize);
TotalFixupSize += FixupChunkSize;
// 까지 BulkData.
TArray<uint8>& PageData = PageResults[PageIndex].Data;
BulkData.Append(PageData.GetData(), PageData.Num());
TotalUncompressedSize += PageResults[PageIndex].UncompressedSize;
TotalCompressedSize += PageData.Num();
PageStreamingState.BulkSize = BulkData.Num() - PageStreamingState.BulkOffset;
PageStreamingState.PageUncompressedSize = PageResults[PageIndex].UncompressedSize;
}
uint32 TotalDiskSize = Resources.RootClusterPage.Num() + StreamableBulkData.Num();
UE_LOG(LogStaticMesh, Log, TEXT("WritePages:"), NumPages);
UE_LOG(LogStaticMesh, Log, TEXT(" %d pages written."), NumPages);
UE_LOG(LogStaticMesh, Log, TEXT(" GPU size: %d bytes. %.3f bytes per page. %.3f%% utilization."), TotalGPUSize, TotalGPUSize / float(NumPages), TotalGPUSize / (float(NumPages) * CLUSTER_PAGE_GPU_SIZE) * 100.0f);
UE_LOG(LogStaticMesh, Log, TEXT(" Uncompressed page data: %d bytes. Compressed page data: %d bytes. Fixup data: %d bytes."), TotalUncompressedSize, TotalCompressedSize, TotalFixupSize);
UE_LOG(LogStaticMesh, Log, TEXT(" Total disk size: %d bytes. %.3f bytes per page."), TotalDiskSize, TotalDiskSize/ float(NumPages));
// 저장(Store)PageData.
Resources.StreamableClusterPages.Lock(LOCK_READ_WRITE);
uint8* Ptr = (uint8*)Resources.StreamableClusterPages.Realloc(StreamableBulkData.Num());
FMemory::Memcpy(Ptr, StreamableBulkData.GetData(), StreamableBulkData.Num());
Resources.StreamableClusterPages.Unlock();
Resources.StreamableClusterPages.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload);
Resources.bLZCompressed = bLZCompress;
}6.4.2.8 FImposterAtlas::Rasterize
// Engine\Source\Developer\NaniteBuilder\Private\ImposterAtlas.cpp
// Cluster의 래스터라이제이션 까지 Imposter.
void FImposterAtlas::Rasterize( const FIntPoint& TilePos, const FCluster& Cluster, uint32 ClusterIndex )
{
constexpr uint32 ViewSize = TileSize;// * SuperSample;
FIntRect Scissor( 0, 0, ViewSize, ViewSize );
// 페치/가져오기(Fetch)로컬 까지 Imposter의 변환(Transform)모멘트(Moment).
FMatrix LocalToImposter = GetLocalToImposter( TilePos );
TArray< FVector, TInlineAllocator<128> > Positions;
Positions.SetNum( Cluster.NumVerts, false );
// Cluster포인트 위치 , 변환(Transform/Convert)까지 Imposter.
for( uint32 VertIndex = 0; VertIndex < Cluster.NumVerts; VertIndex++ )
{
FVector Position = Cluster.GetPosition( VertIndex );
Position = LocalToImposter.TransformPosition( Position );
Positions[ VertIndex ].X = ( Position.X * 0.5f + 0.5f ) * ViewSize;
Positions[ VertIndex ].Y = ( Position.Y * 0.5f + 0.5f ) * ViewSize;
Positions[ VertIndex ].Z = ( Position.Z * 0.5f + 0.5f ) * 254.0f + 1.0f; // zero is reserved as masked
}
// 순회(Traverse/Iterate), 래스터라이제이션 까지 Imposter.
for( uint32 TriIndex = 0; TriIndex < Cluster.NumTris; TriIndex++ )
{
FVector Verts[3];
Verts[0] = Positions[ Cluster.Indexes[ TriIndex * 3 + 0 ] ];
Verts[1] = Positions[ Cluster.Indexes[ TriIndex * 3 + 1 ] ];
Verts[2] = Positions[ Cluster.Indexes[ TriIndex * 3 + 2 ] ];
// 래스터라이제이션 .
RasterizeTri( Verts, Scissor, 0,
// 저장(Save)래스터라이제이션 의 결과 .
[&]( int32 x, int32 y, float z )
{
uint32 Depth = FMath::RoundToInt( FMath::Clamp( z, 1.0f, 255.0f ) );
uint16 PixelValue = ( Depth << 8 ) | ( ClusterIndex << 7 ) | TriIndex;
//uint32 PixelIndex = x + y * ViewSize;
uint32 PixelIndex = x + ( y + ( TilePos.X + TilePos.Y * AtlasSize ) * TileSize ) * TileSize;
Pixels[ PixelIndex ] = FMath::Max( Pixels[ PixelIndex ], PixelValue );
} );
}
}
// Engine\Source\Developer\NaniteBuilder\Private\Rasterizer.h
// 의 , 기록/쓰기(Write)데이터 호출(Call)FWritePixel함수 .
template< typename FWritePixel >
void RasterizeTri( const FVector Verts[3], const FIntRect& ScissorRect, uint32 SubpixelDilate, FWritePixel WritePixel )
{
constexpr uint32 SubpixelBits = 8;
constexpr uint32 SubpixelSamples = 1 << SubpixelBits;
FVector v01 = Verts[1] - Verts[0];
FVector v02 = Verts[2] - Verts[0];
float DetXY = v01.X * v02.Y - v01.Y * v02.X;
if( DetXY >= 0.0f )
{
// 백페이스 컬링 .
// 만약 컬링 ,포인트 ,로 코드 winding.
return;
}
FVector2D GradZ;
GradZ.X = ( v01.Z * v02.Y - v01.Y * v02.Z ) / DetXY;
GradZ.Y = ( v01.X * v02.Z - v01.Z * v02.X ) / DetXY;
// 24.8 fixed point
FIntPoint Vert0 = ToIntPoint( Verts[0] * SubpixelSamples );
FIntPoint Vert1 = ToIntPoint( Verts[1] * SubpixelSamples );
FIntPoint Vert2 = ToIntPoint( Verts[2] * SubpixelSamples );
// 모멘트(Moment)바운딩 박스(AABB).
FIntRect RectSubpixel( Vert0, Vert0 );
RectSubpixel.Include( Vert1 );
RectSubpixel.Include( Vert2 );
RectSubpixel.InflateRect( SubpixelDilate );
// 까지 픽셀(Pixel).
FIntRect RectPixel = ( ( RectSubpixel + (SubpixelSamples / 2) - 1 ) ) / SubpixelSamples;
// 클리핑/클램핑(Clipping)까지 뷰포트(Viewport).
RectPixel.Clip( ScissorRect );
// 픽셀(Pixel), 클리핑/클램핑(Clipping).
if( RectPixel.IsEmpty() )
return;
// 12.8 fixed point
FIntPoint Edge01 = Vert0 - Vert1;
FIntPoint Edge12 = Vert1 - Vert2;
FIntPoint Edge20 = Vert2 - Vert0;
// 을(를) 활용하여 픽셀(Pixel)조정(Adjust)MinPixel.
// 12.8 fixed point
// 의 해상도/크기 = 2047x2047 픽셀(Pixel).
const FIntPoint BaseSubpixel = RectPixel.Min * SubpixelSamples + (SubpixelSamples / 2);
Vert0 -= BaseSubpixel;
Vert1 -= BaseSubpixel;
Vert2 -= BaseSubpixel;
auto EdgeC = [=]( const FIntPoint& Edge, const FIntPoint& Vert )
{
int64 ex = Edge.X;
int64 ey = Edge.Y;
int64 vx = Vert.X;
int64 vy = Vert.Y;
// Half-edge constants
// 24.16 fixed point
int64 C = ey * vx - ex * vy;
// 보정(Correct)패딩/채우기 (fill convention)
// Top left rule for CCW
C -= ( Edge.Y < 0 || ( Edge.Y == 0 && Edge.X > 0 ) ) ? 0 : 1;
// .
C += ( FMath::Abs( Edge.X ) + FMath::Abs( Edge.Y ) ) * SubpixelDilate;
// 픽셀(Pixel).
// 는 의 ,。
// 24.8 fixed point
return int32( C >> SubpixelBits );
};
int32 C0 = EdgeC( Edge01, Vert0 );
int32 C1 = EdgeC( Edge12, Vert1 );
int32 C2 = EdgeC( Edge20, Vert2 );
float Z0 = Verts[0].Z - ( GradZ.X * Vert0.X + GradZ.Y * Vert0.Y ) / SubpixelSamples;
int32 CY0 = C0;
int32 CY1 = C1;
int32 CY2 = C2;
float ZY = Z0;
// 순회(Traverse/Iterate)모멘트(Moment)의 픽셀(Pixel), 패딩/채우기 의 픽셀(Pixel).
for( int32 y = RectPixel.Min.Y; y < RectPixel.Max.Y; y++ )
{
int32 CX0 = CY0;
int32 CX1 = CY1;
int32 CX2 = CY2;
float ZX = ZY;
for( int32 x = RectPixel.Min.X; x < RectPixel.Max.X; x++ )
{
// 만약 현재(Current)3개 의 X는 , , 호출(Call)WritePixel기록/쓰기(Write)데이터 .
if( ( CX0 | CX1 | CX2 ) >= 0 )
{
WritePixel( x, y, ZX );
}
CX0 -= Edge01.Y;
CX1 -= Edge12.Y;
CX2 -= Edge20.Y;
ZX += GradZ.X;
}
CY0 += Edge01.X;
CY1 += Edge12.X;
CY2 += Edge20.X;
ZY += GradZ.Y;
}
}6.4.2.9 Nanite 데이터 구성 요약
이 섹션에서는 Nanite 데이터 구축 과정을 요약합니다. 초기 항목은 BuildNaniteFromHiResSourceModel입니다.
UStaticMesh의 HiResSourceModel에서 Nanite의 고정밀 모델을 얻습니다.
탄젠트, 라이트맵 UV 등을 계산합니다.
후속 Nanite 빌드 단계에 전달될 임시 RenderData 데이터를 구성합니다.
섹션별 인덱스, 버텍스 및 인덱스 버퍼를 구축합니다.
BuildCombinedSectionIndices: 섹션별 인덱스 버퍼를 연결합니다.
ComputeBoundsFromVertexList: Nanite 빌드 전에 고해상도 메시에서 경계 상자를 계산합니다.
SectionInfoMap에서 섹션 재질 인덱스를 구문 분석합니다.
NaniteBuilderModule.Build: Nanite 빌드 모듈을 실행합니다.
다음은 NaniteBuilderModule.Build의 주요 프로세스에 대한 개요입니다.
삼각형 인덱스와 재료 인덱스의 연관 배열을 구성합니다.
BuildNaniteData: Nanite 데이터를 구축합니다.
버텍스 색상을 처리합니다.
- 모든 섹션을 탐색하고 각 섹션에 대한 클러스터를 구축합니다.
ClusterTriangles: 섹션을 하나 이상의 클러스터로 분할합니다.
공유 엣지, 경계 엣지, 엣지 해시 등의 데이터를 초기화합니다.
엣지 해시를 병렬로 처리합니다.
공유 모서리와 경계 모서리를 평행하게 찾습니다.
일관되지 않은 삼각형 세트를 처리합니다.
FGraphPartitioner를 사용하여 메시를 나눕니다.
FGraphPartitioner는 내부적으로 METIS 타사 오픈 소스 라이브러리를 사용합니다. METIS는 고품질의 메쉬 분할을 효율적으로 제공할 수 있으며 낮은 충전율의 특성을 가지고 있어 메쉬 분할의 효과와 효율성을 보장할 수 있습니다. METIS 파티셔닝 알고리즘은 정리(Coarsening), 파티셔닝(Partitioning), 비대화(Uncoarsening)의 세 단계로 구성됩니다.
클러스터를 병렬로 구축합니다.
원본 정적 메쉬 데이터를 대략적인 표현으로 대체해야 하는지 여부를 감지합니다.
대략적인 표현을 사용하려면 다음 조건을 충족해야 합니다. Nanite에서 설정한 PercentTriangles가 1보다 작고 원본 메시의 삼각형 수가 2000보다 큽니다.
모든 섹션에 대해 BuildDAG를 호출하여 방향성 비순환 그래프를 작성하여 표면 축소 및 모델 축소를 가속화합니다.
거친 표현을 사용하는 경우 BuildCoarseRepresentation을 호출하여 거친 표현의 데이터를 구축한 다음 거친 메쉬 범위를 사용하여 원래 시퀀스 번호를 존중하고 재료를 유지하면서 메쉬 섹션 정보를 수정합니다.
인코드: Nanite 메시를 인코딩합니다.
RemoveDegenerateTriangles: 모든 클러스터 퇴화된 삼각형을 제거합니다.
BuildMaterialRanges: 모든 클러스터의 재료 범위를 구축합니다.
ConstrainClusters: 클러스터를 ClusterGroup으로 제한합니다.
CalculateQuantizedPositionsUniformGrid: 양자화된 위치를 계산합니다.
CalculateEncodingInfos: 인코딩 정보를 계산합니다.
AssignClustersToPages: 페이지에 클러스터를 할당합니다.
BuildHierarchies: ClusterGroup의 계층적 노드를 빌드합니다.
WritePages: 클러스터 및 ClusterGroup 정보를 페이지에 씁니다.
필요한 경우(섹션이 하나만 있는 경우) FImposterAtlas를 생성합니다.
모든 클러스터에 대한 임포스터를 병렬로 생성하여 각 클러스터의 모든 삼각형을 FImposterAtlas로 래스터라이제이션합니다.
Nanite의 빌드 프로세스에는 주로 다음을 포함하지만 이에 국한되지 않는 다양한 최적화 기술이 사용됩니다.
다중 스레드를 활용하여 논리를 병렬로 처리하고 빌드 시간을 줄이기 위해 ParallelFor를 광범위하게 사용합니다.
MITES를 이용하여 우수한 Meshing을 얻을 수 있습니다.
Cluster, ClusterGroup, ClusterGroupPart 등 다양한 수준의 개념을 활용하여 그리드 관련 데이터를 유기적으로 결합합니다.
DAG, Hierarchy, Mip Level, Coarse Representation 등을 사용하여 메쉬 구성 및 분할을 가속화하고 최적화합니다.
GPU 렌더링에 필요한 Page, ImposterAtlas 및 기타 정보를 미리 생성합니다.
LZ 압축, 고정 소수점, 비트 연산 등과 같은 높은 수준의 데이터 압축을 대량으로 사용합니다.
또한 나나이트는 이전에 소문이 돌았던 지오메트리 이미지(Geometry Image) 기술을 사용하지 않지만 핵심 아이디어나 기술은 여전히 비교적 유사하다.
UE5의 Coder 중에는 국제 수학의 대가 Qiu Chengtong의 제자이자 기하학적 이미지의 선구자인 Gu Xianfeng 교수와 함께 논문을 발표한 저자가 있다고 합니다.
기하학 이미지 기술에 관해서는 Gu 교수의 기하학 이미지 논문과 그의 공개 계정인 Lao Gu가 기하학에 관해 이야기하는 내용을 참조할 수 있습니다.
6.4.3 나나이트 렌더링
이 섹션에서는 Nanite의 렌더링 단계의 코드와 논리를 설명합니다.
6.4.3.1 Nanite 렌더링 개요
UE5는 Nanite 기능의 렌더링을 지원하기 위해 렌더링 모듈에 주요 변경 사항을 적용했으며, 그 내용은 다음과 같이 요약됩니다:
- 엔진 모듈:
이전 장에서 분석한 FMeshNaniteSettings 유형을 추가했습니다.
FStaticMeshRenderData는 Nanite::FResources 데이터 및 관련 처리 로직을 추가합니다.
UStaticMeshComponent는 bDisplayNaniteProxyMesh를 추가하고 FMeshNaniteSettings 데이터를 추가합니다.
UHierarchicalInstancedStaticMeshComponent, UInstancedStaticMeshComponent 및 기타 구성요소는 Nanite 데이터 및 SceneProxy에 대한 지원을 추가합니다.
InstanceUniformShaderParameters 모듈이 추가되었습니다.
렌더링 모듈:
FNaniteCommandInfo, ENaniteMeshPass, FNaniteDrawListContext, FCullingContext, FRasterContext, FRasterResults, FNaniteShader, FNaniteMaterialVS, FNaniteMeshProcessor, FNaniteMaterialTables, ERasterTechnique, ERasterScheduling, EOutputBufferMode, FPackedView 등 유형을 포함한 NaniteRender 모듈이 추가되었습니다. 및 처리 인터페이스.
FPrimitiveSceneInfo는 NaniteCommandInfos, NaniteMaterialIds, LumenPrimitiveIndex 및 CachedRayTracingMeshCommandsHashPerLOD, bRegisteredWithVelocityData, InstanceDataOffset, NumInstanceDataEntries 인스턴스화 및 라이트 트레이싱 관련 데이터 및 처리 인터페이스를 추가합니다.
Nanite::FSceneProxy, Nanite::FResources, Nanite::FVertexFactory 및 기타 유형을 포함한 NaniteResources 모듈이 추가되었습니다.
Nanite::FPageKey, Nanite::FGPUStreamingRequest, Nanite::FStreamingRequest, Nanite::FStreamingPageInfo, Nanite::FRootPageInfo, Nanite::FPendingPage, Nanite::FAsyncState, Nanite::FStreamingManager, Nanite::, Nanite:: 및 기타 유형을 포함하는 NaniteStreamingManager 모듈이 추가되었습니다.
FPrimitiveSceneProxyz는 SupportsNaniteRendering, IsNaniteMesh, bSupportsMeshCardRepresentation, IsAlwaysVisible, GetPrimitiveInstances, RayTracingGroupId 등을 추가합니다.
SceneInterface 및 SceneManagement는 FInstanceCullingManagerResources 및 기타 유형을 추가하고 FPrimitiveUniformShaderParameters 대신 FGPUScenePrimitiveCollector를 사용합니다.
SceneView는 FViewShaderParameters, PrecomputedIndirectLightingColorScale, GlobalDistanceField, VirtualTexture, PhysicsField, Lumen, Instance, Page 및 기타 셰이더 바인딩을 추가합니다.
FPrimitiveFlagsCompact 및 기타 유형은 bIsNaniteMesh 태그를 추가합니다.
셰이더 모듈:
ClusterCulling, Culling, HZBCull, InstanceCulling, MaterialCulling, Shadow, GBuffer, Impsoter, DataDecode, DataPacked, Rasterizer, WritePixel 등이 추가되었습니다.
6.4.3.2 Nanite 렌더링 기본
이 섹션에서는 Nanite 렌더링과 관련된 주요 개념, 유형 및 인터페이스를 분석합니다.
- InstanceUniformShaderParameters
// Engine\Source\Runtime\Engine\Public\InstanceUniformShaderParameters.h
#define INSTANCE_SCENE_DATA_FLAG_CAST_SHADOWS 0x1
#define INSTANCE_SCENE_DATA_FLAG_DETERMINANT_SIGN 0x2
#define INSTANCE_SCENE_DATA_FLAG_HAS_IMPOSTER 0x4
// Nanite인스턴스 정보 .
class FNaniteInfo
{
public:
uint32 RuntimeResourceID; // 의 리소스 .
uint32 HierarchyOffset_AndHasImposter; // 레벨 회 구조체 및 는 Imposter의 데이터 .
FNaniteInfo()
: RuntimeResourceID(0xFFFFFFFFu)
, HierarchyOffset_AndHasImposter(0xFFFFFFFFu)
{
}
FNaniteInfo(uint32 InRuntimeResourceID, int32 InHierarchyOffset, bool bHasImposter)
: RuntimeResourceID(InRuntimeResourceID)
, HierarchyOffset_AndHasImposter((InHierarchyOffset << 1) | (bHasImposter ? 1u : 0u))
{
}
};
// Nanite인스턴스 정보 .
struct FPrimitiveInstance
{
FMatrix InstanceToLocal;
FMatrix PrevInstanceToLocal;
FMatrix LocalToWorld;
FMatrix PrevLocalToWorld;
FVector4 NonUniformScale;
FVector4 InvNonUniformScaleAndDeterminantSign;
FBoxSphereBounds RenderBounds;
FBoxSphereBounds LocalBounds;
FVector4 LightMapAndShadowMapUVBias;
uint32 PrimitiveId; // ID.
FNaniteInfo NaniteInfo; // Nanite정보 .
uint32 LastUpdateSceneFrameNumber;
float PerInstanceRandom;
uint32 Flags;
};
(......)
// FInstanceUniformShaderParameters의 선언 . 및 shader의 FInstanceSceneData매칭 .
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FInstanceUniformShaderParameters,ENGINE_API)
SHADER_PARAMETER(FMatrix, LocalToWorld)
SHADER_PARAMETER(FMatrix, PrevLocalToWorld)
SHADER_PARAMETER(FVector4, NonUniformScale)
SHADER_PARAMETER(FVector4, InvNonUniformScaleAndDeterminantSign)
SHADER_PARAMETER(FVector, LocalBoundsCenter)
SHADER_PARAMETER(uint32, PrimitiveId)
SHADER_PARAMETER(FVector, LocalBoundsExtent)
SHADER_PARAMETER(uint32, LastUpdateSceneFrameNumber)
SHADER_PARAMETER(uint32, NaniteRuntimeResourceID)
SHADER_PARAMETER(uint32, NaniteHierarchyOffset)
SHADER_PARAMETER(float, PerInstanceRandom)
SHADER_PARAMETER(uint32, Flags)
SHADER_PARAMETER(FVector4, LightMapAndShadowMapUVBias)
END_GLOBAL_SHADER_PARAMETER_STRUCT()
// 인스턴스 셰이딩 데이터 .
struct FInstanceSceneShaderData
{
// 및 SceneData.ush의 GetInstanceData()매칭 .
enum { InstanceDataStrideInFloat4s = 10 };
FVector4 Data[InstanceDataStrideInFloat4s];
(......)
};- NaniteStreamingManager
// Engine\Source\Runtime\Engine\Public\Rendering\NaniteStreamingManager.h
namespace Nanite
{
//
struct FPageKey
{
// 리소스 ID.
uint32 RuntimeResourceID;
// .
uint32 PageIndex;
};
//
FORCEINLINE uint32 GetTypeHash( const FPageKey& Key )
{
return Key.RuntimeResourceID * 0xFC6014F9u + Key.PageIndex * 0x58399E77u;
}
// 비교 .
FORCEINLINE bool operator==( const FPageKey& A, const FPageKey& B )
{
return A.RuntimeResourceID == B.RuntimeResourceID && A.PageIndex == B.PageIndex;
}
FORCEINLINE bool operator!=(const FPageKey& A, const FPageKey& B)
{
return !(A == B);
}
// (deduplication)【】의 데이터 정보 .
struct FGPUStreamingRequest
{
uint32 RuntimeResourceID;
uint32 PageIndex_NumPages;
uint32 Priority;
};
// (deduplication)【】의 데이터 정보 .
struct FStreamingRequest
{
FPageKey Key;
uint32 Priority;
};
// 정보 .
struct FStreamingPageInfo
{
FStreamingPageInfo* Next;
FStreamingPageInfo* Prev;
FPageKey RegisteredKey;
FPageKey ResidentKey;
uint32 GPUPageIndex;
uint32 LatestUpdateIndex;
uint32 RefCount;
};
// 정보 .
struct FRootPageInfo
{
uint32 RuntimeResourceID;
uint32 NumClusters;
};
// 일시 중지 .
struct FPendingPage
{
#if !WITH_EDITOR
uint8* MemoryPtr;
FIoRequest Request;
IAsyncReadFileHandle* AsyncHandle;
IAsyncReadRequest* AsyncRequest;
#endif
uint32 GPUPageIndex;
FPageKey InstallKey;
#if !UE_BUILD_SHIPPING
uint32 BytesLeftToStream;
#endif
};
// 비동기 정보 .
struct FAsyncState
{
FRHIGPUBufferReadback* LatestReadbackBuffer = nullptr;
const uint32* LatestReadbackBufferPtr = nullptr;
uint32 NumReadyPages = 0;
bool bUpdateActive = false;
bool bBuffersTransitionedToWrite = false;
};
// Nanite.
class FStreamingManager : public FRenderResource
{
public:
FStreamingManager();
// 초기화(Initialize)/해제(Release)RHI리소스 .
virtual void InitRHI() override;
virtual void ReleaseRHI() override;
// 리소스 .
void Add( FResources* Resources );
void Remove( FResources* Resources );
// Nanite렌더링(Render)호출(Call)회 , 필수: EndUpdate[]호출(Call)。
ENGINE_API void BeginAsyncUpdate(FRDGBuilder& GraphBuilder);
// Nanite렌더링(Render)호출(Call)회 , 필수: BeginUpdate[]호출(Call)。
ENGINE_API void EndAsyncUpdate(FRDGBuilder& GraphBuilder);
ENGINE_API bool IsAsyncUpdateInProgress();
// 추가(Add)개 ,호출(Call)회 。
ENGINE_API void SubmitFrameStreamingRequests(FRDGBuilder& GraphBuilder);
(......)
private:
friend class FStreamingUpdateTask;
// 힙 버퍼 , 데이터 및 버퍼 .
struct FHeapBuffer
{
int32 TotalUpload = 0;
FGrowOnlySpanAllocator Allocator;
FScatterUploadBuffer UploadBuffer;
FRWByteAddressBuffer DataBuffer;
void Release()
{
UploadBuffer.Release();
DataBuffer.Release();
}
};
// FPackedCluster*, GeometryData { Index, Position, TexCoord, TangentX, TangentZ }*
FHeapBuffer ClusterPageData;
FHeapBuffer ClusterPageHeaders;
FScatterUploadBuffer ClusterFixupUploadBuffer;
FHeapBuffer Hierarchy; // 레벨 회 구조체 .
FHeapBuffer RootPages; // .
TRefCountPtr< FRDGPooledBuffer > StreamingRequestsBuffer;
uint32 MaxStreamingPages;
uint32 MaxPendingPages;
uint32 MaxPageInstallsPerUpdate;
uint32 MaxStreamingReadbackBuffers;
// 데이터 .
uint32 ReadbackBuffersWriteIndex;
uint32 ReadbackBuffersNumPending;
TArray<uint32> NextRootPageVersion;
uint32 NextUpdateIndex;
uint32 NumRegisteredStreamingPages;
uint32 NumPendingPages;
uint32 NextPendingPageIndex;
TArray<FRootPageInfo> RootPageInfos;
#if !UE_BUILD_SHIPPING
uint64 PrevUpdateTick;
#endif
TArray< FRHIGPUBufferReadback* > StreamingRequestReadbackBuffers;
TArray< FResources* > PendingAdds;
TMap< uint32, FResources* > RuntimeResourceMap;
TMap< FPageKey, FStreamingPageInfo* > RegisteredStreamingPagesMap; // This is updated immediately.
TMap< FPageKey, FStreamingPageInfo* > CommittedStreamingPageMap; // This update is deferred to the point where the page has been loaded and committed to memory.
TArray< FStreamingRequest > PrioritizedRequestsHeap;
FStreamingPageInfo StreamingPageLRU;
FStreamingPageInfo* StreamingPageInfoFreeList;
TArray< FStreamingPageInfo > StreamingPageInfos;
// 의 보정/수정(Fix)정보 , 개 정보 ,로써 해제(Release)。
TArray< FFixupChunk* > StreamingPageFixupChunks;
TArray< FPendingPage > PendingPages;
#if !WITH_EDITOR
TArray< uint8 > PendingPageStagingMemory;
#endif
TArray< uint8 > PendingPageStagingMemoryLZ;
FRequestsHashTable* RequestsHashTable = nullptr;
FStreamingPageUploader* PageUploader = nullptr;
FGraphEventArray AsyncTaskEvents;
FAsyncState AsyncState;
// .
void CollectDependencyPages( FResources* Resources, TSet< FPageKey >& DependencyPages, const FPageKey& Key );
void SelectStreamingPages( FResources* Resources, TArray< FPageKey >& SelectedPages, TSet<FPageKey>& SelectedPagesSet, uint32 RuntimeResourceID, uint32 PageIndex, uint32 MaxSelectedPages );
// /.
void RegisterStreamingPage( FStreamingPageInfo* Page, const FPageKey& Key );
void UnregisterPage( const FPageKey& Key );
void MovePageToFreeList( FStreamingPageInfo* Page );
void ApplyFixups( const FFixupChunk& FixupChunk, const FResources& Resources, uint32 PageIndex, uint32 GPUPageIndex );
bool ArePageDependenciesCommitted(uint32 RuntimeResourceID, uint32 PageIndex, uint32 DependencyPageStart, uint32 DependencyPageNum);
// 반환(Return)는 /레벨 회 버퍼 는 변환(Transform/Convert)로 계산/산출(Calculate)상태 .
bool ProcessNewResources( FRDGBuilder& GraphBuilder);
uint32 DetermineReadyPages();
void InstallReadyPages( uint32 NumReadyPages );
// 비동기 갱신(Update).
void AsyncUpdate();
void ClearStreamingRequestCount(FRDGBuilder& GraphBuilder, FRDGBufferUAVRef BufferUAVRef);
};
// Nanite선언 .
extern ENGINE_API TGlobalResource< FStreamingManager > GStreamingManager;
}- NaniteRender
// Engine\Source\Runtime\Renderer\Private\Nanite\NaniteRender.h
static constexpr uint32 NANITE_MAX_MATERIALS = 64;
static constexpr uint32 MAX_VIEWS_PER_CULL_RASTERIZE_PASS_BITS = 12;
static constexpr uint32 MAX_VIEWS_PER_CULL_RASTERIZE_PASS_MASK = ( ( 1 << MAX_VIEWS_PER_CULL_RASTERIZE_PASS_BITS ) - 1 );
static constexpr uint32 MAX_VIEWS_PER_CULL_RASTERIZE_PASS = ( 1 << MAX_VIEWS_PER_CULL_RASTERIZE_PASS_BITS );
(......)
// Nanite버퍼 파라미터 .
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FNaniteUniformParameters, )
SHADER_PARAMETER(FIntVector4, SOAStrides)
SHADER_PARAMETER(FIntVector4, MaterialConfig) // .x mode, .yz grid size, .w unused
SHADER_PARAMETER(uint32, MaxNodes)
SHADER_PARAMETER(uint32, MaxVisibleClusters)
SHADER_PARAMETER(uint32, RenderFlags)
SHADER_PARAMETER(FVector4, RectScaleOffset) // xy: scale, zw: offset
SHADER_PARAMETER_SRV(ByteAddressBuffer, ClusterPageData)
SHADER_PARAMETER_SRV(ByteAddressBuffer, ClusterPageHeaders)
SHADER_PARAMETER_SRV(ByteAddressBuffer, VisibleClustersSWHW)
SHADER_PARAMETER_SRV(StructuredBuffer<uint>, VisibleMaterials)
SHADER_PARAMETER_TEXTURE(Texture2D<uint2>, MaterialRange)
SHADER_PARAMETER_TEXTURE(Texture2D<UlongType>, VisBuffer64)
SHADER_PARAMETER_TEXTURE(Texture2D<UlongType>, DbgBuffer64)
SHADER_PARAMETER_TEXTURE(Texture2D<uint>, DbgBuffer32)
END_SHADER_PARAMETER_STRUCT()
(......)
// 래스터라이제이션 파라미터 .
BEGIN_SHADER_PARAMETER_STRUCT( FRasterParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV( RWTexture2D< uint >, OutDepthBuffer ) // 뎁스
SHADER_PARAMETER_RDG_TEXTURE_UAV( RWTexture2D< UlongType >, OutVisBuffer64 ) //
SHADER_PARAMETER_RDG_TEXTURE_UAV( RWTexture2D< UlongType >, OutDbgBuffer64 ) // 데이터
SHADER_PARAMETER_RDG_TEXTURE_UAV( RWTexture2D< uint >, OutDbgBuffer32 )
SHADER_PARAMETER_RDG_TEXTURE_UAV( RWTexture2D< uint >, LockBuffer ) // 버퍼
END_SHADER_PARAMETER_STRUCT()
// Nanite드로우/렌더(Draw)정보 .
class FNaniteCommandInfo
{
public:
static constexpr int32 MAX_STATE_BUCKET_ID = (1 << 14) - 1; // Must match NaniteDataDecode.ush
void SetStateBucketId(int32 InStateBucketId)
{
StateBucketId = InStateBucketId;
}
int32 GetStateBucketId() const
{
check(StateBucketId < MAX_STATE_BUCKET_ID);
return StateBucketId;
}
uint32 GetMaterialId() const
{
return GetMaterialId(GetStateBucketId());
}
static uint32 GetMaterialId(int32 StateBucketId)
{
float DepthId = GetDepthId(StateBucketId);
return *reinterpret_cast<uint32*>(&DepthId);
}
static float GetDepthId(int32 StateBucketId)
{
return float(StateBucketId + 1) / float(MAX_STATE_BUCKET_ID);
}
private:
// 저장(Store)까지 FMeshDrawCommand의 FScene::NaniteDrawCommands 내에서 .
int32 StateBucketId = INDEX_NONE;
};
struct MeshDrawCommandKeyFuncs;
// Nanite드로우/렌더(Draw)커맨드 리스트 , Nanite의 비교 타입/유형 .
class FNaniteDrawListContext : public FMeshPassDrawListContext
{
public:
FNaniteDrawListContext(FRWLock& InNaniteDrawCommandLock, FStateBucketMap& InNaniteDrawCommands);
virtual FMeshDrawCommand& AddCommand(FMeshDrawCommand& Initializer, uint32 NumElements) override final;
virtual void FinalizeCommand(
const FMeshBatch& MeshBatch,
int32 BatchElementIndex,
int32 DrawPrimitiveId,
int32 ScenePrimitiveId,
ERasterizerFillMode MeshFillMode,
ERasterizerCullMode MeshCullMode,
FMeshDrawCommandSortKey SortKey,
EFVisibleMeshDrawCommandFlags Flags,
const FGraphicsMinimalPipelineStateInitializer& PipelineState,
const FMeshProcessorShaders* ShadersForDebugging,
FMeshDrawCommand& MeshDrawCommand
) override final;
(......)
private:
FRWLock* NaniteDrawCommandLock;
FStateBucketMap* NaniteDrawCommands; // Nanite드로우/렌더(Draw).
FNaniteCommandInfo CommandInfo; // Nanite정보 .
FMeshDrawCommand MeshDrawCommandForStateBucketing;
};
// Nanite셰이딩 .
class FNaniteShader : public FGlobalShader
{
public:
(......)
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters);
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment;
};
// 뎁스 드로우/렌더(Draw)풀스크린(Full Screen)의 포인트 셰이딩 , 플랫폼.
class FNaniteMaterialVS : public FNaniteShader
{
DECLARE_GLOBAL_SHADER(FNaniteMaterialVS);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(float, MaterialDepth)
END_SHADER_PARAMETER_STRUCT()
(......)
void GetShaderBindings(
const FScene* Scene,
ERHIFeatureLevel::Type FeatureLevel,
const FPrimitiveSceneProxy* PrimitiveSceneProxy,
const FMaterialRenderProxy& MaterialRenderProxy,
const FMaterial& Material,
const FMeshPassProcessorRenderState& DrawRenderState,
const FMeshMaterialShaderElementData& ShaderElementData,
FMeshDrawSingleShaderBindings& ShaderBindings) const
{
ShaderBindings.Add(NaniteUniformBuffer, DrawRenderState.GetNaniteUniformBuffer());
}
private:
LAYOUT_FIELD(FShaderParameter, MaterialDepth);
LAYOUT_FIELD(FShaderUniformBufferParameter, NaniteUniformBuffer);
};
// Nanite처리(Process).
class FNaniteMeshProcessor : public FMeshPassProcessor
{
public:
(......)
virtual void AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId = -1) override final;
private:
FMeshPassProcessorRenderState PassDrawRenderState;
};
// 생성(Create)Nanite처리(Process)인스턴스 .
FMeshPassProcessor* CreateNaniteMeshProcessor(const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext);
// Nanite머티리얼(Material)테이블 .
class FNaniteMaterialTables
{
public:
FNaniteMaterialTables(uint32 MaxMaterials = NANITE_MAX_MATERIALS);
~FNaniteMaterialTables();
void Release();
void UpdateBufferState(FRDGBuilder& GraphBuilder, uint32 NumPrimitives);
void Begin(FRHICommandListImmediate& RHICmdList, uint32 NumPrimitives, uint32 NumPrimitiveUpdates);
void* GetDepthTablePtr(uint32 PrimitiveIndex, uint32 EntryCount);
void Finish(FRHICommandListImmediate& RHICmdList);
FRHIShaderResourceView* GetDepthTableSRV() const { return DepthTableDataBuffer.SRV; }
private:
uint32 MaxMaterials = 0;
uint32 NumPrimitiveUpdates = 0;
uint32 NumDepthTableUpdates = 0;
uint32 NumHitProxyTableUpdates = 0;
// CPU 및 을(를) 활용하여 의 데이터 버퍼 .
FScatterUploadBuffer DepthTableUploadBuffer;
FRWByteAddressBuffer DepthTableDataBuffer;
FScatterUploadBuffer HitProxyTableUploadBuffer;
FRWByteAddressBuffer HitProxyTableDataBuffer;
};
namespace Nanite
{
// 래스터라이제이션 .
enum class ERasterTechnique : uint8
{
LockBufferFallback = 0, // 을(를) 활용하여 을(를) 활용하여 버퍼 64원자적 (조건 ).
PlatformAtomics = 1, // 을(를) 활용하여 플랫폼의 64원자적 .
NVAtomics = 2, // 을(를) 활용하여 Nv확장 의 64원자적 .
AMDAtomicsD3D11 = 3, // 을(를) 활용하여 AMD확장 (D3D11)의 64원자적 .
AMDAtomicsD3D12 = 4, // 을(를) 활용하여 AMD확장 (D3D12)의 64원자적 .
DepthOnly = 5, // 뎁스 을(를) 활용하여 32원자적 , .
NumTechniques
};
// 래스터라이제이션 스케줄링 .
enum class ERasterScheduling : uint8
{
HardwareOnly = 0, // 오직 을(를) 활용하여 의 래스터라이제이션 .
HardwareThenSoftware = 1, // 을(를) 활용하여 래스터라이제이션 ,을(를) 활용하여 (comtue shader)래스터라이제이션 .
HardwareAndSoftwareOverlap = 2, // 을(를) 활용하여 래스터라이제이션 ,오버랩 을(를) 활용하여 (comtue shader)래스터라이제이션 .
};
// 출력버퍼 . 생성(Create)디바이스을(를) 활용하여 래스터라이제이션 .
enum class EOutputBufferMode : uint8
{
VisBuffer, // 버퍼 , 기본값(Default), 을(를) 활용하여 출력ID 및 뎁스 .
DepthOnly, // 래스터라이제이션 뎁스 까지 32버퍼 .
};
// 패딩/채우기 의 뷰(View).
struct FPackedView
{
FMatrix TranslatedWorldToView;
FMatrix TranslatedWorldToClip;
FMatrix ViewToClip;
FMatrix ClipToWorld;
FMatrix PrevTranslatedWorldToView;
FMatrix PrevTranslatedWorldToClip;
FMatrix PrevViewToClip;
FMatrix PrevClipToWorld;
FIntVector4 ViewRect;
FVector4 ViewSizeAndInvSize;
FVector4 ClipSpaceScaleOffset;
FVector4 PreViewTranslation;
FVector4 PrevPreViewTranslation;
FVector4 WorldCameraOrigin;
FVector4 ViewForwardAndNearPlane;
FVector2D LODScales;
float MinBoundsRadiusSq;
uint32 StreamingPriorityCategory_AndFlags;
FIntVector4 TargetLayerIdX_AndMipLevelY_AndNumMipLevelsZ;
FIntVector4 HZBTestViewRect; // In full resolution
// 계산/산출(Calculate)LOD,뷰(View)크기 및 투영/프로젝션 설정(Set)。글로벌 변수GNaniteMaxPixelsPerEdge.
void UpdateLODScales();
};
// 클리핑/클램핑(Clipping).
struct FCullingContext
{
FGlobalShaderMap* ShaderMap;
uint32 DrawPassIndex;
uint32 NumInstancesPreCull;
uint32 RenderFlags;
uint32 DebugFlags;
TRefCountPtr<IPooledRenderTarget> PrevHZB; // 만약 null, HZB클리핑/클램핑(Clipping)활성화(Enable).
FIntRect HZBBuildViewRect;
bool bTwoPassOcclusion;
bool bSupportsMultiplePasses;
FIntVector4 SOAStrides;
FRDGBufferRef MainRasterizeArgsSWHW;
FRDGBufferRef PostRasterizeArgsSWHW;
FRDGBufferRef SafeMainRasterizeArgsSWHW;
FRDGBufferRef SafePostRasterizeArgsSWHW;
FRDGBufferRef MainAndPostPassPersistentStates;
FRDGBufferRef VisibleClustersSWHW;
FRDGBufferRef OccludedInstances;
FRDGBufferRef OccludedInstancesArgs;
FRDGBufferRef TotalPrevDrawClustersBuffer;
FRDGBufferRef StreamingRequests;
FRDGBufferRef ViewsBuffer;
FRDGBufferRef InstanceDrawsBuffer;
FRDGBufferRef StatsBuffer;
};
// 래스터라이제이션 .
struct FRasterContext
{
FGlobalShaderMap* ShaderMap;
FVector2D RcpViewSize;
FIntPoint TextureSize;
ERasterTechnique RasterTechnique;
ERasterScheduling RasterScheduling;
FRasterParameters Parameters;
FRDGTextureRef LockBuffer;
FRDGTextureRef DepthBuffer;
FRDGTextureRef VisBuffer64;
FRDGTextureRef DbgBuffer64;
FRDGTextureRef DbgBuffer32;
uint32 VisualizeModeBitMask;
bool VisualizeActive;
};
// 래스터라이제이션 결과 .
struct FRasterResults
{
FIntVector4 SOAStrides;
uint32 MaxVisibleClusters;
uint32 MaxNodes;
uint32 RenderFlags;
FRDGBufferRef ViewsBuffer{};
FRDGBufferRef VisibleClustersSWHW{};
FRDGTextureRef VisBuffer64{};
FRDGTextureRef DbgBuffer64{};
FRDGTextureRef DbgBuffer32{};
FRDGTextureRef MaterialDepth{};
FRDGTextureRef NaniteMask{};
FRDGTextureRef VelocityBuffer{};
TArray<FVisualizeResult, TInlineAllocator<32>> Visualizations;
};
// 초기화(Initialize)클리핑/클램핑(Clipping).
FCullingContext InitCullingContext(FRDGBuilder& GraphBuilder, const FScene& Scene, ...);
// 초기화(Initialize)래스터라이제이션 .
FRasterContext InitRasterContext(FRDGBuilder& GraphBuilder, ERHIFeatureLevel::Type FeatureLevel, ...);
// 패딩/채우기 의 뷰(View)파라미터 .
struct FPackedViewParams
{
FViewMatrices ViewMatrices;
FViewMatrices PrevViewMatrices;
FIntRect ViewRect;
FIntPoint RasterContextSize;
uint32 StreamingPriorityCategory = 0;
float MinBoundsRadius = 0.0f;
float LODScaleFactor = 1.0f;
uint32 Flags = 0;
int32 TargetLayerIndex = 0;
int32 PrevTargetLayerIndex = INDEX_NONE;
int32 TargetMipLevel = 0;
int32 TargetMipCount = 1;
FIntRect HZBTestViewRect = {0, 0, 0, 0};
};
FPackedView CreatePackedView( const FPackedViewParams& Params );
FPackedView CreatePackedViewFromViewInfo(const FViewInfo& View, FIntPoint RasterContextSize, ...);
// 래스터라이제이션 상태 .
struct FRasterState
{
bool bNearClip = true; // 는 활성화(Enable)Near클리핑/클램핑(Clipping).
ERasterizerCullMode CullMode = CM_CW; // 래스터라이제이션 클리핑/클램핑(Clipping), 기본값: .
};
// 클리핑/클램핑(Clipping)의 래스터라이제이션 .
void CullRasterize(
FRDGBuilder& GraphBuilder,
const FScene& Scene,
const TArray<FPackedView, SceneRenderingAllocator>& Views,
FCullingContext& CullingContext,
const FRasterContext& RasterContext,
const FRasterState& RasterState = FRasterState(),
const TArray<FInstanceDraw, SceneRenderingAllocator>* OptionalInstanceDraws = nullptr,
bool bExtractStats = false
);
// 래스터라이제이션 까지 그림자(Shadow)(virtual shadow map)
void CullRasterize(
FRDGBuilder& GraphBuilder,
const FScene& Scene,
const TArray<FPackedView, SceneRenderingAllocator>& Views,
uint32 NumPrimaryViews, // Number of non-mip views
FCullingContext& CullingContext,
const FRasterContext& RasterContext,
const FRasterState& RasterState = FRasterState(),
const TArray<FInstanceDraw, SceneRenderingAllocator>* OptionalInstanceDraws = nullptr,
FVirtualShadowMapArray* VirtualShadowMapArray = nullptr,
bool bExtractStats = false
);
// 압축해제/언패킹(Unpacking)래스터라이제이션 결과 .
void ExtractResults(FRDGBuilder& GraphBuilder, const FCullingContext& CullingContext, const FRasterContext& RasterContext, FRasterResults& RasterResults);
// 트리거 그림자(Shadow).
void EmitShadowMap(FRDGBuilder& GraphBuilder, const FRasterContext& RasterContext, const FRDGTextureRef DepthBuffer, ...);
// 트리거 그림자(Shadow).
void EmitCubemapShadow(FRDGBuilder& GraphBuilder, const FRasterContext& RasterContext, const FRDGTextureRef CubemapDepthBuffer, ...);
// 트리거 뎁스 .
void EmitDepthTargets(FRDGBuilder& GraphBuilder, const FScene& Scene, const FViewInfo& View, ...);
// 드로우/렌더(Draw)BasePass.
void DrawBasePass(FRDGBuilder& GraphBuilder, const FSceneTextures& SceneTextures, const FDBufferTextures& DBufferTextures, const FScene& Scene, const FViewInfo& View, const FRasterResults& RasterResults
);
// 드로우/렌더(Draw)Lumen채널/패스(Pass).
void DrawLumenMeshCapturePass(FRDGBuilder& GraphBuilder, const FScene& Scene, ...);
(......)
}
// 는 렌더링(Render)Nanite.
extern bool ShouldRenderNanite(const FScene* Scene, const FViewInfo& View, bool bCheckForAtomicSupport = true);- NaniteSceneProxy
// Engine\Source\Runtime\Engine\Public\NaniteSceneProxy.h
namespace Nanite
{
// Nanite.
class FSceneProxyBase : public FPrimitiveSceneProxy
{
public:
struct FMaterialSection
{
UMaterialInterface* Material = nullptr;
int32 MaterialIndex = INDEX_NONE;
};
public:
ENGINE_API SIZE_T GetTypeHash() const override;
FSceneProxyBase(UPrimitiveComponent* Component)
: FPrimitiveSceneProxy(Component)
{
bIsNaniteMesh = true;
bAlwaysVisible = true;
}
// 검사/감지(Detect)는 Nanite렌더링(Render)의 조건 : 반투명(Translucent), 는 , 는 Masked, 는 노멀(Normal)반투명(Translucent), 는 반투명(Translucent).
static bool IsNaniteRenderable(FMaterialRelevance MaterialRelevance)
{
return MaterialRelevance.bOpaque &&
!MaterialRelevance.bDecal &&
!MaterialRelevance.bMasked &&
!MaterialRelevance.bNormalTranslucency &&
!MaterialRelevance.bSeparateTranslucency;
}
virtual bool CanBeOccluded() const override;
inline const TArray<FMaterialSection>& GetMaterialSections() const;
inline int32 GetMaterialMaxIndex() const;
virtual const TArray<FPrimitiveInstance>* GetPrimitiveInstances() const;
virtual TArray<FPrimitiveInstance>* GetPrimitiveInstances();
virtual uint8 GetCurrentFirstLODIdx_RenderThread() const override;
protected:
ENGINE_API void DrawStaticElementsInternal(FStaticPrimitiveDrawInterface* PDI, const FLightCacheInterface* LCI);
protected:
TArray<FMaterialSection> MaterialSections;
TArray<FPrimitiveInstance> Instances;
int32 MaterialMaxIndex = INDEX_NONE;
};
// Nanite.
class FSceneProxy : public FSceneProxyBase
{
public:
FSceneProxy(UStaticMeshComponent* Component);
FSceneProxy(UInstancedStaticMeshComponent* Component);
FSceneProxy(UHierarchicalInstancedStaticMeshComponent* Component);
virtual ~FSceneProxy() = default;
public:
// FPrimitiveSceneProxy인터페이스 .
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override;
virtual void GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const override;
// 페치/가져오기(Fetch)정적 또는 동적 .
virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override;
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override;
// 인터페이스 .
#if RHI_RAYTRACING
virtual bool IsRayTracingRelevant() const { return true; }
virtual bool IsRayTracingStaticRelevant() const { return false; }
virtual void GetDynamicRayTracingInstances(FRayTracingMaterialGatheringContext& Context, TArray<struct FRayTracingInstance>& OutRayTracingInstances) override;
#endif
virtual uint32 GetMemoryFootprint() const override;
virtual void GetLCIs(FLCIArray& LCIs) override
{
FLightCacheInterface* LCI = &MeshInfo;
LCIs.Add(LCI);
}
// 거리인터페이스 .
virtual void GetDistancefieldAtlasData(const FDistanceFieldVolumeData*& OutDistanceFieldData, float& SelfShadowBias) const override;
virtual void GetDistancefieldInstanceData(TArray<FMatrix>& ObjectLocalToWorldTransforms) const override;
virtual bool HasDistanceFieldRepresentation() const override;
// GI인터페이스 .
virtual const FCardRepresentationData* GetMeshCardRepresentation() const override;
virtual int32 GetLightMapCoordinateIndex() const override;
// 페치/가져오기(Fetch)정적 .
const UStaticMesh* GetStaticMesh() const
{
return StaticMesh;
}
protected:
virtual void CreateRenderThreadResources() override;
class FMeshInfo : public FLightCacheInterface
{
public:
FMeshInfo(const UStaticMeshComponent* InComponent);
// FLightCacheInterface.
virtual FLightInteraction GetInteraction(const FLightSceneProxy* LightSceneProxy) const override;
private:
TArray<FGuid> IrrelevantLights;
};
bool IsCollisionView(const FEngineShowFlags& EngineShowFlags, bool& bDrawSimpleCollision, bool& bDrawComplexCollision) const;
protected:
FMeshInfo MeshInfo;
FResources* Resources = nullptr;
const FStaticMeshRenderData* RenderData;
const FDistanceFieldVolumeData* DistanceFieldData;
const FCardRepresentationData* CardRepresentationData;
FMaterialRelevance MaterialRelevance;
uint32 bReverseCulling : 1;
uint32 bHasMaterialErrors : 1;
const UStaticMesh* StaticMesh = nullptr;
#if RHI_RAYTRACING
TArray<FRayTracingGeometry*> RayTracingGeometries;
#endif
(......)
};
} // namespace Nanite- RenderUtils
// Engine\Source\Runtime\RenderCore\Public\RenderUtils.h
(......)
// 검사/감지(Detect)플랫폼는 지원 Nanite렌더링(Render).
RENDERCORE_API bool DoesPlatformSupportNanite(EShaderPlatform Platform)
{
// 현재(Current)플랫폼정의 DDPI(FGenericDataDrivenShaderPlatformInfo).
const bool bValidPlatform = FDataDrivenShaderPlatformInfo::IsValid(Platform);
// NaniteGPUScene.
const bool bSupportGPUScene = FDataDrivenShaderPlatformInfo::GetSupportsGPUScene(Platform);
// Nanite검사/감지(Detect).
const bool bSupportNanite = FDataDrivenShaderPlatformInfo::GetSupportsNanite(Platform);
const bool bFullCheck = bValidPlatform && bSupportGPUScene && bSupportNanite;
return bFullCheck;
}
// 을(를) 활용하여 Nanite, 만약 반환(Return)true.
inline bool UseNanite(EShaderPlatform ShaderPlatform, bool bCheckForAtomicSupport = true);
// 을(를) 활용하여 VSM, 반환(Return)true.
inline bool UseVirtualShadowMaps(EShaderPlatform ShaderPlatform, const FStaticFeatureLevel FeatureLevel);
// 을(를) 활용하여 Nanite의 VSM, 반환(Return)true. 는 r.Shadow.Virtual.NonNaniteVSM로 0, UseVirtualShadowMaps로 true.
inline bool UseNonNaniteVirtualShadowMaps(EShaderPlatform ShaderPlatform, const FStaticFeatureLevel FeatureLevel);- 다른
// Engine\Source\Runtime\Engine\Classes\Components\StaticMeshComponent.h
class ENGINE_API UStaticMeshComponent : public UMeshComponent
{
(......)
uint8 bDisplayNaniteProxyMesh:1; // nanite활성화(Enable)의 ,만약 로 true,오직 .
(......)
};
// Engine\Source\Runtime\Engine\Public\PrimitiveSceneProxy.h
class FPrimitiveSceneProxy
{
inline bool IsNaniteMesh() const
{
return bIsNaniteMesh;
}
(......)
private:
uint8 bIsNaniteMesh : 1; // 는 Nanite.
(......)
};
// 만약 Nanite렌더링(Render), 이면 반환(Return)true.
ENGINE_API extern bool SupportsNaniteRendering(const FVertexFactory* RESTRICT VertexFactory, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy);
ENGINE_API extern bool SupportsNaniteRendering(const FVertexFactory* RESTRICT VertexFactory, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, const class FMaterialRenderProxy* MaterialRenderProxy, ERHIFeatureLevel::Type FeatureLevel);
// Engine\Source\Runtime\Renderer\Public\MeshPassProcessor.h
struct FMeshPassProcessorRenderState
{
public:
void SetNaniteUniformBuffer(FRHIUniformBuffer* InNaniteUniformBuffer);
FRHIUniformBuffer* GetNaniteUniformBuffer() const;
(......)
private:
FRHIUniformBuffer* NaniteUniformBuffer = nullptr; // Nanite버퍼 .
(......)
};
// Engine\Source\Runtime\RenderCore\Public\VertexFactory.h
// 포인트 플래그 .
enum class EVertexFactoryFlags : uint32
{
None = 0u,
UsedWithMaterials = 1u << 1,
SupportsStaticLighting = 1u << 2,
SupportsDynamicLighting = 1u << 3,
SupportsPrecisePrevWorldPos = 1u << 4,
SupportsPositionOnly = 1u << 5,
SupportsCachingMeshDrawCommands = 1u << 6,
SupportsPrimitiveIdStream = 1u << 7,
SupportsNaniteRendering = 1u << 8, // 는 지원 Nanite렌더링(Render).
};
// 는 지원 Nanite렌더링(Render).
bool SupportsNaniteRendering() const { return HasFlags(EVertexFactoryFlags::SupportsNaniteRendering); }
// Engine\Source\Runtime\RHI\Public\RHIDefinitions.h
// 을(를) 활용하여 의 데이터 셰이딩 플랫폼정보 .
class RHI_API FGenericDataDrivenShaderPlatformInfo
{
static FORCEINLINE_DEBUGGABLE const bool GetSupportsNanite(const FStaticShaderPlatform Platform)
{
return Infos[Platform].bSupportsNanite;
}
(......)
};6.4.3.3 나나이트 렌더링 프로세스
Nanite의 주요 렌더링 단계는 FDeferredShadingSceneRenderer::Render에서도 발생합니다. Nanite와 관련된 단계와 이전 기사에 관련된 중요한 단계는 아래에 설명되어 있습니다.
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
{
// 을(를) 활용하여 Nanite렌더링(Render)。
const bool bNaniteEnabled = UseNanite(ShaderPlatform) && ViewFamily.EngineShowFlags.NaniteMeshes;
// 갱신(Update)정보 .
Scene->UpdateAllPrimitiveSceneInfos(GraphBuilder, true);
// 을(를) 활용하여 GPUScene.
FGPUSceneScopeBeginEndHelper GPUSceneScopeBeginEndHelper(Scene->GPUScene, GPUSceneDynamicContext, Scene);
bool bVisualizeNanite = false;
if (bNaniteEnabled) // Nanite활성화(Enable)실행(Execute)
{
// 갱신(Update)Nanite글로벌 리소스 . 로 Nanite의 버퍼 。
Nanite::GGlobalResources.Update(GraphBuilder);
// 비동기 갱신(Update)Nanite.
Nanite::GStreamingManager.BeginAsyncUpdate(GraphBuilder);
// 처리(Process)Nanite시각화(Visualization).
FNaniteVisualizationData& NaniteVisualization = GetNaniteVisualizationData();
if (Views.Num() > 0)
{
const FName& NaniteViewMode = Views[0].CurrentNaniteVisualizationMode;
if (NaniteVisualization.Update(NaniteViewMode))
{
ViewFamily.EngineShowFlags.SetVisualizeNanite(true);
}
bVisualizeNanite = NaniteVisualization.IsActive() && ViewFamily.EngineShowFlags.VisualizeNanite;
}
}
(......)
// 는 적용 Nanite머티리얼(Material).
const bool bShouldApplyNaniteMaterials
= !ViewFamily.EngineShowFlags.ShaderComplexity
&& !ViewFamily.UseDebugViewPS()
&& !ViewFamily.EngineShowFlags.Wireframe
&& !ViewFamily.EngineShowFlags.LightMapDensity;
(......)
// 인스턴스 클리핑/클램핑(Clipping).
FInstanceCullingManager InstanceCullingManager(GInstanceCullingManagerResources, Scene->GPUScene.IsEnabled());
bDoInitViewAftersPrepass = InitViews(GraphBuilder, ..., InstanceCullingManager);
(......)
// 처리(Process)GPUScene.
{
(......)
// 갱신(Update)GPUScene.
Scene->GPUScene.Update(GraphBuilder, *Scene);
(......)
// 동적 셰이딩 데이터 까지 GPU.
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
FViewInfo& View = Views[ViewIndex];
Scene->GPUScene.UploadDynamicPrimitiveShaderDataForView(GraphBuilder, Scene, View);
}
// 인스턴스 클리핑/클램핑(Clipping).
{
InstanceCullingManager.CullInstances(GraphBuilder, Scene->GPUScene);
}
(......)
}
(......)
if (bNaniteEnabled)
{
Nanite::ListStatFilters(this);
// 필수: 의 Nanite렌더링(Render)호출(Call).
Nanite::GStreamingManager.EndAsyncUpdate(GraphBuilder);
}
(......)
// 뎁스 채널/패스(Pass).
RenderPrePass(GraphBuilder, SceneTextures.Depth.Target, InstanceCullingManager);
(......)
// Nanite래스터라이제이션
TArray<Nanite::FRasterResults, TInlineAllocator<2>> NaniteRasterResults;
if (bNaniteEnabled && Views.Num() > 0)
{
LLM_SCOPE_BYTAG(Nanite);
NaniteRasterResults.AddDefaulted(Views.Num());
RDG_GPU_STAT_SCOPE(GraphBuilder, NaniteRaster);
const FIntPoint RasterTextureSize = SceneTextures.Depth.Target->Desc.Extent;
const FViewInfo& PrimaryViewRef = Views[0];
const FIntRect PrimaryViewRect = PrimaryViewRef.ViewRect;
// 래스터라이제이션 뷰(View)
{
Nanite::FRasterState RasterState;
Nanite::FRasterContext RasterContext = Nanite::InitRasterContext(GraphBuilder, FeatureLevel, RasterTextureSize);
const bool bTwoPassOcclusion = true;
const bool bUpdateStreaming = true;
const bool bSupportsMultiplePasses = false;
const bool bForceHWRaster = RasterContext.RasterScheduling == Nanite::ERasterScheduling::HardwareOnly;
const bool bPrimaryContext = true;
const bool bDiscardNonMoving = ViewFamily.EngineShowFlags.DrawOnlyVSMInvalidatingGeo != 0;
// 순회(Traverse/Iterate)view
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
// 초기화(Initialize)클리핑/클램핑(Clipping).
Nanite::FCullingContext CullingContext = Nanite::InitCullingContext(
GraphBuilder,
*Scene,
!bIsEarlyDepthComplete ? View.PrevViewInfo.NaniteHZB : View.PrevViewInfo.HZB,
View.ViewRect,
bTwoPassOcclusion,
bUpdateStreaming,
bSupportsMultiplePasses,
bForceHWRaster,
bPrimaryContext,
bDiscardNonMoving
);
static FString EmptyFilterName = TEXT(""); // Empty filter represents primary view.
const bool bExtractStats = Nanite::IsStatFilterActive(EmptyFilterName);
Nanite::FPackedView PackedView = Nanite::CreatePackedViewFromViewInfo(View, RasterTextureSize, VIEW_FLAG_HZBTEST, /*StreamingPriorityCategory*/ 3);
// 클리핑/클램핑(Clipping)의 래스터라이제이션 .
Nanite::CullRasterize(
GraphBuilder,
*Scene,
{ PackedView },
CullingContext,
RasterContext,
RasterState,
/*OptionalInstanceDraws*/ nullptr,
bExtractStats
);
Nanite::FRasterResults& RasterResults = NaniteRasterResults[ViewIndex];
// 뎁스 , 이면 렌더링(Render).
if (bNeedsPrePass)
{
Nanite::EmitDepthTargets(
GraphBuilder,
*Scene,
Views[ViewIndex],
CullingContext.SOAStrides,
CullingContext.VisibleClustersSWHW,
CullingContext.ViewsBuffer,
SceneTextures.Depth.Target,
RasterContext.VisBuffer64,
RasterResults.MaterialDepth,
RasterResults.NaniteMask,
RasterResults.VelocityBuffer,
bNeedsPrePass
);
}
// 레벨 회 뎁스 버퍼(Depth Buffer)HZB.
if (!bIsEarlyDepthComplete && bTwoPassOcclusion && View.ViewState)
{
// 개 채널/패스(Pass)의 의 씬 뎁스(SceneDepth),따라서 을(를) 활용하여 의 HZB채널/패스(Pass), 이면 채널/패스(Pass)HZB소멸 차폐/오클루전 컬링 。
RDG_EVENT_SCOPE(GraphBuilder, "Nanite::BuildHZB");
FRDGTextureRef SceneDepth = SystemTextures.Black;
FRDGTextureRef GraphHZB = nullptr;
// HZB.
BuildHZBFurthest(
GraphBuilder,
SceneDepth,
RasterContext.VisBuffer64,
PrimaryViewRect,
FeatureLevel,
ShaderPlatform,
TEXT("Nanite.HZB"),
/* OutFurthestHZBTexture = */ &GraphHZB );
GraphBuilder.QueueTextureExtraction( GraphHZB, &View.ViewState->PrevFrameViewInfo.NaniteHZB );
}
Nanite::ExtractResults(GraphBuilder, CullingContext, RasterContext, RasterResults);
}
}
}
(......)
// 렌더링(Render)Nanite의 BasePass.
{
RenderBasePass(GraphBuilder, SceneTextures, DBufferTextures, BasePassDepthStencilAccess, ForwardScreenSpaceShadowMaskTexture, InstanceCullingManager);
AddServiceLocalQueuePass(GraphBuilder);
if (bNaniteEnabled && bShouldApplyNaniteMaterials)
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
const FViewInfo& View = Views[ViewIndex];
Nanite::FRasterResults& RasterResults = NaniteRasterResults[ViewIndex];
// 만약 드로우/렌더(Draw)뎁스 , 이면 드로우/렌더(Draw)뎁스
if (!bNeedsPrePass)
{
Nanite::EmitDepthTargets(
GraphBuilder,
*Scene,
Views[ViewIndex],
RasterResults.SOAStrides,
RasterResults.VisibleClustersSWHW,
RasterResults.ViewsBuffer,
SceneTextures.Depth.Target,
RasterResults.VisBuffer64,
RasterResults.MaterialDepth,
RasterResults.NaniteMask,
RasterResults.VelocityBuffer,
bNeedsPrePass
);
}
// 드로우/렌더(Draw)BasePass.
Nanite::DrawBasePass(
GraphBuilder,
SceneTextures,
DBufferTextures,
*Scene,
View,
RasterResults
);
}
}
if (!bAllowReadOnlyDepthBasePass)
{
AddResolveSceneDepthPass(GraphBuilder, Views, SceneTextures.Depth);
}
(......)
}
(......)
if (bNaniteEnabled)
{
// 계산/산출(Calculate).
if (!bOcclusionBeforeBasePass)
{
ComputeVolumetricFog(GraphBuilder);
}
// .
Nanite::GStreamingManager.SubmitFrameStreamingRequests(GraphBuilder);
}
(......)
// 렌더링(Render)레이턴시 라이트(Light).
RenderLights(GraphBuilder, SceneTextures, ...);
(......)
// 렌더링(Render)반투명(Translucent).
RenderTranslucency(GraphBuilder, SceneTextures, ...);
(......)
// 포스트 프로세스
AddPostProcessingPasses(GraphBuilder, View, PostProcessingInputs, NaniteResults, InstanceCullingManager);
(......)
}Nanite의 렌더링 프로세스와 일반 모드 비교 유형은 먼저 메타데이터, GPUScene 및 자르기 데이터를 업데이트한 다음 BasePass 및 조명을 렌더링하고 마지막으로 반투명 및 후처리를 수행하는 것을 볼 수 있습니다. 그러나 GStreamingManager, FInstanceCullingManager, HZB 구성, Nanite 래스터라이제이션 및 기타 단계가 추가되는 등 일반 모드와 차이점도 있습니다. UE5와 관련된 주요 단계를 보여주기 위해 RenderDoc을 사용하여 샘플 프로젝트 AncientGame을 가로채겠습니다:

RenderDoc이 가로채는 UE5 렌더링 프로세스. 빨간색 상자는 UE5 관련 단계입니다.
6.4.3.4 나나이트 자르기
Nanite의 인스턴스화된 클리핑은 FInstanceCullingManager에 의해 처리되며 FDeferredShadingSceneRenderer::Render의 전체 프로세스를 통해 실행됩니다. 다음은 해당 유형 및 관련 유형의 정의 및 선언입니다.
// Engine\Source\Runtime\Engine\Public\SceneManagement.h
// 인스턴스 클리핑/클램핑(Clipping)리소스 , 을(를) 활용하여 FInstanceCullingManager 내에서 .
class FInstanceCullingManagerResources : public FRenderResource
{
public:
// 드로우/렌더(Draw)인스턴스 개수 는 1024*1024=104개 .
static constexpr uint32 MaxIndirectInstances = 1024 * 1024;
// 초기화(Initialize) 및 해제(Release)RHI리소스 .
virtual void InitRHI() override;
virtual void ReleaseRHI() override;
// 페치/가져오기(Fetch)데이터 인터페이스 .
FRHIBuffer* GetInstancesIdBuffer() const { return InstanceIdsBuffer.Buffer; }
FRHIShaderResourceView* GetInstancesIdBufferSrv() const { return InstanceIdsBuffer.SRV.GetReference(); }
FRHIShaderResourceView* GetPageInfoBufferSrv() const { return PageInfoBuffer.SRV.GetReference(); }
FUnorderedAccessViewRHIRef GetInstancesIdBufferUav() const { return InstanceIdsBuffer.UAV; }
FUnorderedAccessViewRHIRef GetPageInfoBufferUav() const { return PageInfoBuffer.UAV; }
private:
FRWBuffer PageInfoBuffer; // 정보 버퍼 .
FRWBuffer InstanceIdsBuffer; // 인스턴스 ID버퍼 .
};
// 글로벌 FInstanceCullingManagerResources객체/오브젝트 .
extern ENGINE_API TGlobalResource<FInstanceCullingManagerResources> GInstanceCullingManagerResources;
// Engine\Source\Runtime\Renderer\Private\InstanceCulling\InstanceCullingManager.h
// 인스턴스 클리핑/클램핑(Clipping) 내에서 데이터 .
class FInstanceCullingIntermediate
{
public:
// 개 뷰(View)의 개 Instance, CullInstances인터페이스 처리(Process).
FRDGBufferRef VisibleInstanceFlags = nullptr;
// 인스턴스 ID확장 을(를) 활용하여 의 , 을(를) 활용하여 글로벌 인스턴스 ID버퍼 내에서 할당(Allocate). CullInstances초기화(Initialize)로 0.
FRDGBufferRef InstanceIdOutOffsetBuffer = nullptr;
// 인스턴스 개수 .
int32 NumInstances = 0;
// 뷰(View)개수 .
int32 NumViews = 0;
};
// 인스턴스 클리핑/클램핑(Clipping)결과 .
struct FInstanceCullingResult
{
// 드로우/렌더(Draw)파라미터 버퍼 .
FRDGBufferRef DrawIndirectArgsBuffer = nullptr;
// 인스턴스 ID버퍼 .
FRDGBufferRef InstanceIdOffsetBuffer = nullptr;
// 페치/가져오기(Fetch)드로우/렌더(Draw)파라미터 까지 FInstanceCullingDrawParams 내에서 .
void GetDrawParameters(FInstanceCullingDrawParams &OutParams) const
{
OutParams.DrawIndirectArgsBuffer = DrawIndirectArgsBuffer;
OutParams.InstanceIdOffsetBuffer = InstanceIdOffsetBuffer;
}
// 검사/감지(Detect)페치/가져오기(Fetch)드로우/렌더(Draw)파라미터 .
static void CondGetDrawParameters(const FInstanceCullingResult* InstanceCullingResult, FInstanceCullingDrawParams& OutParams)
{
if (InstanceCullingResult)
{
InstanceCullingResult->GetDrawParameters(OutParams);
}
else
{
OutParams.DrawIndirectArgsBuffer = nullptr;
OutParams.InstanceIdOffsetBuffer = nullptr;
}
}
};
// 인스턴스 드로우/렌더(Draw)의 파라미터 및 클리핑/클램핑(Clipping)의 할당(Allocate), 을(를) 활용하여 GPUScene클리핑/클램핑(Clipping).
class FInstanceCullingManager
{
public:
FInstanceCullingManager(FInstanceCullingManagerResources& InResources, bool bInIsEnabled);
// 의 인스턴스 .
static constexpr uint32 MaxAverageInstanceFactor = 128;
bool IsEnabled() const { return bIsEnabled; }
// 클리핑/클램핑(Clipping)의 뷰(View), 반환(Return)뷰(View)의 id.
int32 RegisterView(const Nanite::FPackedViewParams& Params);
int32 RegisterView(const FViewInfo& ViewInfo);
// 클리핑/클램핑(Clipping)인스턴스 , 뷰(View)초기화(Initialize) 및 , GPUScene갱신(Update)렌더링(Render).
void CullInstances(FRDGBuilder& GraphBuilder, FGPUScene& GPUScene);
// 에 의해 CullInstances패딩/채우기 , 을(를) 활용하여 실행(Execute)클리핑/클램핑(Clipping) 및 렌더링(Render).
FInstanceCullingIntermediate CullingIntermediate;
private:
FInstanceCullingManagerResources& Resources;
TArray<Nanite::FPackedView> CullingViews;
bool bIsEnabled;
(....)
};다음으로, FInstanceCullingManager::CullInstances의 코드를 분석합니다:
// Engine\Source\Runtime\Renderer\Private\InstanceCulling\InstanceCullingManager.cpp
void FInstanceCullingManager::CullInstances(FRDGBuilder& GraphBuilder, FGPUScene& GPUScene)
{
#if GPUCULL_TODO
// 페치/가져오기(Fetch)뷰(View) 및 인스턴스 개수 .
int32 NumViews = CullingViews.Num();
int32 NumInstances = GPUScene.InstanceDataAllocator.GetMaxSize();
RDG_EVENT_SCOPE(GraphBuilder, "CullInstances [%d Views X %d Instances]", NumViews, NumInstances);
(......)
TArray<uint32> NullArray;
NullArray.AddZeroed(1);
// 초기화(Initialize)클리핑/클램핑(Clipping) 내에서 데이터 CullingIntermediate.
CullingIntermediate.InstanceIdOutOffsetBuffer = CreateStructuredBuffer(GraphBuilder, TEXT("InstanceCulling.OutputOffsetBufferOut"), NullArray);
int32 NumInstanceFlagWords = FMath::DivideAndRoundUp(NumInstances, int32(sizeof(uint32) * 8));
CullingIntermediate.NumInstances = NumInstances;
CullingIntermediate.NumViews = NumViews;
if (NumInstances && NumViews) // 뷰(View)개수 및 인스턴스 개수 0GPU클리핑/클램핑(Clipping).
{
// 로 개 뷰(View)의 개 인스턴스 생성(Create)개 버퍼 개 ,
CullingIntermediate.VisibleInstanceFlags = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), NumInstanceFlagWords * NumViews), TEXT("InstanceCulling.VisibleInstanceFlags"));
FRDGBufferUAVRef VisibleInstanceFlagsUAV = GraphBuilder.CreateUAV(CullingIntermediate.VisibleInstanceFlags);
if (CVarCullInstances.GetValueOnRenderThread() != 0)
{
// UAV.
AddClearUAVPass(GraphBuilder, VisibleInstanceFlagsUAV, 0);
// 처리(Process)클리핑/클램핑(Clipping)인스턴스 CS의 파라미터 .
FCullInstancesCs::FParameters* PassParameters = GraphBuilder.AllocParameters<FCullInstancesCs::FParameters>();
// 로부터 GPUScene페치/가져오기(Fetch)인스턴스 및 데이터 .
PassParameters->GPUSceneInstanceSceneData = GPUScene.InstanceDataBuffer.SRV;
PassParameters->GPUScenePrimitiveSceneData = GPUScene.PrimitiveBuffer.SRV;
PassParameters->InstanceDataSOAStride = GPUScene.InstanceDataSOAStride;
PassParameters->NumInstances = NumInstances;
PassParameters->NumInstanceFlagWords = NumInstanceFlagWords;
// GPUView의 타입/유형 는 Nanite::FPackedView.
// GPUInViews의 타입/유형 는 StructuredBuffer< Nanite::FPackedView >.
PassParameters->InViews = GraphBuilder.CreateSRV(CreateStructuredBuffer(GraphBuilder, TEXT("InstanceCulling.CullingViews"), CullingViews));
PassParameters->NumViews = NumViews;
// 저장(Store)결과 의 버퍼 .
PassParameters->InstanceVisibilityFlagsOut = VisibleInstanceFlagsUAV;
// CS을(를) 활용하여 의 는 FCullInstancesCs, .
auto ComputeShader = GetGlobalShaderMap(GMaxRHIFeatureLevel)->GetShader<FCullInstancesCs>();
// 추가(Add)클리핑/클램핑(Clipping)의 CS Pass.
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("CullInstancesCs"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(NumInstances, FCullInstancesCs::NumThreadsPerGroup)
);
}
else // 뷰(View)개수 및 인스턴스 개수 는 0
{
// .
AddClearUAVPass(GraphBuilder, VisibleInstanceFlagsUAV, 0xFFFFFFFF);
}
}
#endif // GPUCULL_TODO
}위 로직은 클리핑 셰이더 FCullInstancesCs의 매개변수를 구성하고 FComputeShaderUtils::AddPass를 호출하여 클리핑 작업을 수행하는 것입니다. FCullInstancesCs의 코드를 계속 분석해 보겠습니다.
// Engine\Source\Runtime\Renderer\Private\InstanceCulling\InstanceCullingManager.cpp
class FCullInstancesCs : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FCullInstancesCs);
SHADER_USE_PARAMETER_STRUCT(FCullInstancesCs, FGlobalShader)
public:
static constexpr int32 NumThreadsPerGroup = 64;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return UseGPUScene(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("INDIRECT_ARGS_NUM_WORDS"), FInstanceCullingContext::IndirectArgsNumWords);
OutEnvironment.SetDefine(TEXT("VF_SUPPORTS_PRIMITIVE_SCENE_DATA"), 1);
OutEnvironment.SetDefine(TEXT("USE_GLOBAL_GPU_SCENE_DATA"), 1);
OutEnvironment.SetDefine(TEXT("NUM_THREADS_PER_GROUP"), NumThreadsPerGroup);
OutEnvironment.SetDefine(TEXT("NANITE_MULTI_VIEW"), 1);
}
// 선언 셰이딩 을(를) 활용하여 까지 의 파라미터 .
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_SRV(StructuredBuffer<float4>, GPUSceneInstanceSceneData)
SHADER_PARAMETER_SRV(StructuredBuffer<float4>, GPUScenePrimitiveSceneData)
SHADER_PARAMETER(uint32, InstanceDataSOAStride)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer< Nanite::FPackedView >, InViews)
// 저장(Store)결과 의 버퍼 .
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, InstanceVisibilityFlagsOut)
SHADER_PARAMETER(int32, NumInstances)
SHADER_PARAMETER(int32, NumInstanceFlagWords)
SHADER_PARAMETER(int32, NumViews)
END_SHADER_PARAMETER_STRUCT()
};
// 구현 셰이딩 .
IMPLEMENT_GLOBAL_SHADER(FCullInstancesCs, "/Engine/Private/InstanceCulling/CullInstances.usf", "CullInstancesCs", SF_Compute);매크로를 구현하기 위한 위의 마지막 문장은 FCullInstancesCs가 호출하는 셰이더 코드 파일이 CullInstances.usf임을 보여줍니다. 분석:
// Engine\Shaders\Private\InstanceCulling\CullInstances.usf
#include "../Common.ush"
#include "../SceneData.ush"
#include "../Nanite/NaniteDataDecode.ush"
#include "../Nanite/HZBCull.ush"
RWStructuredBuffer<uint> InstanceVisibilityFlagsOut;
uint NumInstances;
uint NumInstanceFlagWords;
uint NumViews;
uint InstanceDataSOAStride;
// 클리핑/클램핑(Clipping)인스턴스 .
[numthreads(NUM_THREADS_PER_GROUP, 1, 1)]
void CullInstancesCs(uint InstanceId : SV_DispatchThreadID)
{
// InstanceId.
if (InstanceId >= NumInstances)
{
return;
}
const bool bNearClip = true;
// 압축해제/언패킹(Unpacking)Instance데이터 Mask 및 Offset.
FInstanceSceneData InstanceData = GetInstanceData(InstanceId, InstanceDataSOAStride);
uint WordMask = 1U << (InstanceId % 32U);
uint InstanceWordOffset = InstanceId / 32U;
// 는 유효(Valid): PrimitiveId는 최댓값(Max)로컬 바운딩 박스(AABB)로 0.
bool bIsValid = InstanceData.PrimitiveId != 0xFFFFFFFFu && dot(InstanceData.LocalBoundsExtent, InstanceData.LocalBoundsExtent) > 0.0f;
// 순회(Traverse/Iterate)view, 개 view의 프러스텀/프러스텀 및 인스턴스 의 바운딩 박스(AABB)교차(Intersection).
for (uint ViewId = 0; ViewId < NumViews; ++ViewId)
{
uint Flag = WordMask;
if (bIsValid)
{
FNaniteView NaniteView = GetNaniteView(ViewId);
// 계산/산출(Calculate)로컬 까지 클리핑/클램핑(Clipping)의 변환(Transform)모멘트(Moment).
float4x4 LocalToTranslatedWorld = InstanceData.LocalToWorld;
LocalToTranslatedWorld[3].xyz += NaniteView.PreViewTranslation.xyz;
float4x4 LocalToClip = mul(LocalToTranslatedWorld, NaniteView.TranslatedWorldToClip);
// 및 프러스텀/프러스텀 교차(Intersection)검사/감지(Detect).
FFrustumCullData Cull = BoxCullFrustum(InstanceData.LocalBoundsCenter, InstanceData.LocalBoundsExtent, LocalToClip, bNearClip, false);
if (!Cull.bIsVisible)
{
Flag = 0U;
}
}
// 인스턴스 , 설정(Set)InstanceVisibilityFlagsOut위치 의 로 1.
if (Flag != 0U)
{
uint WordOffset = NumInstanceFlagWords * ViewId + InstanceWordOffset;
// ※ 주의: CS호출(Call)원자적 InterlockXXX인터페이스 , 조건 .
InterlockedOr(InstanceVisibilityFlagsOut[WordOffset], Flag);
}
}
}VisibleInstanceFlags 가시성 데이터를 사용하면 후속 패스 드로잉에서 이를 기반으로 드로잉 지침과 드로잉 매개변수를 동적으로 생성하여 GPU 클리핑 및 구동 렌더링 파이프라인을 달성할 수 있습니다.
6.4.3.5 나나이트 래스터라이제이션
Nanite 래스터라이제이션는 주로 각 View에 대한 FCullingContext 인스턴스를 빌드하고 초기화한 다음 CullRasterize를 호출하고 래스터라이제이션 결과를 저장하고 HZB를 빌드합니다. 키 코드는 다음과 같습니다.
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
const FViewInfo& View = Views[ViewIndex];
// 초기화(Initialize)클리핑/클램핑(Clipping).
Nanite::FCullingContext CullingContext = Nanite::InitCullingContext(
GraphBuilder, *Scene,
!bIsEarlyDepthComplete ? View.PrevViewInfo.NaniteHZB : View.PrevViewInfo.HZB,
View.ViewRect,
bTwoPassOcclusion, bUpdateStreaming, bSupportsMultiplePasses, bForceHWRaster, bPrimaryContext, bDiscardNonMoving);
static FString EmptyFilterName = TEXT("");
const bool bExtractStats = Nanite::IsStatFilterActive(EmptyFilterName);
Nanite::FPackedView PackedView = Nanite::CreatePackedViewFromViewInfo(View, RasterTextureSize, VIEW_FLAG_HZBTEST, 3);
// 클리핑/클램핑(Clipping)의 래스터라이제이션 .
Nanite::CullRasterize(GraphBuilder, *Scene, { PackedView }, CullingContext, RasterContext, RasterState, nullptr, bExtractStats);
Nanite::FRasterResults& RasterResults = NaniteRasterResults[ViewIndex];
// 렌더링(Render)렌더링(Render).
if (bNeedsPrePass)
{
Nanite::EmitDepthTargets(GraphBuilder, *Scene, Views[ViewIndex], CullingContext.SOAStrides, CullingContext.VisibleClustersSWHW, CullingContext.ViewsBuffer, SceneTextures.Depth.Target, RasterContext.VisBuffer64,RasterResults.MaterialDepth,RasterResults.NaniteMask,RasterResults.VelocityBuffer,bNeedsPrePass);
}
// HZB.
if (!bIsEarlyDepthComplete && bTwoPassOcclusion && View.ViewState)
{
RDG_EVENT_SCOPE(GraphBuilder, "Nanite::BuildHZB");
FRDGTextureRef SceneDepth = SystemTextures.Black;
FRDGTextureRef GraphHZB = nullptr;
BuildHZBFurthest(GraphBuilder,SceneDepth, RasterContext.VisBuffer64, PrimaryViewRect, FeatureLevel, ShaderPlatform, TEXT("Nanite.HZB"), &GraphHZB );
GraphBuilder.QueueTextureExtraction( GraphHZB, &View.ViewState->PrevFrameViewInfo.NaniteHZB );
}
// 래스터라이제이션 및 클리핑/클램핑(Clipping)결과 .
Nanite::ExtractResults(GraphBuilder, CullingContext, RasterContext, RasterResults);
}Nanite::CullRasterize의 코드를 분석하는 데 중점을 두겠습니다.
// Engine\Source\Runtime\Renderer\Private\Nanite\NaniteRender.cpp
void CullRasterize(
FRDGBuilder& GraphBuilder,
const FScene& Scene,
const TArray<FPackedView, SceneRenderingAllocator>& Views,
uint32 NumPrimaryViews, // Number of non-mip views
FCullingContext& CullingContext,
const FRasterContext& RasterContext,
const FRasterState& RasterState,
const TArray<FInstanceDraw, SceneRenderingAllocator>* OptionalInstanceDraws,
// VirtualShadowMapArray is the supplier of virtual to physical translation, probably could abstract this a bit better,
FVirtualShadowMapArray* VirtualShadowMapArray,
bool bExtractStats
)
{
// 만약 뷰(View), 까지 개 Pass래스터라이제이션 . 오직 depth-only렌더링(Render).
if (Views.Num() > MAX_VIEWS_PER_CULL_RASTERIZE_PASS)
{
CullRasterizeMultiPass(GraphBuilder, Scene, Views, NumPrimaryViews, CullingContext, RasterContext, RasterState, OptionalInstanceDraws, VirtualShadowMapArray, bExtractStats);
return;
}
RDG_EVENT_SCOPE(GraphBuilder, "Nanite::CullRasterize");
(......)
// 생성(Create)뷰(View)의 구조체 버퍼 .
{
const uint32 ViewsBufferElements = FMath::RoundUpToPowerOfTwo(Views.Num());
CullingContext.ViewsBuffer = CreateStructuredBuffer(GraphBuilder, TEXT("Nanite.Views"), Views.GetTypeSize(), ViewsBufferElements, Views.GetData(), Views.Num() * Views.GetTypeSize());
}
// 처리(Process)클리핑/클램핑(Clipping)의 구조체 버퍼 .
if (OptionalInstanceDraws)
{
const uint32 InstanceDrawsBufferElements = FMath::RoundUpToPowerOfTwo(OptionalInstanceDraws->Num());
CullingContext.InstanceDrawsBuffer = CreateStructuredBuffer
(
GraphBuilder,
TEXT("Nanite.InstanceDraws"),
OptionalInstanceDraws->GetTypeSize(),
InstanceDrawsBufferElements,
OptionalInstanceDraws->GetData(),
OptionalInstanceDraws->Num() * OptionalInstanceDraws->GetTypeSize()
);
CullingContext.NumInstancesPreCull = OptionalInstanceDraws->Num();
}
else
{
CullingContext.InstanceDrawsBuffer = nullptr;
CullingContext.NumInstancesPreCull = Scene.GPUScene.InstanceDataAllocator.GetMaxSize();
}
(......)
// 클리핑/클램핑(Clipping)파라미터 .
FCullingParameters CullingParameters;
{
CullingParameters.InViews = GraphBuilder.CreateSRV(CullingContext.ViewsBuffer);
CullingParameters.NumViews = Views.Num();
CullingParameters.NumPrimaryViews = NumPrimaryViews;
CullingParameters.DisocclusionLodScaleFactor = GNaniteDisocclusionHack ? 0.01f : 1.0f; // TODO: Get rid of this hack
CullingParameters.HZBTexture = RegisterExternalTextureWithFallback(GraphBuilder, CullingContext.PrevHZB, GSystemTextures.BlackDummy);
CullingParameters.HZBSize = CullingContext.PrevHZB ? CullingContext.PrevHZB->GetDesc().Extent : FVector2D(0.0f);
CullingParameters.HZBSampler = TStaticSamplerState< SF_Point, AM_Clamp, AM_Clamp, AM_Clamp >::GetRHI();
CullingParameters.SOAStrides = CullingContext.SOAStrides;
CullingParameters.MaxCandidateClusters = Nanite::FGlobalResources::GetMaxCandidateClusters();
CullingParameters.MaxVisibleClusters = Nanite::FGlobalResources::GetMaxVisibleClusters();
CullingParameters.RenderFlags = CullingContext.RenderFlags;
CullingParameters.DebugFlags = CullingContext.DebugFlags;
CullingParameters.CompactedViewInfo = nullptr;
CullingParameters.CompactedViewsAllocation = nullptr;
}
FVirtualTargetParameters VirtualTargetParameters;
// 처리(Process)VSM(그림자(Shadow))배열 .
if (VirtualShadowMapArray)
{
VirtualTargetParameters.VirtualShadowMap = VirtualShadowMapArray->GetUniformBuffer(GraphBuilder);
VirtualTargetParameters.PageFlags = GraphBuilder.CreateSRV(VirtualShadowMapArray->PageFlagsRDG, PF_R32_UINT);
VirtualTargetParameters.HPageFlags = GraphBuilder.CreateSRV(VirtualShadowMapArray->HPageFlagsRDG, PF_R32_UINT);
VirtualTargetParameters.PageRectBounds = GraphBuilder.CreateSRV(VirtualShadowMapArray->PageRectBoundsRDG);
// 만약 이전 프레임(Previous Frame)의 HZB, 이전 프레임(Previous Frame)의 Page테이블 .
FRDGBufferRef HZBPageTableRDG = VirtualShadowMapArray->PageTableRDG;
if (CullingContext.PrevHZB)
{
check( VirtualShadowMapArray->CacheManager );
TRefCountPtr<FRDGPooledBuffer> HZBPageTable = VirtualShadowMapArray->CacheManager->PrevBuffers.PageTable;
check( HZBPageTable );
HZBPageTableRDG = GraphBuilder.RegisterExternalBuffer( HZBPageTable, TEXT( "Shadow.Virtual.HZBPageTable" ) );
}
VirtualTargetParameters.ShadowHZBPageTable = GraphBuilder.CreateSRV( HZBPageTableRDG, PF_R32_UINT );
}
// 처리(Process)GPUScene데이터 .
FGPUSceneParameters GPUSceneParameters;
GPUSceneParameters.GPUSceneInstanceSceneData = Scene.GPUScene.InstanceDataBuffer.SRV;
GPUSceneParameters.GPUScenePrimitiveSceneData = Scene.GPUScene.PrimitiveBuffer.SRV;
GPUSceneParameters.GPUSceneFrameNumber = Scene.GPUScene.GetSceneFrameNumber();
// 클리핑/클램핑(Clipping)VSM.
if (VirtualShadowMapArray && CVarCompactVSMViews.GetValueOnRenderThread() != 0)
{
RDG_GPU_STAT_SCOPE(GraphBuilder, NaniteInstanceCullVSM);
// 압축/패킹(Packing)뷰(View)의 (의 )mip뷰(View), GPU,이므로 GPUmippage。
const uint32 ViewsBufferElements = FMath::RoundUpToPowerOfTwo(Views.Num());
FRDGBufferRef CompactedViews = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FPackedView), ViewsBufferElements), TEXT("Shadow.Virtual.CompactedViews"));
FRDGBufferRef CompactedViewInfo = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateStructuredDesc(sizeof(FCompactedViewInfo), Views.Num()), TEXT("Shadow.Virtual.CompactedViewInfo"));
const static uint32 TheZeros[2] = { 0U, 0U };
FRDGBufferRef CompactedViewsAllocation = CreateStructuredBuffer(GraphBuilder, TEXT("Shadow.Virtual.CompactedViewsAllocation"), sizeof(uint32), 2, TheZeros, sizeof(TheZeros), ERDGInitialDataFlags::NoCopy);
{
FCompactViewsVSM_CS::FParameters* PassParameters = GraphBuilder.AllocParameters< FCompactViewsVSM_CS::FParameters >();
PassParameters->GPUSceneParameters = GPUSceneParameters;
PassParameters->CullingParameters = CullingParameters;
PassParameters->VirtualShadowMap = VirtualTargetParameters;
PassParameters->CompactedViewsOut = GraphBuilder.CreateUAV(CompactedViews);
PassParameters->CompactedViewInfoOut = GraphBuilder.CreateUAV(CompactedViewInfo);
PassParameters->CompactedViewsAllocationOut = GraphBuilder.CreateUAV(CompactedViewsAllocation);
auto ComputeShader = CullingContext.ShaderMap->GetShader<FCompactViewsVSM_CS>();
// 을(를) 활용하여 CS압축/패킹(Packing)클리핑/클램핑(Clipping)VSM.
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("CompactViewsVSM"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(NumPrimaryViews, 64)
);
}
// 을(를) 활용하여 압축/패킹(Packing)의 뷰(View)의 정보 .
CullingParameters.InViews = GraphBuilder.CreateSRV(CompactedViews);
CullingContext.ViewsBuffer = CompactedViews;
CullingParameters.CompactedViewInfo = GraphBuilder.CreateSRV(CompactedViewInfo);
CullingParameters.CompactedViewsAllocation = GraphBuilder.CreateSRV(CompactedViewsAllocation);
}
// 초기화(Initialize)클리핑/클램핑(Clipping)의 파라미터 .
{
FInitArgs_CS::FParameters* PassParameters = GraphBuilder.AllocParameters< FInitArgs_CS::FParameters >();
PassParameters->RenderFlags = CullingParameters.RenderFlags;
PassParameters->OutMainAndPostPassPersistentStates = GraphBuilder.CreateUAV( CullingContext.MainAndPostPassPersistentStates );
PassParameters->InOutMainPassRasterizeArgsSWHW = GraphBuilder.CreateUAV( CullingContext.MainRasterizeArgsSWHW );
uint32 ClampedDrawPassIndex = FMath::Min(CullingContext.DrawPassIndex, 2u);
if (CullingContext.bTwoPassOcclusion)
{
PassParameters->OutOccludedInstancesArgs = GraphBuilder.CreateUAV( CullingContext.OccludedInstancesArgs );
PassParameters->InOutPostPassRasterizeArgsSWHW = GraphBuilder.CreateUAV( CullingContext.PostRasterizeArgsSWHW );
}
if (CullingContext.RenderFlags & RENDER_FLAG_HAVE_PREV_DRAW_DATA)
{
PassParameters->InOutTotalPrevDrawClusters = GraphBuilder.CreateUAV(CullingContext.TotalPrevDrawClustersBuffer);
}
else
{
// Use any UAV just to keep render graph happy that something is bound, but the shader doesn't actually touch this.
PassParameters->InOutTotalPrevDrawClusters = PassParameters->OutMainAndPostPassPersistentStates;
}
FInitArgs_CS::FPermutationDomain PermutationVector;
PermutationVector.Set<FInitArgs_CS::FOcclusionCullingDim>( CullingContext.bTwoPassOcclusion );
PermutationVector.Set<FInitArgs_CS::FDrawPassIndexDim>( ClampedDrawPassIndex );
auto ComputeShader = CullingContext.ShaderMap->GetShader< FInitArgs_CS >( PermutationVector );
// 는 을(를) 활용하여 CS초기화(Initialize)파라미터 .
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME( "InitArgs" ),
ComputeShader,
PassParameters,
FIntVector( 1, 1, 1 )
);
}
// 할당(Allocate)버퍼 , 오직 CullRasterize.
FRDGBufferRef MainCandidateNodesAndClustersBuffer = nullptr;
FRDGBufferRef PostCandidateNodesAndClustersBuffer = nullptr;
AllocateCandidateBuffers(GraphBuilder, CullingContext.ShaderMap, &MainCandidateNodesAndClustersBuffer, CullingContext.bTwoPassOcclusion ? &PostCandidateNodesAndClustersBuffer : nullptr);
// 인스턴스 레벨 단계 및 Cluster클리핑/클램핑(Clipping), 차폐/오클루전 Pass 또는 차폐/오클루전 Pass.
AddPass_InstanceHierarchyAndClusterCull(
GraphBuilder,
Scene,
CullingParameters,
Views,
NumPrimaryViews,
CullingContext,
RasterContext,
RasterState,
GPUSceneParameters,
MainCandidateNodesAndClustersBuffer,
PostCandidateNodesAndClustersBuffer,
CullingContext.bTwoPassOcclusion ? CULLING_PASS_OCCLUSION_MAIN : CULLING_PASS_NO_OCCLUSION,
VirtualShadowMapArray,
VirtualTargetParameters
);
// 래스터라이제이션 .
AddPass_Rasterize(
GraphBuilder,
Views,
RasterContext,
RasterState,
CullingContext.SOAStrides,
CullingContext.RenderFlags,
CullingContext.ViewsBuffer,
CullingContext.VisibleClustersSWHW,
nullptr,
CullingContext.SafeMainRasterizeArgsSWHW,
CullingContext.TotalPrevDrawClustersBuffer,
GPUSceneParameters,
true,
VirtualShadowMapArray,
VirtualTargetParameters
);
// 차폐/오클루전 Pass. 검사/감지(Detect)이전 프레임(Previous Frame)의 인스턴스 및 Cluster, 만약 , 렌더링(Render).
if (CullingContext.bTwoPassOcclusion)
{
// 을(를) 활용하여 이전 프레임(Previous Frame)의 차폐/오클루전 개 의 HZB,로써 회 검사/감지(Detect)의 차폐/오클루전 。
{
RDG_EVENT_SCOPE(GraphBuilder, "BuildPreviousOccluderHZB");
FSceneTextureParameters SceneTextures = GetSceneTextureParameters(GraphBuilder);
FRDGTextureRef SceneDepth = SceneTextures.SceneDepthTexture;
FRDGTextureRef RasterizedDepth = RasterContext.VisBuffer64;
if( RasterContext.RasterTechnique == ERasterTechnique::DepthOnly )
{
SceneDepth = GraphBuilder.RegisterExternalTexture( GSystemTextures.BlackDummy );
RasterizedDepth = RasterContext.DepthBuffer;
}
FRDGTextureRef OutFurthestHZBTexture;
FIntRect ViewRect(0, 0, RasterContext.TextureSize.X, RasterContext.TextureSize.Y);
if (Views.Num() == 1)
{
ViewRect = FIntRect(Views[0].ViewRect.X, Views[0].ViewRect.Y, Views[0].ViewRect.Z, Views[0].ViewRect.W);
}
// HZB.
BuildHZBFurthest(
GraphBuilder,
SceneDepth,
RasterizedDepth,
CullingContext.HZBBuildViewRect,
Scene.GetFeatureLevel(),
Scene.GetShaderPlatform(),
TEXT("Nanite.PreviousOccluderHZB"),
/* OutFurthestHZBTexture = */ &OutFurthestHZBTexture);
CullingParameters.HZBTexture = OutFurthestHZBTexture;
CullingParameters.HZBSize = CullingParameters.HZBTexture->Desc.Extent;
}
// Pass.
AddPass_InstanceHierarchyAndClusterCull(
GraphBuilder,
Scene,
CullingParameters,
Views,
NumPrimaryViews,
CullingContext,
RasterContext,
RasterState,
GPUSceneParameters,
MainCandidateNodesAndClustersBuffer,
PostCandidateNodesAndClustersBuffer,
CULLING_PASS_OCCLUSION_POST,
VirtualShadowMapArray,
VirtualTargetParameters
);
// 렌더링(Render)Pass.
AddPass_Rasterize(
GraphBuilder,
Views,
RasterContext,
RasterState,
CullingContext.SOAStrides,
CullingContext.RenderFlags,
CullingContext.ViewsBuffer,
CullingContext.VisibleClustersSWHW,
CullingContext.MainRasterizeArgsSWHW,
CullingContext.SafePostRasterizeArgsSWHW,
CullingContext.TotalPrevDrawClustersBuffer,
GPUSceneParameters,
false,
VirtualShadowMapArray,
VirtualTargetParameters
);
}
if (RasterContext.RasterTechnique != ERasterTechnique::DepthOnly)
{
// 개 Pass렌더링(Render)의 Cluster 및 개수 및 뎁스 렌더링(Render).
CullingContext.DrawPassIndex++;
CullingContext.RenderFlags |= RENDER_FLAG_HAVE_PREV_DRAW_DATA;
}
(......)
}AddPass_InstanceHierarchyAndClusterCull 및 AddPass_Rasterize의 두 인터페이스에 중점을 두겠습니다. 첫 번째는 AddPass_InstanceHierarchyAndClusterCull입니다.
void AddPass_InstanceHierarchyAndClusterCull(
FRDGBuilder& GraphBuilder,
const FScene& Scene,
const FCullingParameters& CullingParameters,
const TArray<FPackedView, SceneRenderingAllocator>& Views,
const uint32 NumPrimaryViews,
const FCullingContext& CullingContext,
const FRasterContext& RasterContext,
const FRasterState& RasterState,
const FGPUSceneParameters &GPUSceneParameters,
FRDGBufferRef MainCandidateNodesAndClusters,
FRDGBufferRef PostCandidateNodesAndClusters,
uint32 CullingPass,
FVirtualShadowMapArray *VirtualShadowMapArray,
FVirtualTargetParameters &VirtualTargetParameters
)
{
(......)
const bool bMultiView = Views.Num() > 1 || VirtualShadowMapArray != nullptr;
if (VirtualShadowMapArray)
{
(......)
}
// 처리(Process)인스턴스 클리핑/클램핑(Clipping).
else if (CullingContext.NumInstancesPreCull > 0 || CullingPass == CULLING_PASS_OCCLUSION_POST)
{
RDG_GPU_STAT_SCOPE( GraphBuilder, NaniteInstanceCull );
// 처리(Process)인스턴스 클리핑/클램핑(Clipping)CS의 파라미터 .
FInstanceCull_CS::FParameters* PassParameters = GraphBuilder.AllocParameters< FInstanceCull_CS::FParameters >();
PassParameters->NumInstances = CullingContext.NumInstancesPreCull;
PassParameters->MaxNodes = Nanite::FGlobalResources::GetMaxNodes();
PassParameters->ImposterMaxPixels = GNaniteImposterMaxPixels;
PassParameters->GPUSceneParameters = GPUSceneParameters;
PassParameters->RasterParameters = RasterContext.Parameters;
PassParameters->CullingParameters = CullingParameters;
const ERasterTechnique Technique = RasterContext.RasterTechnique;
PassParameters->OnlyCastShadowsPrimitives = Technique == ERasterTechnique::DepthOnly ? 1 : 0;
PassParameters->ImposterAtlas = Nanite::GStreamingManager.GetRootPagesSRV();
PassParameters->OutMainAndPostPassPersistentStates = GraphBuilder.CreateUAV( CullingContext.MainAndPostPassPersistentStates );
if (CullingContext.StatsBuffer)
{
PassParameters->OutStatsBuffer = GraphBuilder.CreateUAV(CullingContext.StatsBuffer);
}
// 에 따라 의 클리핑/클램핑(Clipping)설정(Set)의 파라미터 .
if( CullingPass == CULLING_PASS_NO_OCCLUSION )
{
if( CullingContext.InstanceDrawsBuffer )
{
PassParameters->InInstanceDraws = GraphBuilder.CreateSRV( CullingContext.InstanceDrawsBuffer );
}
PassParameters->OutCandidateNodesAndClusters = GraphBuilder.CreateUAV( MainCandidateNodesAndClusters);
}
else if( CullingPass == CULLING_PASS_OCCLUSION_MAIN )
{
PassParameters->OutOccludedInstances = GraphBuilder.CreateUAV( CullingContext.OccludedInstances );
PassParameters->OutOccludedInstancesArgs = GraphBuilder.CreateUAV( CullingContext.OccludedInstancesArgs );
PassParameters->OutCandidateNodesAndClusters = GraphBuilder.CreateUAV( MainCandidateNodesAndClusters );
}
else
{
PassParameters->InInstanceDraws = GraphBuilder.CreateSRV( CullingContext.OccludedInstances );
PassParameters->InOccludedInstancesArgs = GraphBuilder.CreateSRV( CullingContext.OccludedInstancesArgs );
PassParameters->OutCandidateNodesAndClusters = GraphBuilder.CreateUAV( PostCandidateNodesAndClusters);
}
check(CullingContext.ViewsBuffer);
// 처리(Process)파라미터 .
const uint32 InstanceCullingPass = CullingContext.InstanceDrawsBuffer != nullptr ? CULLING_PASS_EXPLICIT_LIST : CullingPass;
FInstanceCull_CS::FPermutationDomain PermutationVector;
PermutationVector.Set<FInstanceCull_CS::FCullingPassDim>(InstanceCullingPass);
PermutationVector.Set<FInstanceCull_CS::FMultiViewDim>(bMultiView);
PermutationVector.Set<FInstanceCull_CS::FNearClipDim>(RasterState.bNearClip);
PermutationVector.Set<FInstanceCull_CS::FDebugFlagsDim>(CullingContext.DebugFlags != 0);
PermutationVector.Set<FInstanceCull_CS::FRasterTechniqueDim>(int32(RasterContext.RasterTechnique));
auto ComputeShader = CullingContext.ShaderMap->GetShader<FInstanceCull_CS>(PermutationVector);
// Pass인스턴스 클리핑/클램핑(Clipping).
if( InstanceCullingPass == CULLING_PASS_OCCLUSION_POST )
{
PassParameters->IndirectArgs = CullingContext.OccludedInstancesArgs;
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME( "Post Pass: InstanceCull" ),
ComputeShader,
PassParameters,
PassParameters->IndirectArgs,
0
);
}
else // 채널/패스(Pass)인스턴스 클리핑/클램핑(Clipping).
{
FComputeShaderUtils::AddPass(
GraphBuilder,
InstanceCullingPass == CULLING_PASS_OCCLUSION_MAIN ? RDG_EVENT_NAME( "Main Pass: InstanceCull" ) :
InstanceCullingPass == CULLING_PASS_NO_OCCLUSION ? RDG_EVENT_NAME( "Main Pass: InstanceCull - No occlusion" ) :
RDG_EVENT_NAME( "Main Pass: InstanceCull - Explicit list" ),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(CullingContext.NumInstancesPreCull, 64)
);
}
}
// Cluster클리핑/클램핑(Clipping).
{
RDG_GPU_STAT_SCOPE(GraphBuilder, NaniteClusterCull);
FPersistentClusterCull_CS::FParameters* PassParameters = GraphBuilder.AllocParameters< FPersistentClusterCull_CS::FParameters >();
// Cluster클리핑/클램핑(Clipping)을(를) 활용하여 까지 GPUScene、GStreamingManager파라미터 。
PassParameters->GPUSceneParameters = GPUSceneParameters;
PassParameters->CullingParameters = CullingParameters;
PassParameters->MaxNodes = Nanite::FGlobalResources::GetMaxNodes();
PassParameters->ClusterPageHeaders = Nanite::GStreamingManager.GetClusterPageHeadersSRV();
PassParameters->ClusterPageData = Nanite::GStreamingManager.GetClusterPageDataSRV();
PassParameters->HierarchyBuffer = Nanite::GStreamingManager.GetHierarchySRV();
check(CullingContext.DrawPassIndex == 0 || CullingContext.RenderFlags & RENDER_FLAG_HAVE_PREV_DRAW_DATA); // sanity check
// 처리(Process)이전 프레임(Previous Frame)데이터 .
if (CullingContext.RenderFlags & RENDER_FLAG_HAVE_PREV_DRAW_DATA)
{
PassParameters->InTotalPrevDrawClusters = GraphBuilder.CreateSRV(CullingContext.TotalPrevDrawClustersBuffer);
}
else
{
FRDGBufferRef Dummy = GraphBuilder.RegisterExternalBuffer(Nanite::GGlobalResources.GetStructureBufferStride8(), TEXT("Nanite.StructuredBufferStride8"));
PassParameters->InTotalPrevDrawClusters = GraphBuilder.CreateSRV(Dummy);
}
PassParameters->MainAndPostPassPersistentStates = GraphBuilder.CreateUAV( CullingContext.MainAndPostPassPersistentStates );
// 포인트 및 Cluster.
if( CullingPass == CULLING_PASS_NO_OCCLUSION || CullingPass == CULLING_PASS_OCCLUSION_MAIN )
{
PassParameters->InOutCandidateNodesAndClusters = GraphBuilder.CreateUAV( MainCandidateNodesAndClusters );
PassParameters->VisibleClustersArgsSWHW = GraphBuilder.CreateUAV( CullingContext.MainRasterizeArgsSWHW );
if( CullingPass == CULLING_PASS_OCCLUSION_MAIN )
{
PassParameters->OutOccludedNodesAndClusters = GraphBuilder.CreateUAV( PostCandidateNodesAndClusters );
}
}
else
{
PassParameters->InOutCandidateNodesAndClusters = GraphBuilder.CreateUAV( PostCandidateNodesAndClusters );
PassParameters->OffsetClustersArgsSWHW = GraphBuilder.CreateSRV( CullingContext.MainRasterizeArgsSWHW );
PassParameters->VisibleClustersArgsSWHW = GraphBuilder.CreateUAV( CullingContext.PostRasterizeArgsSWHW );
}
// 출력결과 UAV, Cluster 및 .
PassParameters->OutVisibleClustersSWHW = GraphBuilder.CreateUAV( CullingContext.VisibleClustersSWHW );
PassParameters->OutStreamingRequests = GraphBuilder.CreateUAV( CullingContext.StreamingRequests );
if (VirtualShadowMapArray)
{
PassParameters->VirtualShadowMap = VirtualTargetParameters;
PassParameters->OutDynamicCasterFlags = GraphBuilder.CreateUAV(VirtualShadowMapArray->DynamicCasterPageFlagsRDG, PF_R32_UINT);
}
if (CullingContext.StatsBuffer)
{
PassParameters->OutStatsBuffer = GraphBuilder.CreateUAV(CullingContext.StatsBuffer);
}
PassParameters->LargePageRectThreshold = CVarLargePageRectThreshold.GetValueOnRenderThread();
check(CullingContext.ViewsBuffer);
// .
FPersistentClusterCull_CS::FPermutationDomain PermutationVector;
PermutationVector.Set<FPersistentClusterCull_CS::FCullingPassDim>(CullingPass);
PermutationVector.Set<FPersistentClusterCull_CS::FMultiViewDim>(bMultiView);
PermutationVector.Set<FPersistentClusterCull_CS::FNearClipDim>(RasterState.bNearClip);
PermutationVector.Set<FPersistentClusterCull_CS::FVirtualTextureTargetDim>(VirtualShadowMapArray != nullptr);
PermutationVector.Set<FPersistentClusterCull_CS::FClusterPerPageDim>(GNaniteClusterPerPage && VirtualShadowMapArray != nullptr);
PermutationVector.Set<FPersistentClusterCull_CS::FDebugFlagsDim>(CullingContext.DebugFlags != 0);
auto ComputeShader = CullingContext.ShaderMap->GetShader<FPersistentClusterCull_CS>(PermutationVector);
// CS Pass호출(Call).
FComputeShaderUtils::AddPass(
GraphBuilder,
CullingPass == CULLING_PASS_NO_OCCLUSION ? RDG_EVENT_NAME( "Main Pass: PersistentCull - No occlusion" ) :
CullingPass == CULLING_PASS_OCCLUSION_MAIN ? RDG_EVENT_NAME( "Main Pass: PersistentCull" ) :
RDG_EVENT_NAME( "Post Pass: PersistentCull" ),
ComputeShader,
PassParameters,
FIntVector(GRHIPersistentThreadGroupCount, 1, 1)
);
}
// 계산/산출(Calculate)래스터라이제이션 파라미터 , 로써 의 래스터라이제이션 채널/패스(Pass).
{
FCalculateSafeRasterizerArgs_CS::FParameters* PassParameters = GraphBuilder.AllocParameters< FCalculateSafeRasterizerArgs_CS::FParameters >();
const bool bPrevDrawData = (CullingContext.RenderFlags & RENDER_FLAG_HAVE_PREV_DRAW_DATA) != 0;
const bool bPostPass = (CullingPass == CULLING_PASS_OCCLUSION_POST) != 0;
if (bPrevDrawData)
{
PassParameters->InTotalPrevDrawClusters = GraphBuilder.CreateSRV(CullingContext.TotalPrevDrawClustersBuffer);
}
if (bPostPass)
{
PassParameters->OffsetClustersArgsSWHW = GraphBuilder.CreateSRV(CullingContext.MainRasterizeArgsSWHW);
PassParameters->InRasterizerArgsSWHW = GraphBuilder.CreateSRV(CullingContext.PostRasterizeArgsSWHW);
PassParameters->OutSafeRasterizerArgsSWHW = GraphBuilder.CreateUAV(CullingContext.SafePostRasterizeArgsSWHW);
}
else
{
PassParameters->InRasterizerArgsSWHW = GraphBuilder.CreateSRV(CullingContext.MainRasterizeArgsSWHW);
PassParameters->OutSafeRasterizerArgsSWHW = GraphBuilder.CreateUAV(CullingContext.SafeMainRasterizeArgsSWHW);
}
PassParameters->MaxVisibleClusters = Nanite::FGlobalResources::GetMaxVisibleClusters();
PassParameters->RenderFlags = CullingContext.RenderFlags;
FCalculateSafeRasterizerArgs_CS::FPermutationDomain PermutationVector;
PermutationVector.Set<FCalculateSafeRasterizerArgs_CS::FHasPrevDrawData>(bPrevDrawData);
PermutationVector.Set<FCalculateSafeRasterizerArgs_CS::FIsPostPass>(bPostPass);
auto ComputeShader = CullingContext.ShaderMap->GetShader< FCalculateSafeRasterizerArgs_CS >(PermutationVector);
FComputeShaderUtils::AddPass(
GraphBuilder,
bPostPass ? RDG_EVENT_NAME("Post Pass: CalculateSafeRasterizerArgs") : RDG_EVENT_NAME("Main Pass: CalculateSafeRasterizerArgs"),
ComputeShader,
PassParameters,
FIntVector(1, 1, 1)
);
}
}위에는 Compute Shader에 대한 여러 호출이 포함됩니다. 공간 제한으로 인해 셰이더 코드는 분석되지 않습니다. AddPass_Rasterize에 집중해 보겠습니다.
void AddPass_Rasterize(
FRDGBuilder& GraphBuilder,
const TArray<FPackedView, SceneRenderingAllocator>& Views,
const FRasterContext& RasterContext,
const FRasterState& RasterState,
FIntVector4 SOAStrides,
uint32 RenderFlags,
FRDGBufferRef ViewsBuffer,
FRDGBufferRef VisibleClustersSWHW,
FRDGBufferRef ClusterOffsetSWHW,
FRDGBufferRef IndirectArgs,
FRDGBufferRef TotalPrevDrawClustersBuffer,
const FGPUSceneParameters& GPUSceneParameters,
bool bMainPass,
FVirtualShadowMapArray* VirtualShadowMapArray,
FVirtualTargetParameters& VirtualTargetParameters
)
{
(......)
// 할당(Allocate)래스터라이제이션 파라미터 .
auto* RasterPassParameters = GraphBuilder.AllocParameters<FHWRasterizePS::FParameters>();
auto* CommonPassParameters = &RasterPassParameters->Common;
// 설정(Set)Cluster 및 .
CommonPassParameters->ClusterPageData = GStreamingManager.GetClusterPageDataSRV();
CommonPassParameters->ClusterPageHeaders = GStreamingManager.GetClusterPageHeadersSRV();
// 뷰(View)버퍼 데이터 .
if (ViewsBuffer)
{
CommonPassParameters->InViews = GraphBuilder.CreateSRV(ViewsBuffer);
}
// 드로우/렌더(Draw)파라미터 .
CommonPassParameters->GPUSceneParameters = GPUSceneParameters;
CommonPassParameters->RasterParameters = RasterContext.Parameters;
CommonPassParameters->VisualizeModeBitMask = RasterContext.VisualizeModeBitMask;
CommonPassParameters->SOAStrides = SOAStrides;
CommonPassParameters->MaxVisibleClusters = Nanite::FGlobalResources::GetMaxVisibleClusters();
CommonPassParameters->RenderFlags = RenderFlags;
if (RasterState.CullMode == CM_CCW)
{
CommonPassParameters->RenderFlags |= RENDER_FLAG_REVERSE_CULLING;
}
CommonPassParameters->VisibleClustersSWHW = GraphBuilder.CreateSRV(VisibleClustersSWHW);
if (VirtualShadowMapArray)
{
CommonPassParameters->VirtualShadowMap = VirtualTargetParameters;
}
if (!bMainPass)
{
CommonPassParameters->InClusterOffsetSWHW = GraphBuilder.CreateSRV(ClusterOffsetSWHW);
}
CommonPassParameters->IndirectArgs = IndirectArgs;
const bool bHavePrevDrawData = (RenderFlags & RENDER_FLAG_HAVE_PREV_DRAW_DATA);
if (bHavePrevDrawData)
{
CommonPassParameters->InTotalPrevDrawClusters = GraphBuilder.CreateSRV(TotalPrevDrawClustersBuffer);
}
const ERasterTechnique Technique = RasterContext.RasterTechnique;
const ERasterScheduling Scheduling = RasterContext.RasterScheduling;
const bool bNearClip = RasterState.bNearClip;
const bool bMultiView = Views.Num() > 1 || VirtualShadowMapArray != nullptr;
ERDGPassFlags ComputePassFlags = ERDGPassFlags::Compute;
// 만약 는 의 , 생성(Create)SkipBarrier플래그 의 UAV.
if (Scheduling == ERasterScheduling::HardwareAndSoftwareOverlap)
{
const auto CreateSkipBarrierUAV = [&](auto& InOutUAV)
{
if (InOutUAV)
{
// ERDGUnorderedAccessViewFlags::SkipBarrier플래그 .
InOutUAV = GraphBuilder.CreateUAV(InOutUAV->Desc, ERDGUnorderedAccessViewFlags::SkipBarrier);
}
};
// 생성(Create)SkipBarrier플래그 의 UAV, 로써 오버랩 .
CreateSkipBarrierUAV(CommonPassParameters->RasterParameters.OutDepthBuffer);
CreateSkipBarrierUAV(CommonPassParameters->RasterParameters.OutVisBuffer64);
CreateSkipBarrierUAV(CommonPassParameters->RasterParameters.OutDbgBuffer64);
CreateSkipBarrierUAV(CommonPassParameters->RasterParameters.OutDbgBuffer32);
CreateSkipBarrierUAV(CommonPassParameters->RasterParameters.LockBuffer);
ComputePassFlags = ERDGPassFlags::AsyncCompute;
}
FIntRect ViewRect(Views[0].ViewRect.X, Views[0].ViewRect.Y, Views[0].ViewRect.Z, Views[0].ViewRect.W);
if (bMultiView)
{
ViewRect.Min = FIntPoint::ZeroValue;
ViewRect.Max = RasterContext.TextureSize;
}
// 처리(Process)VSM.
if (VirtualShadowMapArray)
{
ViewRect.Min = FIntPoint::ZeroValue;
if( GNaniteClusterPerPage )
{
ViewRect.Max = FIntPoint( FVirtualShadowMap::PageSize, FVirtualShadowMap::PageSize ) * FVirtualShadowMap::RasterWindowPages;
}
else
{
ViewRect.Max = FIntPoint( FVirtualShadowMap::VirtualMaxResolutionXY, FVirtualShadowMap::VirtualMaxResolutionXY );
}
}
// 을(를) 활용하여 의 렌더링(Render)파이프라인 래스터라이제이션 .
{
const bool bUsePrimitiveShader = UsePrimitiveShader();
const bool bUseAutoCullingShader =
GRHISupportsPrimitiveShaders &&
!bUsePrimitiveShader &&
GNaniteAutoShaderCulling != 0;
// 처리(Process)VS파라미터 .
FHWRasterizeVS::FPermutationDomain PermutationVectorVS;
PermutationVectorVS.Set<FHWRasterizeVS::FRasterTechniqueDim>(int32(Technique));
PermutationVectorVS.Set<FHWRasterizeVS::FAddClusterOffset>(bMainPass ? 0 : 1);
PermutationVectorVS.Set<FHWRasterizeVS::FMultiViewDim>(bMultiView);
PermutationVectorVS.Set<FHWRasterizeVS::FPrimShaderDim>(bUsePrimitiveShader);
PermutationVectorVS.Set<FHWRasterizeVS::FAutoShaderCullDim>(bUseAutoCullingShader);
PermutationVectorVS.Set<FHWRasterizeVS::FHasPrevDrawData>(bHavePrevDrawData);
PermutationVectorVS.Set<FHWRasterizeVS::FVisualizeDim>(RasterContext.VisualizeActive && Technique != ERasterTechnique::DepthOnly);
PermutationVectorVS.Set<FHWRasterizeVS::FNearClipDim>(bNearClip);
PermutationVectorVS.Set<FHWRasterizeVS::FVirtualTextureTargetDim>(VirtualShadowMapArray != nullptr);
PermutationVectorVS.Set<FHWRasterizeVS::FClusterPerPageDim>(GNaniteClusterPerPage && VirtualShadowMapArray != nullptr );
// 처리(Process)PS파라미터 .
FHWRasterizePS::FPermutationDomain PermutationVectorPS;
PermutationVectorPS.Set<FHWRasterizePS::FRasterTechniqueDim>(int32(Technique));
PermutationVectorPS.Set<FHWRasterizePS::FMultiViewDim>(bMultiView);
PermutationVectorPS.Set<FHWRasterizePS::FPrimShaderDim>(bUsePrimitiveShader);
PermutationVectorPS.Set<FHWRasterizePS::FVisualizeDim>(RasterContext.VisualizeActive && Technique != ERasterTechnique::DepthOnly);
PermutationVectorPS.Set<FHWRasterizePS::FNearClipDim>(bNearClip);
PermutationVectorPS.Set<FHWRasterizePS::FVirtualTextureTargetDim>(VirtualShadowMapArray != nullptr);
PermutationVectorPS.Set<FHWRasterizePS::FClusterPerPageDim>( GNaniteClusterPerPage && VirtualShadowMapArray != nullptr );
auto VertexShader = RasterContext.ShaderMap->GetShader<FHWRasterizeVS>(PermutationVectorVS);
auto PixelShader = RasterContext.ShaderMap->GetShader<FHWRasterizePS>(PermutationVectorPS);
// 추가(Add)래스터라이제이션 Pass.
GraphBuilder.AddPass(
bMainPass ? RDG_EVENT_NAME("Main Pass: Rasterize") : RDG_EVENT_NAME("Post Pass: Rasterize"),
RasterPassParameters,
ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
[VertexShader, PixelShader, RasterPassParameters, ViewRect, bUsePrimitiveShader, bMainPass](FRHICommandListImmediate& RHICmdList)
{
// 렌더링(Render)Pass정보 .
FRHIRenderPassInfo RPInfo;
// Resolve파라미터 .
RPInfo.ResolveParameters.DestRect.X1 = ViewRect.Min.X;
RPInfo.ResolveParameters.DestRect.Y1 = ViewRect.Min.Y;
RPInfo.ResolveParameters.DestRect.X2 = ViewRect.Max.X;
RPInfo.ResolveParameters.DestRect.Y2 = ViewRect.Max.Y;
RHICmdList.BeginRenderPass(RPInfo, bMainPass ? TEXT("Main Pass: Rasterize") : TEXT("Post Pass: Rasterize"));
RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, FMath::Min(ViewRect.Max.X, 32767), FMath::Min(ViewRect.Max.Y, 32767), 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
// PSO.
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = GetStaticRasterizerState<false>(FM_Solid, CM_CW);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.PrimitiveType = bUsePrimitiveShader ? PT_PointList : PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
SetGraphicsPipelineState( RHICmdList, GraphicsPSOInit );
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), RasterPassParameters->Common);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *RasterPassParameters);
RHICmdList.SetStreamSource( 0, nullptr, 0 );
// ※ 주의: 호출(Call)의 는 Indirect타입/유형 의 인터페이스 , IndirectArgs는 AddPass_InstanceHierarchyAndClusterCull의 결과 .
RHICmdList.DrawPrimitiveIndirect(RasterPassParameters->Common.IndirectArgs->GetIndirectRHICallBuffer(), 16);
RHICmdList.EndRenderPass();
});
}
// 래스터라이제이션 (을(를) 활용하여 Compute Shader계산/산출(Calculate)).
if (Scheduling != ERasterScheduling::HardwareOnly)
{
// 처리(Process)래스터라이제이션 CS의 파라미터 .
FMicropolyRasterizeCS::FPermutationDomain PermutationVectorCS;
PermutationVectorCS.Set<FMicropolyRasterizeCS::FAddClusterOffset>(bMainPass ? 0 : 1);
PermutationVectorCS.Set<FMicropolyRasterizeCS::FMultiViewDim>(bMultiView);
PermutationVectorCS.Set<FMicropolyRasterizeCS::FHasPrevDrawData>(bHavePrevDrawData);
PermutationVectorCS.Set<FMicropolyRasterizeCS::FRasterTechniqueDim>(int32(Technique));
PermutationVectorCS.Set<FMicropolyRasterizeCS::FVisualizeDim>(RasterContext.VisualizeActive && Technique != ERasterTechnique::DepthOnly);
PermutationVectorCS.Set<FMicropolyRasterizeCS::FNearClipDim>(bNearClip);
PermutationVectorCS.Set<FMicropolyRasterizeCS::FVirtualTextureTargetDim>(VirtualShadowMapArray != nullptr);
PermutationVectorCS.Set<FMicropolyRasterizeCS::FClusterPerPageDim>(GNaniteClusterPerPage&& VirtualShadowMapArray != nullptr);
auto ComputeShader = RasterContext.ShaderMap->GetShader<FMicropolyRasterizeCS>(PermutationVectorCS);
// 디스패치 호출(Call), 래스터라이제이션 의 데이터 및 파라미터 CommonPassParameters.
FComputeShaderUtils::AddPass(
GraphBuilder,
bMainPass ? RDG_EVENT_NAME("Main Pass: Rasterize") : RDG_EVENT_NAME("Post Pass: Rasterize"),
ComputePassFlags,
ComputeShader,
CommonPassParameters,
CommonPassParameters->IndirectArgs,
0);
}
}하드웨어 래스터라이제이션 및 소프트웨어 래스터라이제이션 프로세스를 더 자세히 살펴보려면 분석을 위한 셰이더 로직을 입력해야 합니다.
// Engine\Shaders\Private\Nanite\Rasterizer.usf
(......)
// 래스터라이제이션 (을(를) 활용하여 래스터라이제이션 )
void RasterizeTri(
FNaniteView NaniteView,
int4 ViewRect,
uint PixelValue,
#if VISUALIZE
uint2 VisualizeValues,
#endif
float3 Verts[3],
bool bUsePageTable )
{
float3 v01 = Verts[1] - Verts[0];
float3 v02 = Verts[2] - Verts[0];
// 백페이스 컬링
float DetXY = v01.x * v02.y - v01.y * v02.x;
if( DetXY >= 0.0f )
{
return;
}
float InvDet = rcp( DetXY );
float2 GradZ;
GradZ.x = ( v01.z * v02.y - v01.y * v02.z ) * InvDet;
GradZ.y = ( v01.x * v02.z - v01.z * v02.x ) * InvDet;
// 16.8포인트
float2 Vert0 = Verts[0].xy;
float2 Vert1 = Verts[1].xy;
float2 Vert2 = Verts[2].xy;
// 모멘트(Moment)바운딩 박스(AABB)
const float2 MinSubpixel = min3( Vert0, Vert1, Vert2 );
const float2 MaxSubpixel = max3( Vert0, Vert1, Vert2 );
// 까지 픽셀(Pixel)
int2 MinPixel = (int2)floor( ( MinSubpixel + (SUBPIXEL_SAMPLES / 2) - 1 ) * (1.0 / SUBPIXEL_SAMPLES) );
int2 MaxPixel = (int2)floor( ( MaxSubpixel - (SUBPIXEL_SAMPLES / 2) - 1 ) * (1.0 / SUBPIXEL_SAMPLES) );
// 클리핑/클램핑(Clipping)까지 뷰(View).
MinPixel = max( MinPixel, ViewRect.xy );
MaxPixel = min( MaxPixel, ViewRect.zw - 1 );
// 클리핑/클램핑(Clipping)픽셀(Pixel)의
if( any( MinPixel > MaxPixel ) )
return;
// 래스터라이제이션 까지 개 의 최댓값(Max)。
MaxPixel = min( MaxPixel, MinPixel + 63 );
// 4.8 포인트
float2 Edge01 = -v01.xy;
float2 Edge12 = Vert1 - Vert2;
float2 Edge20 = v02.xy;
// 을(를) 활용하여 MinPixel조정(Adjust)MinPixel의 픽셀(Pixel)
// 4.8 fixed point
// 해상도/크기 = 127x127픽셀(Pixel)
const float2 BaseSubpixel = (float2)MinPixel * SUBPIXEL_SAMPLES + (SUBPIXEL_SAMPLES / 2);
Vert0 -= BaseSubpixel;
Vert1 -= BaseSubpixel;
Vert2 -= BaseSubpixel;
// 상수(Constant)
// 8.16 fixed point
float C0 = Edge01.y * Vert0.x - Edge01.x * Vert0.y;
float C1 = Edge12.y * Vert1.x - Edge12.x * Vert1.y;
float C2 = Edge20.y * Vert2.x - Edge20.x * Vert2.y;
// 보정(Correct)패딩/채우기 이면
// Top left rule for CCW
C0 -= saturate(Edge01.y + saturate(1.0f - Edge01.x));
C1 -= saturate(Edge12.y + saturate(1.0f - Edge12.x));
C2 -= saturate(Edge20.y + saturate(1.0f - Edge20.x));
float Z0 = Verts[0].z - ( GradZ.x * Vert0.x + GradZ.y * Vert0.y );
GradZ *= SUBPIXEL_SAMPLES;
// 계산/산출(Calculate)상수(Constant), 및 SUBPIXEL_SAMPLES, SUBPIXEL_SAMPLES, , 래스터라이제이션 결과 , 그러나 .
float CY0 = C0 * (1.0f / SUBPIXEL_SAMPLES);
float CY1 = C1 * (1.0f / SUBPIXEL_SAMPLES);
float CY2 = C2 * (1.0f / SUBPIXEL_SAMPLES);
float ZY = Z0;
// 는 을(를) 활용하여
#if COMPILER_SUPPORTS_WAVE_VOTE
bool bScanLine = WaveActiveAnyTrue( MaxPixel.x - MinPixel.x > 4 );
#else
bool bScanLine = false;
#endif
if( bScanLine ) // 알고리즘 .
{
float3 Edge012 = { Edge01.y, Edge12.y, Edge20.y };
bool3 bOpenEdge = Edge012 < 0;
float3 InvEdge012 = Edge012 == 0 ? 1e8 : rcp( Edge012 );
int y = MinPixel.y;
while( true )
{
// No longer fixed point
float3 CrossX = float3( CY0, CY1, CY2 ) * InvEdge012;
float3 MinX = bOpenEdge ? CrossX : 0;
float3 MaxX = bOpenEdge ? MaxPixel.x - MinPixel.x : CrossX;
float x0 = ceil( max3( MinX.x, MinX.y, MinX.z ) );
float x1 = min3( MaxX.x, MaxX.y, MaxX.z );
float ZX = ZY + GradZ.x * x0;
x0 += MinPixel.x;
x1 += MinPixel.x;
// 순회(Traverse/Iterate)x의 픽셀(Pixel), 기록/쓰기(Write)픽셀(Pixel)데이터 .
for( float x = x0; x <= x1; x++ )
{
// 기록/쓰기(Write)픽셀(Pixel) 및 뎁스 .
WritePixel(OutVisBuffer64, PixelValue, uint2(x,y), ZX, NaniteView, bUsePageTable);
#if VISUALIZE
WritePixel(OutDbgBuffer64, VisualizeValues.x, uint2(x,y), ZX, NaniteView, bUsePageTable);
InterlockedAdd(OutDbgBuffer32[uint2(x,y)], VisualizeValues.y);
#endif
ZX += GradZ.x;
}
if( y >= MaxPixel.y )
break;
// 추가(Add)Y의
CY0 += Edge01.x;
CY1 += Edge12.x;
CY2 += Edge20.x;
ZY += GradZ.y;
y++;
}
}
else // 알고리즘 (모멘트(Moment)알고리즘 , 검사/감지(Detect)는 )
{
int y = MinPixel.y;
while (true)
{
int x = MinPixel.x;
// 3개 는 , .
if (min3(CY0, CY1, CY2) >= 0)
{
WritePixel(OutVisBuffer64, PixelValue, uint2(x, y), ZY, NaniteView, bUsePageTable);
#if VISUALIZE
WritePixel(OutDbgBuffer64, VisualizeValues.x, uint2(x, y), ZY, NaniteView, bUsePageTable);
InterlockedAdd(OutDbgBuffer32[uint2(x, y)], VisualizeValues.y);
#endif
}
if (x < MaxPixel.x)
{
float CX0 = CY0 - Edge01.y;
float CX1 = CY1 - Edge12.y;
float CX2 = CY2 - Edge20.y;
float ZX = ZY + GradZ.x;
x++;
HOIST_DESCRIPTORS
while (true)
{
if (min3(CX0, CX1, CX2) >= 0)
{
WritePixel(OutVisBuffer64, PixelValue, uint2(x, y), ZX, NaniteView, bUsePageTable);
#if VISUALIZE
WritePixel(OutDbgBuffer64, VisualizeValues.x, uint2(x, y), ZX, NaniteView, bUsePageTable);
InterlockedAdd(OutDbgBuffer32[uint2(x, y)], VisualizeValues.y);
#endif
}
if (x >= MaxPixel.x)
break;
CX0 -= Edge01.y;
CX1 -= Edge12.y;
CX2 -= Edge20.y;
ZX += GradZ.x;
x++;
}
}
if (y >= MaxPixel.y)
break;
CY0 += Edge01.x;
CY1 += Edge12.x;
CY2 += Edge20.x;
ZY += GradZ.y;
y++;
}
}
}
#if USE_CONSTRAINED_CLUSTERS
groupshared float3 GroupVerts[256];
#else
groupshared float3 GroupVerts[384];
#endif
// 검사/감지(Detect)클리핑/클램핑(Clipping), 는 (CW). 만약 반환(Return)true, (CCW).
bool ReverseWindingOrder(FInstanceSceneData InstanceData)
{
bool bReverseInstanceCull = (InstanceData.InvNonUniformScaleAndDeterminantSign.w < 0.0f);
bool bRasterStateReverseCull = (RenderFlags & RENDER_FLAG_REVERSE_CULLING);
// Logical XOR
return (bReverseInstanceCull != bRasterStateReverseCull);
}
StructuredBuffer< uint2 > InTotalPrevDrawClusters;
Buffer<uint> InClusterOffsetSWHW;
groupshared float4x4 LocalToSubpixelLDS;
// 테이블 래스터라이제이션 , 을(를) 활용하여 Nanite의 CS.
[numthreads(128, 1, 1)]
void MicropolyRasterize(
uint VisibleIndex : SV_GroupID,
uint GroupIndex : SV_GroupIndex)
{
// 계산/산출(Calculate).
#if HAS_PREV_DRAW_DATA
VisibleIndex += InTotalPrevDrawClusters[0].x;
#endif
#if ADD_CLUSTER_OFFSET
VisibleIndex += InClusterOffsetSWHW[0];
#endif
// 페치/가져오기(Fetch)의 Cluster 및 인스턴스 데이터 .
FVisibleCluster VisibleCluster = GetVisibleCluster( VisibleIndex, VIRTUAL_TEXTURE_TARGET );
FInstanceSceneData InstanceData = GetInstanceData( VisibleCluster.InstanceId );
// 페치/가져오기(Fetch)Nanite뷰(View).
FNaniteView NaniteView = GetNaniteView( VisibleCluster.ViewId );
// 페치/가져오기(Fetch)정보 .
#if CLUSTER_PER_PAGE
// Scalar
uint2 vPage = VisibleCluster.vPage;
FShadowPhysicalPage pPage = ShadowGetPhysicalPage( CalcPageTableLevelOffset( NaniteView.TargetLayerIndex, NaniteView.TargetMipLevel ) + CalcPageOffsetInLevel( NaniteView.TargetMipLevel, vPage ) );
#endif
float4x4 LocalToSubpixel;
// InstancedDynamicData는 Group의 , 따라서 오직 계산/산출(Calculate)회 , 저장(Store)groupshared의 변수 내에서 로써 을(를) 활용하여 .
if( GroupIndex == 0 )
{
LocalToSubpixel = CalculateInstanceDynamicData(NaniteView, InstanceData).LocalToClip;
float2 Scale = float2( 0.5, -0.5 ) * NaniteView.ViewSizeAndInvSize.xy * SUBPIXEL_SAMPLES;
float2 Bias = ( 0.5 * NaniteView.ViewSizeAndInvSize.xy + NaniteView.ViewRect.xy ) * SUBPIXEL_SAMPLES + 0.5f;
#if CLUSTER_PER_PAGE
Bias += ( (float2)pPage.PageIndex - (float2)vPage ) * VSM_PAGE_SIZE * SUBPIXEL_SAMPLES;
#endif
LocalToSubpixel._m00_m10_m20_m30 = LocalToSubpixel._m00_m10_m20_m30 * Scale.x + LocalToSubpixel._m03_m13_m23_m33 * Bias.x;
LocalToSubpixel._m01_m11_m21_m31 = LocalToSubpixel._m01_m11_m21_m31 * Scale.y + LocalToSubpixel._m03_m13_m23_m33 * Bias.y;
LocalToSubpixelLDS = LocalToSubpixel;
}
// 을(를) 활용하여 Group메모리 배리어 로써 동기화 Group데이터 .
GroupMemoryBarrierWithGroupSync();
LocalToSubpixel = LocalToSubpixelLDS;
// 페치/가져오기(Fetch)Cluster데이터 .
FCluster Cluster = GetCluster(VisibleCluster.PageIndex, VisibleCluster.ClusterIndex);
UNROLL
for( uint i = 0; i < 2; i++ )
{
uint VertIndex = GroupIndex + i * 128;
if( VertIndex < Cluster.NumVerts )
{
// 변환(Transform)포인트 , 까지 메모리 내에서 .
float3 PointLocal = DecodePosition( VertIndex, Cluster );
float4 PointClipSubpixel = mul( float4( PointLocal, 1 ), LocalToSubpixel );
float3 Subpixel = PointClipSubpixel.xyz / PointClipSubpixel.w;
GroupVerts[ VertIndex ] = float3(floor(Subpixel.xy), Subpixel.z);
}
}
// 을(를) 활용하여 Group메모리 배리어 로써 동기화 Group데이터 .
GroupMemoryBarrierWithGroupSync();
int4 ViewRect = NaniteView.ViewRect;
#if CLUSTER_PER_PAGE
ViewRect.xy = pPage.PageIndex * VSM_PAGE_SIZE;
ViewRect.zw = ViewRect.xy + VSM_PAGE_SIZE;
#endif
if (GroupIndex < Cluster.NumTris)
{
// ID는 Group.
uint TriangleID = GroupIndex;
// , 처리(Process)반전 의 .
uint3 TriangleIndices = ReadTriangleIndices(Cluster, TriangleID);
if (ReverseWindingOrder(InstanceData))
{
TriangleIndices = uint3(TriangleIndices.x, TriangleIndices.z, TriangleIndices.y);
}
// 페치/가져오기(Fetch)위치 .
float3 Vertices[3];
Vertices[0] = GroupVerts[TriangleIndices.x];
Vertices[1] = GroupVerts[TriangleIndices.y];
Vertices[2] = GroupVerts[TriangleIndices.z];
// 픽셀(Pixel)는 ID.
uint PixelValue = ((VisibleIndex + 1) << 7) | TriangleID;
// 래스터라이제이션 , 기록/쓰기(Write)의 id 및 뎁스 .
RasterizeTri(
NaniteView,
ViewRect,
PixelValue,
#if VISUALIZE
GetVisualizeValues(),
#endif
Vertices,
!CLUSTER_PER_PAGE );
}
}
#define PIXEL_VALUE (RASTER_TECHNIQUE != RASTER_TECHNIQUE_DEPTHONLY)
#define VERTEX_TO_TRIANGLE_MASKS (NANITE_PRIM_SHADER && PIXEL_VALUE)
struct VSOut
{
noperspective float DeviceZ : TEXCOORD0;
#if PIXEL_VALUE
nointerpolation uint PixelValue : TEXCOORD1;
#endif
#if NANITE_MULTI_VIEW
nointerpolation int4 ViewRect : TEXCOORD2;
#endif
#if VISUALIZE
nointerpolation uint2 VisualizeValues : TEXCOORD3;
#endif
#if VIRTUAL_TEXTURE_TARGET
nointerpolation int ViewId : TEXCOORD4;
#endif
#if VERTEX_TO_TRIANGLE_MASKS
CUSTOM_INTERPOLATION uint4 ToTriangleMasks : TEXCOORD5;
#endif
float4 Position : SV_Position;
};
// 래스터라이제이션 의 VS, 는 포인트 데이터 로부터 Cluster 내에서 압축해제/언패킹(Unpacking), 변환(Transform)까지 클리핑/클램핑(Clipping).
VSOut CommonRasterizerVS(FNaniteView NaniteView, FInstanceSceneData InstanceData, FVisibleCluster VisibleCluster, FCluster Cluster, uint VertIndex, out float4 PointClipNoScaling)
{
VSOut Out;
float4x4 LocalToWorld = InstanceData.LocalToWorld;
float3 PointLocal = DecodePosition( VertIndex, Cluster );
float3 PointRotated = LocalToWorld[0].xyz * PointLocal.xxx + LocalToWorld[1].xyz * PointLocal.yyy + LocalToWorld[2].xyz * PointLocal.zzz;
float3 PointTranslatedWorld = PointRotated + (LocalToWorld[3].xyz + NaniteView.PreViewTranslation.xyz);
float4 PointClip = mul( float4( PointTranslatedWorld, 1 ), NaniteView.TranslatedWorldToClip );
PointClipNoScaling = PointClip;
#if CLUSTER_PER_PAGE
PointClip.xy = NaniteView.ClipSpaceScaleOffset.xy * PointClip.xy + NaniteView.ClipSpaceScaleOffset.zw * PointClip.w;
// Offset 0,0 to be at vPage for a 0, VSM_PAGE_SIZE * VSM_RASTER_WINDOW_PAGES viewport.
PointClip.xy += PointClip.w * ( float2(-2, 2) / VSM_RASTER_WINDOW_PAGES ) * VisibleCluster.vPage;
Out.ViewRect.xy = VisibleCluster.vPage * VSM_PAGE_SIZE;
Out.ViewRect.zw = NaniteView.ViewRect.zw;
#elif NANITE_MULTI_VIEW
PointClip.xy = NaniteView.ClipSpaceScaleOffset.xy * PointClip.xy + NaniteView.ClipSpaceScaleOffset.zw * PointClip.w;
Out.ViewRect = NaniteView.ViewRect;
#endif
#if VIRTUAL_TEXTURE_TARGET
Out.ViewId = VisibleCluster.ViewId;
#endif
Out.Position = PointClip;
Out.DeviceZ = PointClip.z / PointClip.w;
// Shader workaround to avoid HW depth clipping. Should be replaced with rasterizer state ideally.
#if !NEAR_CLIP
Out.Position.z = 0.5f * Out.Position.w;
#endif
#if VISUALIZE
Out.VisualizeValues = GetVisualizeValues();
#endif
return Out;
}
#if NANITE_PRIM_SHADER
#pragma argument(wavemode=wave64)
#pragma argument(realtypes)
struct PrimitiveInput
{
uint Index : PRIM_SHADER_SEM_VERT_INDEX;
uint WaveIndex : PRIM_SHADER_SEM_WAVE_INDEX;
};
struct PrimitiveOutput
{
VSOut Out;
uint PrimExport : PRIM_SHADER_SEM_PRIM_EXPORT;
uint VertCount : PRIM_SHADER_SEM_VERT_COUNT;
uint PrimCount : PRIM_SHADER_SEM_PRIM_COUNT;
};
// 압축/패킹(Packing), 내에서 x,y,z의 는 10,10,12.
uint PackTriangleExport(uint3 TriangleIndices)
{
return TriangleIndices.x | (TriangleIndices.y << 10) | (TriangleIndices.z << 20);
}
// 압축해제/언패킹(Unpacking).
uint3 UnpackTriangleExport(uint Packed)
{
const uint Index0 = (Packed & 0x3FF); // 10.
const uint Index1 = (Packed >> 10) & 0x3FF; // 내에서 10
const uint Index2 = (Packed >> 20); // 12.
return uint3(Index0, Index1, Index2);
}
#if VERTEX_TO_TRIANGLE_MASKS // 렌더링(Render).
groupshared uint GroupVertexToTriangleMasks[256][4];
#endif
groupshared uint GroupTriangleCount;
groupshared uint GroupVertexCount;
groupshared uint GroupClusterIndex;
PRIM_SHADER_OUTPUT_TRIANGLES
PRIM_SHADER_PRIM_COUNT(1)
PRIM_SHADER_VERT_COUNT(1)
PRIM_SHADER_VERT_LIMIT(256)
PRIM_SHADER_AMP_FACTOR(128)
PRIM_SHADER_AMP_ENABLE
// 래스터라이제이션 VS(렌더링(Render)).
PrimitiveOutput HWRasterizeVS(PrimitiveInput Input)
{
const uint LaneIndex = WaveGetLaneIndex();
const uint LaneCount = WaveGetLaneCount();
const uint GroupThreadID = LaneIndex + Input.WaveIndex * LaneCount;
if (GroupThreadID == 0)
{
// Input index is only initialized for lane 0, so we need to manually communicate it to all other threads in subgroup (not just wavefront).
GroupClusterIndex = Input.Index;
}
GroupMemoryBarrierWithGroupSync();
// 의 코드 및 MicropolyRasterize타입/유형 , .
uint VisibleIndex = GroupClusterIndex;
#if HAS_PREV_DRAW_DATA
VisibleIndex += InTotalPrevDrawClusters[0].y;
#endif
#if ADD_CLUSTER_OFFSET
VisibleIndex += InClusterOffsetSWHW[GetHWClusterCounterIndex(RenderFlags)];
#endif
VisibleIndex = (MaxVisibleClusters - 1) - VisibleIndex;
// Should be all scalar.
FVisibleCluster VisibleCluster = GetVisibleCluster( VisibleIndex, VIRTUAL_TEXTURE_TARGET );
FInstanceSceneData InstanceData = GetInstanceData( VisibleCluster.InstanceId );
FNaniteView NaniteView = GetNaniteView( VisibleCluster.ViewId );
FInstanceDynamicData InstanceDynamicData = CalculateInstanceDynamicData(NaniteView, InstanceData);
FCluster Cluster = GetCluster(VisibleCluster.PageIndex, VisibleCluster.ClusterIndex);
#if VERTEX_TO_TRIANGLE_MASKS
if (GroupThreadID < Cluster.NumVerts)
{
GroupVertexToTriangleMasks[GroupThreadID][0] = 0;
GroupVertexToTriangleMasks[GroupThreadID][1] = 0;
GroupVertexToTriangleMasks[GroupThreadID][2] = 0;
GroupVertexToTriangleMasks[GroupThreadID][3] = 0;
}
#endif
GroupMemoryBarrierWithGroupSync();
PrimitiveOutput PrimOutput;
PrimOutput.VertCount = Cluster.NumVerts;
PrimOutput.PrimCount = Cluster.NumTris;
bool bCullTriangle = false;
if (GroupThreadID < Cluster.NumTris)
{
uint TriangleID = GroupThreadID;
uint3 TriangleIndices = ReadTriangleIndices(Cluster, TriangleID);
if (ReverseWindingOrder(InstanceData))
{
TriangleIndices = uint3(TriangleIndices.x, TriangleIndices.z, TriangleIndices.y);
}
#if VERTEX_TO_TRIANGLE_MASKS
const uint DwordIndex = (GroupThreadID >> 5) & 3;
const uint TriangleMask = 1 << (GroupThreadID & 31);
InterlockedOr(GroupVertexToTriangleMasks[TriangleIndices.x][DwordIndex], TriangleMask);
InterlockedOr(GroupVertexToTriangleMasks[TriangleIndices.y][DwordIndex], TriangleMask);
InterlockedOr(GroupVertexToTriangleMasks[TriangleIndices.z][DwordIndex], TriangleMask);
#endif
PrimOutput.PrimExport = PackTriangleExport(TriangleIndices);
}
GroupMemoryBarrierWithGroupSync();
if (GroupThreadID < Cluster.NumVerts)
{
float4 PointClipNoScaling;
// 래스터라이제이션 .
PrimOutput.Out = CommonRasterizerVS(NaniteView, InstanceData, VisibleCluster, Cluster, GroupThreadID, PointClipNoScaling);
#if VERTEX_TO_TRIANGLE_MASKS
PrimOutput.Out.PixelValue = ((VisibleIndex + 1) << 7);
PrimOutput.Out.ToTriangleMasks = uint4(GroupVertexToTriangleMasks[GroupThreadID][0],
GroupVertexToTriangleMasks[GroupThreadID][1],
GroupVertexToTriangleMasks[GroupThreadID][2],
GroupVertexToTriangleMasks[GroupThreadID][3]);
#endif
}
return PrimOutput;
}
#else // NANITE_PRIM_SHADER(Nanite셰이딩 )
// 래스터라이제이션 VS(셰이딩 ).
VSOut HWRasterizeVS(
uint VertexID : SV_VertexID,
uint VisibleIndex : SV_InstanceID
)
{
#if HAS_PREV_DRAW_DATA
VisibleIndex += InTotalPrevDrawClusters[0].y;
#endif
#if ADD_CLUSTER_OFFSET
VisibleIndex += InClusterOffsetSWHW[GetHWClusterCounterIndex(RenderFlags)];
#endif
VisibleIndex = (MaxVisibleClusters - 1) - VisibleIndex;
uint TriIndex = VertexID / 3;
VertexID = VertexID - TriIndex * 3;
VSOut Out;
Out.Position = float4(0,0,0,1);
Out.DeviceZ = 0.0f;
FVisibleCluster VisibleCluster = GetVisibleCluster( VisibleIndex, VIRTUAL_TEXTURE_TARGET );
FInstanceSceneData InstanceData = GetInstanceData( VisibleCluster.InstanceId );
FNaniteView NaniteView = GetNaniteView( VisibleCluster.ViewId );
FCluster Cluster = GetCluster(VisibleCluster.PageIndex, VisibleCluster.ClusterIndex);
if( TriIndex < Cluster.NumTris )
{
uint3 TriangleIndices = ReadTriangleIndices( Cluster, TriIndex );
if( ReverseWindingOrder( InstanceData ) )
{
TriangleIndices = uint3( TriangleIndices.x, TriangleIndices.z, TriangleIndices.y );
}
uint VertIndex = TriangleIndices[ VertexID ];
float4 PointClipNoScaling;
// 래스터라이제이션 .
Out = CommonRasterizerVS(NaniteView, InstanceData, VisibleCluster, Cluster, VertIndex, PointClipNoScaling);
#if PIXEL_VALUE
Out.PixelValue = ((VisibleIndex + 1) << 7) | TriIndex;
#endif
}
return Out;
}
#endif // NANITE_PRIM_SHADER
// 래스터라이제이션 의 PS.
void HWRasterizePS(VSOut In)
{
uint2 PixelPos = (uint2)In.Position.xy;
uint PixelValue = 0;
#if PIXEL_VALUE
PixelValue = In.PixelValue;
#endif
#if VERTEX_TO_TRIANGLE_MASKS
uint4 Masks0 = LoadParameterCacheP0( In.ToTriangleMasks );
uint4 Masks1 = LoadParameterCacheP1( In.ToTriangleMasks );
uint4 Masks2 = LoadParameterCacheP2( In.ToTriangleMasks );
uint4 Masks = Masks0 & Masks1 & Masks2;
uint TriangleIndex = Masks.x ? firstbitlow( Masks.x ) :
Masks.y ? firstbitlow( Masks.y ) + 32 :
Masks.z ? firstbitlow( Masks.z ) + 64 :
firstbitlow( Masks.w ) + 96;
PixelValue += TriangleIndex;
#endif
#if VIRTUAL_TEXTURE_TARGET
FNaniteView NaniteView = GetNaniteView(In.ViewId);
#else
FNaniteView NaniteView;
#endif
#if CLUSTER_PER_PAGE
PixelPos += In.ViewRect.xy;
if (all(PixelPos < In.ViewRect.zw))
#elif NANITE_MULTI_VIEW
// In multi-view mode every view has its own scissor, so we have to scissor manually.
if (all(PixelPos >= In.ViewRect.xy && PixelPos < In.ViewRect.zw))
#endif
{
// 기록/쓰기(Write)픽셀(Pixel)데이터 : id(PixelValue), 뎁스 (In.DeviceZ)
WritePixel(OutVisBuffer64, PixelValue, PixelPos, In.DeviceZ, NaniteView, VIRTUAL_TEXTURE_TARGET);
#if VISUALIZE
WritePixel(OutDbgBuffer64, In.VisualizeValues.x, PixelPos, In.DeviceZ, NaniteView, VIRTUAL_TEXTURE_TARGET);
InterlockedAdd(OutDbgBuffer32[PixelPos], In.VisualizeValues.y);
#endif
}
}위의 분석을 보면 소프트웨어 래스터인지 하드웨어 래스터인지 ClusterID, 삼각형 ID, 깊이(시각화 모드인 경우 다른 데이터가 있음)만 기록된 데이터임을 알 수 있는데, 이는 이 단계가 실제로 색상을 지정하지는 않지만 디퍼드 렌더링의 BasePass와 유사하지만 출력 정보가 BasePass에 비해 훨씬 적고 그에 따른 IO 및 비디오 메모리가 크게 줄어든다는 것을 알 수 있습니다. 사실 이 기술은 Visibility Buffer 기술이다. 자세한 내용은 섹션 4.2.3.5 언리얼 렌더링 시스템 분석의 가시성 버퍼(04) - 디퍼드 렌더링(Deferred Shading) 파이프라인을 참조하세요.

Nanite 래스터라이제이션 후 저장 구조: ClusterID는 25비트, 삼각형 ID는 7비트, 깊이는 32비트를 차지합니다.

Nanite 래스터라이제이션 후 결과의 모식도는 위에서 아래로 ClusterID, 삼각형 ID 및 깊이입니다.
Nanite 래스터라이제이션 후 또 다른 중요한 단계는 Nanite::EmitDepthTargets입니다. 이는 장면 깊이, 템플릿, 속도 및 재질 깊이와 같은 데이터를 버퍼링하는 데 사용됩니다.

스텐실 버퍼는 Nanite에서 렌더링되는 픽셀을 나타냅니다.

가장 흥미로운 점은 장면 전면의 각 픽셀이 어느 재질을 덮고 있는지를 나타내는 재질 깊이입니다. 본질적으로 고유한 깊이 값으로 변환되어 깊이 템플릿 텍스처에 저장되는 재질 ID입니다. 실제로 각 재질에는 회색조 값이 있으므로 나중에 Early Z를 사용하여 최적화할 수 있습니다.

6.4.3.6 나나이트 베이스패스
이 섹션에서는 주로 Nanite의 BasePass에 의한 GBuffer 생성을 설명합니다. FDeferredShadingSceneRenderer::Render의 주요 프로세스는 다음과 같습니다:
void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder)
{
(......)
// 렌더링(Render)Nanite의 BasePass.
{
// 드로우/렌더(Draw)의 BasePass.
RenderBasePass(GraphBuilder, SceneTextures, DBufferTextures, BasePassDepthStencilAccess, ForwardScreenSpaceShadowMaskTexture, InstanceCullingManager);
AddServiceLocalQueuePass(GraphBuilder);
if (bNaniteEnabled && bShouldApplyNaniteMaterials)
{
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
{
const FViewInfo& View = Views[ViewIndex];
Nanite::FRasterResults& RasterResults = NaniteRasterResults[ViewIndex];
// 만약 드로우/렌더(Draw)뎁스 , 이면 드로우/렌더(Draw)뎁스
if (!bNeedsPrePass)
{
Nanite::EmitDepthTargets(
GraphBuilder,
*Scene,
Views[ViewIndex],
RasterResults.SOAStrides,
RasterResults.VisibleClustersSWHW,
RasterResults.ViewsBuffer,
SceneTextures.Depth.Target,
RasterResults.VisBuffer64,
RasterResults.MaterialDepth,
RasterResults.NaniteMask,
RasterResults.VelocityBuffer,
bNeedsPrePass
);
}
// 드로우/렌더(Draw)Nanite의 BasePass.
Nanite::DrawBasePass(
GraphBuilder,
SceneTextures,
DBufferTextures,
*Scene,
View,
RasterResults
);
}
}
// 씬 뎁스(SceneDepth).
if (!bAllowReadOnlyDepthBasePass)
{
AddResolveSceneDepthPass(GraphBuilder, Views, SceneTextures.Depth);
}
(......)
}
(......)
}위에 BasePass에는 두 가지 그림이 있다는 점에 유의해야 합니다. 하나는 전통적인 BasePass 그림인 RenderBasePass이고 다른 하나는 Nanite 모드 BasePass 그림인 Nanite::DrawBasePass입니다. 다음은 Nanite::DrawBasePass를 분석한 것입니다.
// Engine\Source\Runtime\Renderer\Private\Nanite\NaniteRender.cpp
void DrawBasePass(
FRDGBuilder& GraphBuilder,
const FSceneTextures& SceneTextures,
const FDBufferTextures& DBufferTextures,
const FScene& Scene,
const FViewInfo& View,
const FRasterResults& RasterResults
)
{
(......)
RDG_EVENT_SCOPE(GraphBuilder, "Nanite::BasePass");
const int32 ViewWidth = View.ViewRect.Max.X - View.ViewRect.Min.X;
const int32 ViewHeight = View.ViewRect.Max.Y - View.ViewRect.Min.Y;
const FIntPoint ViewSize = FIntPoint(ViewWidth, ViewHeight);
const FRDGSystemTextures& SystemTextures = FRDGSystemTextures::Get(GraphBuilder);
FRenderTargetBindingSlots GBufferRenderTargets;
SceneTextures.GetGBufferRenderTargets(ERenderTargetLoadAction::ELoad, GBufferRenderTargets);
// 초기화(Initialize)텍스처(Texture)을(를) 활용하여 .
FRDGTextureRef MaterialDepth = RasterResults.MaterialDepth ? RasterResults.MaterialDepth : SystemTextures.Black;
FRDGTextureRef VisBuffer64 = RasterResults.VisBuffer64 ? RasterResults.VisBuffer64 : SystemTextures.Black;
FRDGTextureRef DbgBuffer64 = RasterResults.DbgBuffer64 ? RasterResults.DbgBuffer64 : SystemTextures.Black;
FRDGTextureRef DbgBuffer32 = RasterResults.DbgBuffer32 ? RasterResults.DbgBuffer32 : SystemTextures.Black;
FRDGBufferRef VisibleClustersSWHW = RasterResults.VisibleClustersSWHW;
// 검사/감지(Detect)머티리얼(Material)클리핑/클램핑(Clipping). SM6지원 ,지원 의 플랫폼4.
if (!FDataDrivenShaderPlatformInfo::GetSupportsWaveOperations(GMaxRHIShaderPlatform) &&
(GNaniteMaterialCulling == 1 || GNaniteMaterialCulling == 2))
{
UE_LOG(LogNanite, Warning, TEXT("r.Nanite.MaterialCulling set to %d which requires wave-ops (not supported on this platform), switching to mode 4"), GNaniteMaterialCulling);
GNaniteMaterialCulling = 4;
}
// 을(를) 활용하여 로컬 , 로써 을(를) 활용하여 수정 뷰(View)까지 의 의 .
int32 NaniteMaterialCulling = GNaniteMaterialCulling;
if ((NaniteMaterialCulling == 1 || NaniteMaterialCulling == 2) && (View.ViewRect.Min.X != 0 || View.ViewRect.Min.Y != 0))
{
NaniteMaterialCulling = 4;
static bool bLoggedAlready = false;
if (!bLoggedAlready)
{
bLoggedAlready = true;
UE_LOG(LogNanite, Warning, TEXT("View has non-zero viewport offset, using material culling mode 4 (overrides r.Nanite.MaterialCulling = %d)."), GNaniteMaterialCulling);
}
}
// 클리핑/클램핑(Clipping)
const bool b32BitMaskCulling = (NaniteMaterialCulling == 1 || NaniteMaterialCulling == 2);
// 타일드(Tiled)클리핑/클램핑(Clipping)
const bool bTileGridCulling = (NaniteMaterialCulling == 3 || NaniteMaterialCulling == 4);
const FIntPoint TileGridDim = bTileGridCulling ? FMath::DivideAndRoundUp(ViewSize, { 64, 64 }) : FIntPoint(1, 1);
// 생성(Create)텍스처(Texture) 및 버퍼 .
FRDGBufferDesc VisibleMaterialsDesc = FRDGBufferDesc::CreateStructuredDesc(4, b32BitMaskCulling ? FNaniteCommandInfo::MAX_STATE_BUCKET_ID+1 : 1);
FRDGBufferRef VisibleMaterials = GraphBuilder.CreateBuffer(VisibleMaterialsDesc, TEXT("Nanite.VisibleMaterials"));
FRDGBufferUAVRef VisibleMaterialsUAV = GraphBuilder.CreateUAV(VisibleMaterials);
FRDGTextureDesc MaterialRangeDesc = FRDGTextureDesc::Create2D(TileGridDim, PF_R32G32_UINT, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_UAV);
FRDGTextureRef MaterialRange = GraphBuilder.CreateTexture(MaterialRangeDesc, TEXT("Nanite.MaterialRange"));
FRDGTextureUAVRef MaterialRangeUAV = GraphBuilder.CreateUAV(MaterialRange);
FRDGTextureSRVDesc MaterialRangeSRVDesc = FRDGTextureSRVDesc::Create(MaterialRange);
FRDGTextureSRVRef MaterialRangeSRV = GraphBuilder.CreateSRV(MaterialRangeSRVDesc);
// 텍스처(Texture)버퍼
AddClearUAVPass(GraphBuilder, VisibleMaterialsUAV, 0);
AddClearUAVPass(GraphBuilder, MaterialRangeUAV, { 0u, 1u, 0u, 0u });
// 머티리얼(Material)로써 타일드(Tiled)클리핑/클램핑(Clipping)
if (b32BitMaskCulling || bTileGridCulling)
{
FClassifyMaterialsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FClassifyMaterialsCS::FParameters>();
PassParameters->View = View.ViewUniformBuffer;
PassParameters->VisibleClustersSWHW = GraphBuilder.CreateSRV(VisibleClustersSWHW);
PassParameters->SOAStrides = RasterResults.SOAStrides;
PassParameters->ClusterPageData = Nanite::GStreamingManager.GetClusterPageDataSRV();
PassParameters->ClusterPageHeaders = Nanite::GStreamingManager.GetClusterPageHeadersSRV();
PassParameters->VisBuffer64 = VisBuffer64;
PassParameters->MaterialDepthTable = Scene.MaterialTables[ENaniteMeshPass::BasePass].GetDepthTableSRV();
uint32 DispatchGroupSize = 0;
PassParameters->ViewRect = FIntVector4(View.ViewRect.Min.X, View.ViewRect.Min.Y, View.ViewRect.Max.X, View.ViewRect.Max.Y);
if (b32BitMaskCulling)
{
checkf(View.ViewRect.Min.X == 0 && View.ViewRect.Min.Y == 0, TEXT("Viewport offset support is not implemented."));
DispatchGroupSize = 8;
PassParameters->VisibleMaterials = VisibleMaterialsUAV;
}
else if (bTileGridCulling)
{
DispatchGroupSize = 64;
PassParameters->FetchClamp = View.ViewRect.Max - 1;
PassParameters->MaterialRange = MaterialRangeUAV;
}
const FIntVector DispatchDim = FComputeShaderUtils::GetGroupCount(View.ViewRect.Max - View.ViewRect.Min, DispatchGroupSize);
FClassifyMaterialsCS::FPermutationDomain PermutationVector;
PermutationVector.Set<FClassifyMaterialsCS::FCullingMethodDim>(NaniteMaterialCulling);
auto ComputeShader = View.ShaderMap->GetShader<FClassifyMaterialsCS>(PermutationVector.ToDimensionValueId());
// 머티리얼(Material)의 CS Pass.
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("Classify Materials"),
ComputeShader,
PassParameters,
DispatchDim
);
}
// 렌더링(Render)GBuffer.
{
// 처리(Process)Pass데이터
FNaniteEmitGBufferParameters* PassParameters = GraphBuilder.AllocParameters<FNaniteEmitGBufferParameters>();
PassParameters->SOAStrides = RasterResults.SOAStrides;
PassParameters->MaxVisibleClusters = RasterResults.MaxVisibleClusters;
PassParameters->MaxNodes = RasterResults.MaxNodes;
PassParameters->RenderFlags = RasterResults.RenderFlags;
PassParameters->ClusterPageData = Nanite::GStreamingManager.GetClusterPageDataSRV();
PassParameters->ClusterPageHeaders = Nanite::GStreamingManager.GetClusterPageHeadersSRV();
PassParameters->VisibleClustersSWHW = GraphBuilder.CreateSRV(VisibleClustersSWHW);
PassParameters->MaterialRange = MaterialRange;
PassParameters->VisibleMaterials = GraphBuilder.CreateSRV(VisibleMaterials, PF_R32_UINT);
PassParameters->VisBuffer64 = VisBuffer64; //
PassParameters->DbgBuffer64 = DbgBuffer64;
PassParameters->DbgBuffer32 = DbgBuffer32;
PassParameters->RenderTargets = GBufferRenderTargets; // 렌더링(Render)텍스처(Texture)
// Uniform Buffer
PassParameters->View = View.ViewUniformBuffer; // To get VTFeedbackBuffer
PassParameters->BasePass = CreateOpaqueBasePassUniformBuffer(GraphBuilder, View, 0, {}, DBufferTextures, nullptr);
switch (NaniteMaterialCulling)
{
// 을(를) 활용하여 8x4의 렌더링(Render), 32bit, 개 bit개 tile.
case 1:
case 2:
PassParameters->GridSize.X = 8;
PassParameters->GridSize.Y = 4;
break;
// 을(를) 활용하여 64x64의 픽셀(Pixel)타일드(Tiled)렌더링(Render).
case 3:
case 4:
PassParameters->GridSize = FMath::DivideAndRoundUp(View.ViewRect.Max - View.ViewRect.Min, { 64, 64 });
break;
// 을(를) 활용하여 풀스크린(Full Screen)렌더링(Render).
default:
PassParameters->GridSize.X = 1;
PassParameters->GridSize.Y = 1;
break;
}
const FExclusiveDepthStencil MaterialDepthStencil = UseComputeDepthExport()
? FExclusiveDepthStencil::DepthWrite_StencilNop
: FExclusiveDepthStencil::DepthWrite_StencilWrite;
PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(
MaterialDepth,
ERenderTargetLoadAction::ELoad,
ERenderTargetLoadAction::ELoad,
MaterialDepthStencil
);
TShaderMapRef<FNaniteMaterialVS> NaniteVertexShader(View.ShaderMap);
// 추가(Add)렌더링(Render)pass.
GraphBuilder.AddPass(
RDG_EVENT_NAME("Emit GBuffer"),
PassParameters,
ERDGPassFlags::Raster,
[PassParameters, &Scene, NaniteVertexShader, ViewRect = View.ViewRect, NaniteMaterialCulling](FRHICommandListImmediate& RHICmdList)
{
RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f);
// 처리(Process)글로벌 버퍼 파라미터 .
FNaniteUniformParameters UniformParams;
UniformParams.SOAStrides = PassParameters->SOAStrides;
UniformParams.MaxVisibleClusters= PassParameters->MaxVisibleClusters;
UniformParams.MaxNodes = PassParameters->MaxNodes;
UniformParams.RenderFlags = PassParameters->RenderFlags;
UniformParams.MaterialConfig.X = NaniteMaterialCulling;
UniformParams.MaterialConfig.Y = PassParameters->GridSize.X;
UniformParams.MaterialConfig.Z = PassParameters->GridSize.Y;
UniformParams.MaterialConfig.W = 0;
UniformParams.RectScaleOffset = FVector4(1.0f, 1.0f, 0.0f, 0.0f); // Render a rect that covers the entire screen
// 머티리얼(Material)클리핑/클램핑(Clipping)
if (NaniteMaterialCulling == 3 || NaniteMaterialCulling == 4)
{
FIntPoint ScaledSize = PassParameters->GridSize * 64;
UniformParams.RectScaleOffset.X = float(ScaledSize.X) / float(ViewRect.Max.X - ViewRect.Min.X);
UniformParams.RectScaleOffset.Y = float(ScaledSize.Y) / float(ViewRect.Max.Y - ViewRect.Min.Y);
}
// Cluster 및 데이터
UniformParams.ClusterPageData = PassParameters->ClusterPageData;
UniformParams.ClusterPageHeaders = PassParameters->ClusterPageHeaders;
UniformParams.VisibleClustersSWHW = PassParameters->VisibleClustersSWHW->GetRHI();
// 머티리얼(Material)데이터
UniformParams.MaterialRange = PassParameters->MaterialRange->GetRHI();
UniformParams.VisibleMaterials = PassParameters->VisibleMaterials->GetRHI();
// 데이터
UniformParams.VisBuffer64 = PassParameters->VisBuffer64->GetRHI();
UniformParams.DbgBuffer64 = PassParameters->DbgBuffer64->GetRHI();
UniformParams.DbgBuffer32 = PassParameters->DbgBuffer32->GetRHI();
const_cast<FScene&>(Scene).UniformBuffers.NaniteUniformBuffer.UpdateUniformBufferImmediate(UniformParams);
FGraphicsMinimalPipelineStateSet GraphicsMinimalPipelineStateSet;
TArray<FNaniteMaterialPassCommand, SceneRenderingAllocator> NaniteMaterialPassCommands;
// Nanite머티리얼(Material)Pass의 .
BuildNaniteMaterialPassCommands(RHICmdList, Scene.NaniteDrawCommands[ENaniteMeshPass::BasePass], NaniteMaterialPassCommands);
FMeshDrawCommandStateCache StateCache;
const uint32 TileCount = UniformParams.MaterialConfig.Y * UniformParams.MaterialConfig.Z; // (W * H)
// 순회(Traverse/Iterate)머티리얼(Material)채널/패스(Pass), 개 .
for (auto CommandsIt = NaniteMaterialPassCommands.CreateConstIterator(); CommandsIt; ++CommandsIt)
{
SubmitNaniteMaterialPassCommand(*CommandsIt, NaniteVertexShader, GraphicsMinimalPipelineStateSet, TileCount, RHICmdList, StateCache);
}
});
}
}BasePass를 렌더링하기 전에 재료 분류(Classify Material)를 위해 재료 분류 패스를 실행해야 하며, 이는 재료 제거와 같은 후속 작업에서 중요한 역할을 합니다. Compute Shader를 사용하여 전체 화면 가시성 버퍼를 분석하고 20x12=240픽셀(재료 범위라고 함, 형식은 R32G32_UINT)을 출력합니다. 각 픽셀(재료 범위)은 각 블록이 나타내는 64×64 영역에 나타나는 재료 범위를 인코딩합니다. 렌더링되는 색상은 다음과 같습니다.

위 코드에 포함된 Wave Operation을 Wave Operation으로 변환한 것이 DX의 개념입니다. VK의 해당 개념은 SM6 이상에서만 지원되는 Subgroup입니다. 자세한 내용은 GDC2017 Talk: Wave-Programming-D3D12-Vulkan을 참조하세요.
위 코드에서 Nanite 재질 Pass의 그리기 명령을 구성할 때 소스 데이터는 Scene.NaniteDrawCommands[ENaniteMeshPass::BasePass]입니다. 이 데이터는 FPrimitiveSceneInfo::UpdateStaticMeshes일 때 생성됩니다. 호출 스택은 다음과 같습니다.
// Engine\Source\Runtime\Renderer\Private\PrimitiveSceneInfo.cpp
void FPrimitiveSceneInfo::UpdateStaticMeshes(FRHICommandListImmediate& RHICmdList, FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos, bool bReAddToDrawLists)
{
(......)
if (bReAddToDrawLists)
{
CacheMeshDrawCommands(RHICmdList, Scene, SceneInfos);
// 캐싱(Cache)Nanite드로우/렌더(Draw).
CacheNaniteDrawCommands(RHICmdList, Scene, SceneInfos);
}
}
void FPrimitiveSceneInfo::CacheNaniteDrawCommands(FRHICommandListImmediate& RHICmdList, FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
{
(......)
// 순회(Traverse/Iterate)의 정보 , 개 Nanite드로우/렌더(Draw).
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfos)
{
BuildNaniteDrawCommands(RHICmdList, Scene, PrimitiveSceneInfo);
}
(......)
}
void BuildNaniteDrawCommands(FRHICommandListImmediate& RHICmdList, FScene* Scene, FPrimitiveSceneInfo* PrimitiveSceneInfo)
{
(......)
for (int32 MeshPass = 0; MeshPass < ENaniteMeshPass::Num; ++MeshPass)
{
FNaniteDrawListContext NaniteDrawListContext(Scene->NaniteDrawCommandLock[MeshPass], Scene->NaniteDrawCommands[MeshPass]);
// 생성(Create)Nanite의 MeshProcessor.
FMeshPassProcessor* NaniteMeshProcessor = nullptr;
switch (MeshPass)
{
case ENaniteMeshPass::BasePass:
NaniteMeshProcessor = CreateNaniteMeshProcessor(Scene, nullptr, &NaniteDrawListContext);
break;
case ENaniteMeshPass::LumenCardCapture:
NaniteMeshProcessor = CreateLumenCardNaniteMeshProcessor(Scene, nullptr, &NaniteDrawListContext);
break;
default:
check(false);
}
// 순회(Traverse/Iterate)정적 , 지원 Nanite렌더링(Render)의 Nanite드로우/렌더(Draw).
int32 StaticMeshesCount = PrimitiveSceneInfo->StaticMeshes.Num();
for (int32 MeshIndex = 0; MeshIndex < StaticMeshesCount; ++MeshIndex)
{
FStaticMeshBatchRelevance& MeshRelevance = PrimitiveSceneInfo->StaticMeshRelevances[MeshIndex];
FStaticMeshBatch& Mesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
if (MeshRelevance.bSupportsNaniteRendering)
{
uint64 BatchElementMask = ~0ull;
// MeshProcessor추가(Add)회 , 의 단계/스텝 의 , .
NaniteMeshProcessor->AddMeshBatch(Mesh, BatchElementMask, Proxy);
FNaniteCommandInfo CommandInfo = NaniteDrawListContext.GetCommandInfoAndReset();
PrimitiveSceneInfo->NaniteCommandInfos[MeshPass].Add(CommandInfo);
const uint32 MaterialDepthId = CommandInfo.GetMaterialId();
const uint32 SectionIndex = Mesh.SegmentIndex;
PrimitiveSceneInfo->NaniteMaterialIds[MeshPass][SectionIndex] = MaterialDepthId;
}
}
NaniteMeshProcessor->~FMeshPassProcessor();
}
(......)
}Nanite::DrawBasePass, BuildNaniteMaterialPassCommands 및 SubmitNaniteMaterialPassCommand의 두 가지 중요한 인터페이스를 계속 분석해 보겠습니다.
// Engine\Source\Runtime\Renderer\Private\Nanite\NaniteRender.cpp
// Nanite머티리얼(Material)Pass의
static void BuildNaniteMaterialPassCommands(
FRHICommandListImmediate& RHICmdList,
const FStateBucketMap& NaniteDrawCommands,
TArray<FNaniteMaterialPassCommand, SceneRenderingAllocator>& OutNaniteMaterialPassCommands)
{
OutNaniteMaterialPassCommands.Reset(NaniteDrawCommands.Num());
FGraphicsMinimalPipelineStateSet GraphicsMinimalPipelineStateSet;
const int32 MaterialSortMode = GNaniteMaterialSortMode;
// 순회(Traverse/Iterate)Nanite드로우/렌더(Draw), 의 FNaniteMaterialPassCommand.
for (auto& Command : NaniteDrawCommands)
{
// FNaniteMaterialPassCommand인스턴스 .
FNaniteMaterialPassCommand PassCommand(Command.Key);
Experimental::FHashElementId SetId = NaniteDrawCommands.FindId(Command.Key);
int32 DrawIdx = SetId.GetIndex();
PassCommand.MaterialDepth = FNaniteCommandInfo::GetDepthId(DrawIdx);
// 을(를) 활용하여 렌더링(Render)상태 의 정렬 의 .
if (MaterialSortMode == 2 && GRHISupportsPipelineStateSortKey)
{
const FMeshDrawCommand& MeshDrawCommand = Command.Key;
const FGraphicsMinimalPipelineStateInitializer& MeshPipelineState = MeshDrawCommand.CachedPipelineId.GetPipelineState(GraphicsMinimalPipelineStateSet);
FGraphicsPipelineState* PipelineState = PipelineStateCache::GetAndOrCreateGraphicsPipelineState(RHICmdList, MeshPipelineState.AsGraphicsPipelineStateInitializer(), EApplyRendertargetOption::DoNothing);
if (PipelineState)
{
const uint64 StateSortKey = PipelineStateCache::RetrieveGraphicsPipelineStateSortKey(PipelineState);
if (StateSortKey != 0)
{
PassCommand.SortKey = StateSortKey;
}
}
}
// 추가(Add)까지 커맨드 리스트 .
OutNaniteMaterialPassCommands.Emplace(PassCommand);
}
// 정렬 머티리얼(Material).
if (MaterialSortMode != 0)
{
OutNaniteMaterialPassCommands.Sort();
}
}
// 개 머티리얼(Material)채널/패스(Pass)드로우/렌더(Draw)
static void SubmitNaniteMaterialPassCommand(
const FMeshDrawCommand& MeshDrawCommand,
const float MaterialDepth,
const TShaderRef<FNaniteMaterialVS>& NaniteVertexShader,
const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet,
const uint32 InstanceFactor,
FRHICommandList& RHICmdList,
FMeshDrawCommandStateCache& StateCache)
{
// 드로우/렌더(Draw).
FMeshDrawCommand::SubmitDrawBegin(MeshDrawCommand, GraphicsMinimalPipelineStateSet, nullptr, 0, InstanceFactor, RHICmdList, StateCache);
// Nanite드로우/렌더(Draw)는 을(를) 활용하여 의 VS, 렌더링(Render)의 머티리얼(Material)뎁스 .
{
FNaniteMaterialVS::FParameters Parameters;
Parameters.MaterialDepth = MaterialDepth;
SetShaderParameters(RHICmdList, NaniteVertexShader, NaniteVertexShader.GetVertexShader(), Parameters);
}
// 드로우/렌더(Draw).
FMeshDrawCommand::SubmitDrawEnd(MeshDrawCommand, InstanceFactor, RHICmdList);
}그런데 이상한 점은 BasePass를 그릴 때 VS만 지정되고 PS는 지정되지 않는다는 점입니다. 그렇다면 PS는 어디에 설정되어 있습니까? 아니면 원래 비어 있습니까? 진실을 찾기 위해 우리는 RenderDoc 프레임 캡처 분석을 사용했으며 PS가 여전히 전통적인 BasePassPixelShader를 사용하고 이 단계 이후에 렌더링된 GBuffer가 기본적으로 전통적인 것과 동일하다는 것을 발견했습니다.

왼쪽 위: 렌더링 화면, 오른쪽 위: GBufferA, 왼쪽 아래: GBufferB, 오른쪽 아래: GBufferC
BasePass를 렌더링하는 과정에서 Nanite는 재질을 Pass로 제출합니다. 이는 이전에 렌더링된 재질 범위 텍스처와 재질 깊이를 사용하여 빠른 제거를 수행할 수 있음을 의미합니다. 다음 두 장의 사진을 예로 들어보세요.


위의 첫 번째 그림에서 재료 영역을 렌더링할 때 재료 깊이와 재료 범위를 기반으로 픽셀을 빠르게 판단하고 제거합니다. 두 번째 그림에 표시된 것처럼 빨간색 상자는 해당 픽셀이 포함된 모든 픽셀이 재료 범위 감지를 통과하지 못해 버텍스 셰이더에 의해 완전히 폐기됨을 나타내고, 녹색 픽셀은 깊이 테스트 및 재료 범위 테스트를 통과하여 GBuffer 출력을 수행하기 위해 PS로 전송됨을 나타냅니다.
6.4.3.7 나나이트 빛과 그림자
Nanite의 빛과 그림자 계산은 RenderLights 인터페이스에서 기존의 빛과 그림자와 혼합됩니다.
// Engine\Source\Runtime\Renderer\Private\LightRendering.cpp
void FDeferredShadingSceneRenderer::RenderLights(
FRDGBuilder& GraphBuilder,
FMinimalSceneTextures& SceneTextures,
const FTranslucencyLightingVolumeTextures& TranslucencyLightingVolumeTextures,
FRDGTextureRef LightingChannelsTexture,
FSortedLightSetSceneInfo& SortedLightSet)
{
(......)
const FSimpleLightArray &SimpleLights = SortedLightSet.SimpleLights;
const TArray<FSortedLightSceneInfo, SceneRenderingAllocator> &SortedLights = SortedLightSet.SortedLights;
const int32 AttenuationLightStart = SortedLightSet.AttenuationLightStart;
const int32 SimpleLightsEnd = SortedLightSet.SimpleLightsEnd;
(......)
{
RDG_EVENT_SCOPE(GraphBuilder, "DirectLighting");
if (ViewFamily.EngineShowFlags.DirectLighting &&
Strata::IsStrataEnabled() && Strata::IsClassificationEnabled())
{
// 갱신(Update)스텐실 버퍼(Stencil Buffer), 로 의 Pass오직 플래그 회 /의 레벨 머티리얼(Material).
Strata::AddStrataStencilPass(GraphBuilder, Views, SceneTextures);
}
(......)
// 그림자(Shadow)라이팅 .
if(ViewFamily.EngineShowFlags.DirectLighting)
{
RDG_EVENT_SCOPE(GraphBuilder, "NonShadowedLights");
(......)
}
// 그림자(Shadow)라이팅 .
{
RDG_EVENT_SCOPE(GraphBuilder, "ShadowedLights");
(......)
// 드로우/렌더(Draw)그림자(Shadow) 및 라이팅 함수 의 라이트(Light).
for (int32 LightIndex = AttenuationLightStart; LightIndex < SortedLights.Num(); LightIndex++)
{
(......)
if (bDrawShadows)
{
INC_DWORD_STAT(STAT_NumShadowedLights);
(......)
else // (OcclusionType == FOcclusionType::Shadowmap)
{
(......)
// 그림자(Shadow)텍스처(Texture).
ClearShadowMask(ScreenShadowMaskTexture);
// 렌더링(Render)그림자(Shadow).
RenderDeferredShadowProjections(GraphBuilder, SceneTextures, TranslucencyLightingVolumeTextures, &LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, bInjectedTranslucentVolume);
}
bUsedShadowMaskTexture = true;
}
(......)
if (bDirectLighting)
{
const bool bRenderOverlap = false;
// 렌더링(Render)개 라이트(Light).
RenderLight(GraphBuilder, SceneTextures, &LightSceneInfo, ScreenShadowMaskTexture, LightingChannelsTexture, bRenderOverlap);
}
(......)
}
}
}
}UE5의 RenderLights 처리 로직은 UE4의 처리 로직과 매우 유사하므로 Strata 템플릿의 초기화만 추가됩니다. 계속해서 RenderLight의 논리를 살펴보겠습니다.
void FDeferredShadingSceneRenderer::RenderLight(
FRHICommandList& RHICmdList,
const FViewInfo& View,
const FLightSceneInfo* LightSceneInfo,
FRHITexture* ScreenShadowMaskTexture,
FRHITexture* LightingChannelsTexture,
bool bRenderOverlap, bool bIssueDrawEvent)
{
(......)
// 렌더링(Render)라이트(Light)의 인터페이스 .
auto RenderInternalLight = [&](bool bStrataFastPath)
{
(......)
// 설정(Set)Strata뎁스/스텐실 버퍼(Depth/Stencil Buffer).
if (Strata::IsStrataEnabled() && Strata::IsClassificationEnabled())
{
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<
false, CF_Always,
true, CF_Equal, SO_Keep, SO_Keep, SO_Keep,
true, CF_Equal, SO_Keep, SO_Keep, SO_Keep,
Strata::StencilBit, 0x0>::GetRHI();
}
else
{
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
}
(......)
if (LightProxy->GetLightType() == LightType_Directional)
{
(......)
else
{
(......)
FDeferredLightPS::FPermutationDomain PermutationVector;
(......)
// 추가(Add)Strata(레벨 )정렬
PermutationVector.Set< FDeferredLightPS::FStrata >(Strata::IsStrataEnabled());
PermutationVector.Set< FDeferredLightPS::FStrataFastPath >(Strata::IsStrataEnabled() && Strata::IsClassificationEnabled() && bStrataFastPath);
TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
(......)
}
(......)
// 설정(Set)Strata.
RHICmdList.SetStencilRef(bStrataFastPath ? Strata::StencilBit : 0u);
// 풀스크린(Full Screen)드로우/렌더(Draw).
DrawRectangle(
RHICmdList,
0, 0,
View.ViewRect.Width(), View.ViewRect.Height(),
View.ViewRect.Min.X, View.ViewRect.Min.Y,
View.ViewRect.Width(), View.ViewRect.Height(),
View.ViewRect.Size(),
GetSceneTextureExtent(),
VertexShader,
EDRF_UseTriangleOptimization);
}
else // (로컬 라이트(Light))
{
(......)
TShaderMapRef<TDeferredLightVS<true> > VertexShader(View.ShaderMap);
// 는 라이트(Light).
const bool bCameraInsideLightGeometry = ((FVector)View.ViewMatrices.GetViewOrigin() - LightBounds.Center).SizeSquared() < FMath::Square(LightBounds.W * 1.05f + View.NearClippingDistance * 2.0f)
|| !View.IsPerspectiveProjection();
// 설정(Set)바인딩(Bind)래스터라이제이션 및 뎁스 상태 , 내에서 bCameraInsideLightGeometry.
SetBoundingGeometryRasterizerAndDepthState(GraphicsPSOInit, View, bCameraInsideLightGeometry);
(......)
else
{
(......)
// Strata.
PermutationVector.Set< FDeferredLightPS::FStrata >(Strata::IsStrataEnabled());
PermutationVector.Set< FDeferredLightPS::FStrataFastPath >(Strata::IsStrataEnabled() && Strata::IsClassificationEnabled() && bStrataFastPath);
TShaderMapRef< FDeferredLightPS > PixelShader( View.ShaderMap, PermutationVector );
(......)
}
(......)
RHICmdList.SetStencilRef(bStrataFastPath ? Strata::StencilBit : 0u);
(......)
// 에 따라 타입/유형 의 로컬 의 드로우/렌더(Draw).
if( LightProxy->GetLightType() == LightType_Point ||
LightProxy->GetLightType() == LightType_Rect )
{
StencilingGeometry::DrawSphere(RHICmdList);
}
else if (LightProxy->GetLightType() == LightType_Spot)
{
StencilingGeometry::DrawCone(RHICmdList);
}
}
};
// 호출(Call)회 Strata의 라이트(Light)드로우/렌더(Draw)(UE4의 라이트(Light)계산/산출(Calculate)).
RenderInternalLight(false);
// 만약 활성화(Enable)Strata, 이면 호출(Call)회 Strata의 라이트(Light)드로우/렌더(Draw).
if (Strata::IsStrataEnabled() && Strata::IsClassificationEnabled())
{
RenderInternalLight(true);
}
}조명 셰이더 코드는 Strata에 대한 지원만 추가하므로 여기서는 설명하지 않습니다.
게다가, UE5의 그림자 계산은 VSM(Virtual Shadow Map) 기술을 사용한다는 점을 언급할 가치가 있습니다. VSM(Virtual Shadow Map) 기술은 일관된 고해상도 그림자, 영화 품질의 자산 및 대규모 오픈 월드에 대한 동적 조명을 제공하는 데 사용되는 새로운 그림자 캐스팅 방법입니다.
VSM은 2007년에 Markus Giegl과 다른 사람들에 의해 처음 제안되었으며 쿼리된 가상 그림자 맵(Queried Virtual Shadow Maps)이라는 논문을 출판한 후 향상된 Fitted Virtual Shadow Maps를 출판했습니다. 수년 후인 2015년에 Olsson Ola와 다른 사람들은 Clusterred와 같은 렌더링 기술을 결합하여 많은 조명에 대한 보다 효율적인 가상 그림자 맵이라는 논문을 출판했습니다.
이 기술의 핵심은 적응형 방식으로 섀도우 맵을 렌더링하여 이전 프레임의 정보를 저장하지 않고 필요할 때 더 높은 섀도우 맵 해상도를 생성하여 완전히 동적인 장면에 적합하게 만드는 것입니다. 따라서 하위 픽셀 정확도로 섀도우 맵 쿼리를 보장하여 기존 섀도우 맵의 섀도우 맵 및 원근 앨리어싱을 제거할 수 있습니다.
VSM은 Virtual Tiled Shadow Mapping 기술을 사용합니다. 알고리즘은 다음과 같이 설명됩니다.
GPU가 지원할 수 있는 최대 텍스처 해상도를 할당합니다(초기에는 일반적으로
, 현재는 이상). 섀도우 맵을 n \times n) (예:
) 동일한 크기의 블록으로 나눕니다. 각 청크에 대해:
그림자를 그림자 맵 텍스처로 렌더링합니다(타일링된 조명 프러스텀를 사용하여 클리핑하고 이전에 타일링된 그림자 맵을 덮어씀).
- 현재 그림자 맵으로 덮힌 장면 부분을 마스크하려면 즉시 사용하십시오.

위: 기존 섀도우 맵을 사용하면 심각한 앨리어싱 문제가 발생했습니다. 하단: 32x32 2048x2048 QVSM을 사용하면 그림자 정확도가 크게 향상됩니다.
UE5 구현에서는 합리적인 메모리 비용으로 고성능을 유지하기 위해 VSM의 최대 해상도는
또한 UE5는 더 높은 그림자 맵 해상도를 얻기 위해 CSM을 대체하기 위해 방향성 조명 그림자에 ClipMap 기술을 채택했습니다. ClipMap은 1998년 Christopher C. Tanner 등에 의해 처음 제안되었습니다. 논문에서 클립맵: 가상 밉맵. 이 기술의 핵심은 섀도우맵 밉맵의 크기에 상한선을 설정하는 것입니다. 이 상한을 초과하는 Mipmap은 잘립니다(메모리에 로드되지 않음).

이는 클립맵 스택(스택)과 클립맵 피라미드(피라미드)를 형성합니다.

카메라(시야)가 변경되면 리매핑 클립맵 스택의 영역을 수정해야 하며 리매핑 후 클립맵 데이터를 로드하여 클립맵 스택 부분이 시야와 일치하도록 해야 합니다.

시야 변경 후 Clipmap 업데이트의 개략도. 여기서는 성능 향상을 위해 Toroidal Update를 사용합니다.
6.4.4 나나이트 요약
Nanite 기술에는 렌더링 전 사전 처리, 렌더링 중 다양한 수준의 세분화된 클리핑, 래스터라이제이션, BasePass 및 조명 단계가 포함됩니다. 이 기간 동안 수많은 데이터 구조, 알고리즘, 렌더링 기술 및 해당 최적화 기술이 적용되었습니다.
Nanite는 이전에 보고된 Geometry Image 기술을 사용하지 않고 Cluster, ClusterGroup 및 Page를 기반으로 다양한 수준의 대략적인 표현을 사용합니다. 이 기술은 사전 계산을 최대한 활용하여 단순화된 데이터와 해당 저장 데이터를 미리 구축할 수 있으므로 렌더링 중에 Nanite 데이터를 보다 효율적으로 재구성, 색인화, 처리 및 렌더링할 수 있습니다. 그러나 Nanite는 정적 메쉬만 지원한다는 단점도 있습니다.
Nanite의 렌더링 단계는 기존 렌더링 파이프라인에 산재되어 있으며 GPUScene 업데이트, 스트림 관리, 자르기, 래스터라이제이션, BasePass 및 Readback과 같은 단계를 연속적으로 거쳐 GPU 기반 렌더링 파이프라인의 성능을 최대한 활용하고 마지막으로 Nanite의 데이터를 RenderTrage에 제공합니다. 각 단계는 여러 단계, 렌더링 기술 및 최적화 기술을 거쳤습니다. 예를 들어, 인스턴스별, 클러스터별, 삼각형별 등 다양한 자르기 세분성이 있으며, 모두 CPU 및 GPU IO를 줄이기 위해 GPU로 구동됩니다. 래스터라이제이션 단계에서는 기본적으로 CS 소프트 래스터 + PS 하드 래스터의 혼합 관계를 사용합니다. 여기서 CS 소프트 래스터는 작은 영역의 삼각형 래스터라이제이션(쿼드 오버드로 방지)를 담당하고 PS는 더 큰 영역의 삼각형 래스터라이제이션를 담당합니다. 래스터라이제이션 후에는 GBuffer 점유율과 대역폭 소비를 줄이기 위해 삼각형 ID와 깊이만 출력됩니다(Visibility Buffer 기술). BasePass 출력 결과는 GBufferA, GBufferB...에 저장된 기존 결과와 동일합니다. 후속 조명 계산 단계에서는 Strata 모드에 대한 지원을 추가하는 것 외에도 다른 조명 로직은 기본적으로 기존과 동일합니다.
그림자의 품질을 향상하고 그림자 소비를 최적화하기 위해 UE5는 VSM 및 Clipmap 기술을 사용하여 효과와 소비의 균형을 맞추는 실시간 그림자 렌더링을 달성합니다.
다음 장은 UE5 스페셜 파트 2에서 소개됩니다:
6.5루멘
6.6 기타 렌더링 기술
6.7 이 기사 요약
특별 지침
모든 참고문헌의 저자에게 감사드립니다. 일부 사진은 참고 자료와 인터넷에서 가져온 것이므로 삭제되었습니다.
이 시리즈의 기사는 저자가 직접 작성한 것이며 블로그에만 게시됩니다. 이 글의 링크를 공유하셔도 좋지만 무단 전재는 허용되지 않습니다!
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
📚 참고 자료 및 공식 링크 (References)
- Unreal Engine Source
- Rendering and Graphics
- Materials
- Graphics Programming
- New Rendering Features
- Nanite Virtualized Geometry
- Lumen Global Illumination and Reflections
- Lumen Technical Details
- Behind the scenes of “Lumen in the Land of Nanite” | Unreal Engine 5
- Unreal Engine 5 Early Access Release Notes
- Virtual Shadow Maps
- 언리얼 엔진(Unreal Engine)5
- 언리얼 엔진(Unreal Engine)5
- Unreal Engine 5 Early-Access?
- Unreal Engine 5 How to Nanite Skeletal Mesh
- UE5 Nanite구현개요 분석
- UE5 Nanite 및 Lumen 기술
- 0x20:UE5 Nanite 소스 코드분석 및 고찰렌더링:BVH 및 Cluster Culling
- Learning Boundary Edges for 3D-Mesh Segmentation
- Family of Graph and Hypergraph Partitioning Software
- METIS Three Phases Coarsening Partitioning Uncoarsening
- METIS: Unstructured Graph Partitioning and Sparse Matrix Ordering System
- Dynamic LZW for Compressing Large Files
- Geometry images
- New Quadric Metric for Simplifying Meshes with Appearance Attributes
- Lock Mesh Edges
- Wave-Programming-D3D12-Vulkan
- GPU-Driven Rendering Pipeline
- Optimizing the Graphics Pipeline with Compute
- A Macro View of Nanite
- DynamicOcclusionWithSignedDistanceFields
- Queried Virtual Shadow Maps
- Fitted Virtual Shadow Maps
- More efficient virtual shadow maps for many lights
- The clipmap: a virtual mipmap