프로시저럴 콘텐츠 제너레이션(Procedural Content Generation, PCG) 프레임워크는 언리얼 엔진에서 자체 프로시저럴 콘텐츠와 툴을 제작할 수 있는 툴세트입니다. 테크니컬 아티스트와 디자이너는 PCG GPU 프로세싱을 활용하여 많은 PCG 프로세싱 작업을 GPU로 직접 전송할 수 있으며, 그 결과 CPU의 리소스를 확보할 수 있습니다.
PCG GPU 프로세싱은 포인트 프로세싱, 런타임 생성(Runtime Generation) 및 스태틱 메시 스폰과 같은 다양한 작업에 효율적입니다.
향후 GPU 실행을 지원하는 노드가 추가될 예정입니다.
GPU를 타깃으로 설정한 노드는 PCG 그래프에서 GPU로 라벨이 지정됩니다. 연결된 GPU 노드의 서브셋은 GPU에서 효율적으로 함께 실행되며, 이를 계산 그래프(Compute Graph)라고 합니다.
번호 | Description |
1 | CPU와 GPU 간의 데이터 전송입니다. 이러한 포인트는 퍼포먼스 비용을 나타냅니다. |
2 | 함께 실행되는 GPU 실행 노드입니다. |
데이터에 GPU 하드웨어를 완전히 활용할 수 있는 충분한 포인트가 있는 경우에 GPU를 타기팅하면 CPU 실행보다 퍼포먼스가 향상될 수 있습니다. 또한, GPU 지원 Static Mesh Spawner 노드와 연결된 일련의 GPU 노드는 스태틱 메시 스폰을 위한 빠른 패스를 제공합니다.
CPU와 GPU 간에 데이터를 전송하고, 실행을 위한 계산 그래프를 준비하는 데 CPU 비용이 발생한다는 점을 명심해야 합니다. 따라서 GPU 실행 기능을 사용하는 최적의 방법은 GPU 지원 노드를 함께 그룹화하여 각 컴퓨팅 그래프에 전송되고 나오는 데이터의 양을 최소화하는 것입니다.
지원되는 노드
커스텀 HLSL 노드
커스텀 HLSL 노드는 사용자가 생성한 HLSL 소스 코드를 통해 스크립팅되는 임의의 데이터 처리 작업에 사용될 수 있습니다. 소스 코드는 컴퓨팅 셰이더에 주입되어 GPU 하드웨어에서 데이터 엘리먼트에 대해 병렬로 실행됩니다.
이 노드는 GPU 하드웨어에 대한 로우 레벨 액세스를 제공하며 고급 사용자가 사용할 수 있습니다.
옵션
| 설명 |
커널 타입(Kernel Type) | 노드 행동에 대한 프리셋을 선택합니다. 사용 가능한 옵션은 아래의 커스텀 HLSL 노드 커널 타입 섹션을 참조하세요. |
Input 핀 | 입력으로 받을 데이터를 정의합니다. 롤아웃을 열면 다음과 같은 옵션이 제공됩니다.
|
Output 핀 | 노드에서 출력되는 데이터를 정의합니다. Input 핀과 동일한 옵션과 출력 데이터 구성을 위한 추가 옵션이 포함되어 있습니다. 이러한 옵션에 대해서는 핀 구성 섹션에서 다루고 있습니다. 참고하세요. |
커널 소스 오버라이드(Kernel Source Override) | 셰이더 소스(Shader Source) 필드를 UComputeSource 에셋으로 대체하는 데 사용됩니다. |
추가 소스(Additional Sources) | 커스텀 HLSL 노드와 함께 추가로 UComputeSource 에셋을 번들링할 수 있도록 허용합니다. |
쓰여지지 않은 핀 데이터 오류 뮤트(Mute Unwritten Pin Data Errors) | 초기화되지 않았을 가능성이 있는 데이터가 있는 출력 핀의 경고를 뮤트합니다. |
시드(Seed) | 랜덤 생성을 구동하는 데 사용되는 시드 값을 정의합니다. |
쿠킹된 HLSL 덤프(Dump Cooked HLSL) | 쿠킹된 HLSL 데이터가 디버깅용으로 생성될 때 해당 데이터를 로그에 출력합니다. |
데이터 설명 덤프(Dump Data Descriptions) | 입력 및 출력 데이터의 데이터 설명이 디버깅용으로 생성될 때 해당 데이터 설명을 로그에 출력합니다. |
셰이더 디버그 값 출력(Print Shader Debug Values) | 셰이더 코드에서 간단한 디버그 로깅을 제공합니다. 해당 내용은 커스텀 HLSL 디버깅하기에서 다룹니다. 섹션을 참조하세요. |
HLSL 소스 에디터
HLSL 소스 에디터(HLSL Source Editor)는 커스텀 HLSL 노드를 빠르게 작성할 수 있는 기능을 제공합니다. PCG 그래프 에디터의 창(Window)->HLSL 소스 에디터(HLSL Source Editor)에서 찾거나 커스텀 HLSL 노드를 선택한 후 노드 세팅에서 소스 에디터 열기(Open Source Editor) 버튼을 클릭하면 됩니다.
HLSL 소스 에디터는 세 부분으로 구성되어 있습니다.
선언(Declarations) 패널
셰이더 함수(Shader Functions)
셰이더 소스(Shader Source)
선언 패널은 셰이더 코드 작성 시 API 레퍼런스 역할을 합니다. 선언은 커널 타입 및 입력/출력 핀 세팅과 같은 커스텀 HLSL 노드 세팅에서 자동으로 생성됩니다.
셰이더 함수 필드에서는 작성자가 셰이더 소스에서 호출할, 재사용할 수 있는 함수를 생성할 수 있습니다.
셰이더 소스 필드는 커널 구현을 위한 주요 엔트리 포인트를 구현하는 곳입니다.
커스텀 HLSL 노드 커널 타입
커널 타입(Kernel Type)은 노드의 행동에 대한 프리셋을 정의합니다.
포인트 프로세서
포인트 프로세서(Point Processor) 커널 타입은 포인트 수정에 적합합니다. 기본 입력 및 출력 핀이 포인트 타입이어야 하며, 각 포인트에 대해 HLSL 코드를 한 번씩 실행합니다. 기본 출력 핀을 통해 전송된 데이터는 주요 입력 핀과 레이아웃이 같습니다. 즉, 데이터의 수와 엘리먼트의 수가 똑같습니다.
기본 출력의 모든 포인트는 기본 입력에서 자동으로 초기화되므로 변경해야 하는 출력 어트리뷰트만 설정하면 됩니다.
또한, 포인트 프로세서(Point Processor)를 사용하여 입력 및 출력 핀을 생성할 수도 있는데, 이 핀들은 원하는 데이터 타입과 데이터/엘리먼트 수를 설정하려면 수동으로 환경설정해야 합니다.
포인트 제네레이터
포인트 제네레이터(Point Generator) 커널 타입은 포인트 세트를 생성하고 채우는 데 적합합니다. 기본 출력 핀이 포인트 타입이어야 하며, 각 포인트에 대해 HLSL 코드를 한 번씩 실행합니다.
이 커널 타입에는 다음과 같은 추가 옵션이 있습니다.
옵션
| 설명 |
포인트 수(Point Count)
| 생성되는 포인트 수를 결정합니다. 셰이더 코드는 생성된 각 포인트에서 실행됩니다. |
포인트 프로세서와 마찬가지로 포인트 제네레이터를 사용하여 입력 및 출력 핀을 생성할 수 있는데, 이 핀들은 원하는 데이터 타입과 데이터 또는 엘리먼트 수를 설정하려면 수동으로 환경설정해야 합니다.
커스텀(Custom)
커스텀 커널 타입은 고급 사용 사례를 위한 세밀한 컨트롤을 노출합니다. 다른 두 커널 타입과 달리 노드가 입력과 출력 간의 특정 관계를 가정하지 않기 때문에, 입력 또는 출력 핀에 필요한 세팅이 없습니다. 출력 데이터는 아래 문서에 설명된 출력 핀 세팅에서 환경설정해야 합니다. 셰이더 코드를 실행할 스레드 수도 환경설정해야 합니다.
이 커널 타입에는 다음과 같은 추가 옵션이 있습니다.
옵션 | 설명 |
디스패치 스레드 수(Dispatch Thread Count) | 셰이더 코드가 실행에 사용하는 스레드 수를 결정합니다. 다음과 같은 모드를 사용할 수 있습니다.
|
핀 구성
커널 타입에 의해 구동되지 않는 핀은 모두 수동으로 환경설정해야 합니다.
출력 핀의 경우, 데이터 크기와 레이아웃을 명시적으로 기술해야 하며, 이는 출력 핀 세팅의 GPU 프로퍼티(GPU Properties) 드롭다운에서 설정할 수 있습니다.
초기화 모드(Initialization Mode) | 이 핀의 출력 데이터가 초기화되는 방식을 설명합니다. 이 메뉴에는 다음과 같은 모드가 포함되어 있습니다.
|
초기화할 핀(Pins to Initialize From) | 이 핀의 데이터를 초기화할 입력 핀을 정의합니다. |
데이터 카운트 모드(Data Count Mode) | 데이터 오브젝트의 수를 정의합니다. 이 메뉴에는 다음과 같은 모드가 포함되어 있습니다.
|
데이터 다중성(Data Multiplicity) | 초기화할 핀이 여러 개 있는 경우 데이터 수를 결합합니다. 사용할 수 있는 모드
|
엘리먼트 수 모드(Element Count Mode) | 엘리먼트의 수를 정의합니다. 이 메뉴에는 다음과 같은 모드가 포함되어 있습니다.
|
엘리먼트 다중성(Element Multiplicity) | 초기화할 핀이 여러 개 있는 경우 엘리먼트 수를 결합합니다. 사용할 수 있는 모드
|
어트리뷰트 상속 모드(Attribute Inheritance Mode) | 어트리뷰트 이름, 타입 및 값을 상속하는 방법을 정의합니다. 메뉴에는 다음과 같은 모드가 포함되어 있습니다.
|
생성할 어트리뷰트(Attributes to Create) | 출력 데이터에 생성할 새 어트리뷰트 목록을 정의합니다. |
커스텀 HLSL 디버깅하기
디버그 디스플레이(디폴트 단축키 'D') 및 검사(디폴트 단축키 'A') 기능은 GPU 노드에서 작동하며 GPU 노드를 통해 흐르는 데이터를 검사할 수 있습니다.
커스텀 HLSL 노드에서 셰이더 디버그 값 프린트(Print Shader Debug Values) 옵션을 토글하여 커스텀 셰이더 코드를 디버깅할 수도 있습니다. 이렇게 하면 실행 중에 로깅되는 버퍼에 float 값을 쓰는 데 사용할 수 있는 새로운 WriteDebugValue 함수가 노출됩니다. 버퍼 크기는 디버그 버퍼 크기(Debug Buffer Size) 프로퍼티로 제어합니다.
예시
예시 1: 사인파를 사용한 높이 오프셋
아래 예시에서는 포인트 프로세서를 사용하여 일련의 포인트에 사인파 기반 높이 오프셋을 적용합니다.
HLSL 소스 에디터 창의 셰이더 소스(Shader Source) 필드에 다음 코드가 추가됩니다.
// Get the position of the incoming point from input pin ‘In’.
float3 Position = In_GetPosition(In_DataIndex, ElementIndex);
// Compute a sine wave with amplitude 500cm and wavelength 400cm.
const float Wave = 500.0f * sin(Position.x / 400.0f);
// Add the wave to the Z coordinate of the point’s position.
Position.z += Wave;
// Write the offset position to the output point on pin ‘Out’.
예시 2: 어트리뷰트 생성하기
아래 예시에서는 포인트 제네레이터를 사용하여 포인트 그리드를 생성하고 어트리뷰트 세트를 사용하여 그리드의 높이를 제어합니다.
HLSL 소스 에디터 창의 셰이더 소스 필드에 다음 코드가 추가됩니다.
// Get PCG Component bounds.
const float3 BoundsMin = GetComponentBoundsMin();
const float3 BoundsMax = GetComponentBoundsMax();
// Get the current point position in a 2D grid, based on the
// number of points and the component bounds.
float3 Position = CreateGrid2D(ElementIndex, NumPoints, BoundsMin, BoundsMax);
Position.z += InHeight_GetFloat(0, 0, 'GridHeight');
// Set the point's position.
제공된 Get 및 Set 함수를 사용하여 셰이더 코드에서 어트리뷰트에 액세스하고, 이름을 아포스트로피로 감싸 어트리뷰트를 쿼리할 수 있습니다. 예를 들면 'GridHeight'와 같은 식입니다.
예시 3: 랜드스케이프에 랜덤 메시 스폰하기
커스텀 HLSL 노드는 일련의 연산을 실행할 수도 있습니다.
아래 예시에서 셰이더 코드는 다음 연산을 수행합니다.
랜드스케이프에 여러 포인트를 생성합니다.
각 포인트에 랜덤 위치 조정을 적용합니다.
포인트 위치 설정하기
각 포인트에 랜덤 시드 값을 씁니다.
스태틱 메시 목록이 포함된 어트리뷰트 세트를 읽고 각 포인트에 랜덤 메시를 할당합니다.
커스텀 HLSL 노드의 다운스트림은 GPU 실행이 활성화되고 메시 어트리뷰트가 'MeshPath'로 설정된 Static Mesh Spawner입니다.
GPU에서 스트링, 이름, 소프트 오브젝트 경로, 소프트 클래스 경로 타입 어트리뷰트는 스트링 키로 변환되어 각 스트링을 고유하게 식별합니다.
HLSL 소스 창의 셰이더 소스 필드에 다음 코드가 추가됩니다.
// Get generation volume bounds
const float3 BoundsMin = GetComponentBoundsMin();
const float3 BoundsMax = GetComponentBoundsMax();
// Compute a position on a 2D grid within the volume.
float3 Pos = CreateGrid2D(ElementIndex, NumPoints, BoundsMin, BoundsMax);
// Initialize the random seed from the position.
uint Seed = ComputeSeedFromPosition(Pos);
Static Mesh Spawner 노드
노드 세팅에서 GPU에서 실행(Execute on GPU) 옵션을 토글하여 Static Mesh Spawner 노드가 GPU에서 실행되도록 할 수 있습니다.
프로시저럴 인스턴싱
Static Mesh Spawner를 GPU에서 실행하도록 설정하면 메시 인스턴스를 전적으로 GPU에 설정하여 CPU 시간과 메모리를 절약할 수 있습니다. 이는 프로시저럴 인스턴스드 스태틱 메시 컴포넌트(Procedurally Instanced Static Mesh Component)를 사용합니다. 이 방법은 메시를 스폰하는 매우 효율적인 패스가 될 수 있지만, 실험단계이며 다음과 같은 단점이 있습니다.
인스턴스는 어떤 방식으로든 유지되거나 저장되지 않습니다. 런타임에 GPU 메모리에만 존재합니다.
따라서 주요 사용 사례는 런타임 생성(Runtime Generation)입니다.
스태틱 베이크드 라이팅 및 HLOD에는 지속되는 인스턴스 정보가 필요하며 현재로서는 지원되지 않습니다.
다음과 같이 현재 CPU가 인스턴스 데이터에 액세스해야 하는 일부 기능은 지원되지 않습니다.
콜리전/피직스
내비게이션
레이 트레이싱
디스턴스 필드 라이팅에 영향 주기
GPU 구현은 실험단계이며 모든 Static Mesh Spawner 기능이 지원되는 것은 아닙니다.
메시 선택 툴 타입
GPU에서 실행할 때 지원되는 메시 선택 툴은 다음과 같으며, 인스턴스 할당 방식 때문에 행동에 약간의 차이가 있습니다.
가중치 적용(PCGMeshSelectorWeighted)
CPU 구현과 비슷하게 이 모드는 입력 포인트 랜덤 시드와 환경설정된 선택 가중치를 사용하여 각 인스턴스에 대한 메시를 랜덤으로 선택합니다. 이러한 메시는 어트리뷰트에 의해 구동되는 것이 아니며, 노드에서 설정해야 합니다.
시스템은 가중치를 사용하여 각 메시에 할당할 인스턴스 수를 결정합니다. 하나 이상의 프리미티브에 대한 할당량이 포화되어 인스턴스가 손실될 가능성을 최소화하기 위해 휴리스틱 기법을 기반으로 초과 할당이 이루어집니다.
이 모드는 포인트의 시드(Seed) 어트리뷰트가 잘 초기화되어 있어야 제대로 작동합니다. 예를 들어, 제공된 셰이더 함수 `ComputeSeedFromPosition()`을 사용하여 포인트를 초기화할 수 있습니다. 모든 포인트 시드가 동일한 값으로 설정되면 모든 포인트에 대해 동일한 선택이 이루어지고, 예상 할당량을 초과하여 결과에서 누락되는 인스턴스가 발생할 수 있습니다.
어트리뷰트별(PCGMeshSelectorByAttribute)
PCGMeshSelectorWeightedByCategory 같은 다른 메시 선택 툴 타입은 현재 GPU에서 실행할 때 지원되지 않습니다.
최종 할당된 인스턴스 수는 생성된 프로시저럴 인스턴스드 스태틱 메시 컴포넌트를 선택하고 인스턴스 수(Num Instances) 프로퍼티를 확인하여 검사할 수 있습니다.
인스턴스 데이터 패킹
CPU 실행과 마찬가지로 어트리뷰트도 인스턴스 데이터에 패킹할 수 있습니다.
시스템은 GPU 실행 전에 얼마나 많은 어트리뷰트를 패킹할지 알아야 하므로, 어트리뷰트별 패커 타입(PCGInstanceDataPackerByAttribute)만 지원됩니다.
기타 노드
현재 GPU 실행을 지원하는 노드는 다음과 같습니다.
Copy Points
Attribute Partition
현재는 스트링(String), 소프트 오브젝트 패스(Soft Object Path) 또는 소프트 클래스 패스(Soft Class Path) 타입의 어트리뷰트에 대한 분할만 지원합니다.
Normal To Density
Data Count
Static Mesh Spawner
커스텀 HLSL
CPU 실행은 지원되지 않습니다.
소스 계산
소스 계산(Compute Source) 에셋을 사용하면 소스 코드를 더 쉽게 공유하고 노드 간 코드 중복을 줄일 수 있습니다.
이 에셋은 데이터 라벨 및 어트리뷰트 이름 같은 PCG 특정 구문과 구문 하이라이트를 사용하여 HLSL 소스 코드의 인라인 편집을 지원합니다.
소스 계산 에셋은 여러 소스 계산 간에 종속성 계층구조를 생성하는 추가 소스(Additional Sources) 프로퍼티를 사용하여 다른 소스 계산 에셋을 참조할 수도 있습니다.
데이터 라벨
데이터 라벨을 사용하여 커스텀 HLSL 소스에서 인덱스가 아닌 라벨로 데이터를 참조할 수 있습니다. 데이터 라벨은 접두사 PCG_DATA_LABEL이 붙은 태그를 통해 데이터에 전달됩니다.
다음과 같은 일부 노드는 출력 데이터에 자동으로 라벨을 지정합니다.
Get Texture Data
Get Virtual Texture Data
Generate Grass Maps
그래스 맵 생성하기
PCG는 지정된 랜드스케이프 머티리얼에서 랜드스케이프 그래스 레이어를 샘플링하여 런타임 프로시저럴 생성 워크플로를 지원할 수 있습니다.
랜드스케이프 머티리얼은 Landscape Grass Output 노드로 구성합니다. 랜드스케이프 머티리얼 구성에 대한 자세한 내용은 랜드스케이프 머티리얼을 참조하세요.
랜드스케이프 데이터를 Generate Grass Maps 노드에 연결합니다. 오버라이드를 사용하거나 제외를 통해 원하는 그래스 타입을 직접 선택합니다.
그래스 맵 텍스처를 샘플링합니다. 인덱스별로, 또는 자동으로 할당된 데이터 라벨을 기준으로 샘플링할 수 있습니다. HLSL 소스 에디터 창의 셰이더 소스 필드에 다음 코드가 추가됩니다.
float3 Min = GetComponentBoundsMin();
float3 Max = GetComponentBoundsMax();
float3 Position = CreateGrid2D(ElementIndex, NumPoints, Min, Max);
uint Seed = ComputeSeedFromPosition(Position);
Position.xy += (float2(FRand(Seed), FRand(Seed)) - 0.5) * 45.0;
Position.z = LS_GetHeight(Position);
float Density = FRand(Seed);
float Thresh = GrassMaps_SampleWorldPos('GSM_PCGGrass1', Position.xy).x;
GPU에서만 잔디 맵 텍스처를 샘플링하려는 경우, PCG 그래프에서 CPU로 리드백 건너뛰기(Skip Readback to CPU)로 토글하면 퍼포먼스가 크게 향상됩니다.
페인팅된 랜드스케이프 레이어:
결과 생성
PCG에서 버추얼 텍스처 사용하기
PCG는 프로시저럴 콘텐츠 제너레이션 워크플로의 일부로 버추얼 텍스처 사용을 지원합니다.
버추얼 텍스처 샘플링
랜드스케이프 데이터에서 버추얼 텍스처를 샘플링하여 높이 샘플링의 퍼포먼스를 향상할 수 있습니다.
예시 1: 랜드스케이프 데이터
버추얼 텍스처를 사용하여 랜드스케이프 데이터를 샘플링하려면, Get Landscape Data 노드 세팅에서 버추얼 텍스처 샘플링(Sample Virtual Textures)이 켜져 있는지 확인하세요.그래야 Landscape Data 노드에서 해당 랜드스케이프 머티리얼을 통해 제공되는 모든 버추얼 텍스처를 사용할 수 있습니다.
이 옵션은 GPU 샘플링에만 영향을 줍니다.
// Get Position and Height
float3 Position = CreateGrid2D(ElementIndex, NumPoints, GetComponentBoundsMin(), GetComponentBoundsMax());
Position.z = A_GetHeight(Position);
// Get Normal and Orientation
const float3 Normal = A_GetNormal(Position);
const FQuat Orientation = QuatFromNormal(Normal);
// Get Base Color
const float3 BaseColor = A_GetBaseColor(Position);
예시 2: 버추얼 텍스처 데이터
버추얼 텍스처를 샘플링하려면 월드에 런타임 버추얼 텍스처 컴포넌트를 쿼리합니다. 각각 런타임 버추얼 텍스처 에셋의 데이터 라벨이 태그된 버추얼 텍스처 데이터를 생성합니다.
float3 Position = CreateGrid2D(ElementIndex, NumPoints, GetComponentBoundsMin(), GetComponentBoundsMax());
// Sample virtual textures
bool bInsideVolume;
float3 BaseColor;
float Specular;
float Roughness;
float WorldHeight;
float3 Normal;
float Displacement;
버추얼 텍스처 프라이밍
생성 전에 버추얼 텍스처가 프라이밍되는지 확인해야 합니다. 그렇지 않으면 샘플링 결과가 부정확해질 수 있습니다.
버추얼 텍스처 프라이밍을 요청하려면, FPCGVirtualTexturePrimingInfo 타입의 그래프 파라미터를 PCG 그래프에 추가합니다. 이렇게 하면 다음과 같은 옵션이 노출됩니다.
버추얼 텍스처(Virtual Texture) | 프라이밍할 버추얼 텍스처를 정의합니다. |
그리드(Grid) | 그래프에서 버추얼 텍스처가 샘플링되는 가장 큰 그리드를 정의합니다. 버추얼 텍스처는 이 그리드에 의해 결정되는 생성 반경에 맞춰 프라이밍됩니다. |
월드 텍셀 크기(World Texel Size) | 프라이밍된 버추얼 텍스처에서 원하는 텍셀의 크기를 정의합니다. 이에 따라 어떤 밉맵 레벨을 프라이밍할지 결정됩니다. |
버추얼 텍스처 프라이밍은 콘솔 명령인 pcg.VirtualTexturePriming.Enable을 사용하여 제어할 수 있습니다. 이 기능은 pcg.VirtualTexturePriming.DebugDrawTexturePrimingBounds 명령을 사용하여 디버깅할 수 있습니다.