TECHARTNOMAD | TECHARTFLOWIO.COM

UNITY3D

Unity Rendering Graph System.

jplee 2024. 9. 18. 16:12

유니티 역시 언리얼 RDG 와 유사한 RGS 가 꽤 오래전에 추가 되었습니다. 다만 최근 국내 게임개발에서 복잡한 시스템이나 규모 있는 프로젝트는 주로 언리얼 엔진을 사용하고 있는 편이고 작은 규모의 게임은 유니티로 개발 되고 있는 듯 합니다. 그래서 유니티 코리아 및 기타 국내 포럼등에서 RGS 에 대한 프로모트가 없다보니 관련 된 내용이 비교적 적더군요. 이후 인덱스 페이지를 만들면서 유니티 RGS 에 대한 정보들도 취합을 해 보는 것이 좋을 듯 합니다.


유니티 RGS 메모. 유니티 도큐먼트 발췌.

Rendering Graph System.

 

https://portal.productboard.com/unity/1-unity-platform-rendering-visual-effects/c/2157-render-graph-integration?utm_medium=social&utm_source=starter_share

 

portal.productboard.com

 


렌더 그래프 시스템의 장점

효율적인 메모리 관리

리소스 할당을 수동으로 관리할 때는 모든 렌더링 기능이 동시에 활성화되어 있는 시나리오를 고려해야 하므로 최악의 시나리오에 대비하여 할당해야 합니다. 

특정 렌더링 기능이 활성화되지 않은 경우 해당 기능을 처리할 리소스는 있지만 렌더링 파이프라인은 이를 사용하지 않습니다. 렌더 그래프는 프레임이 실제로 사용하는 리소스만 할당합니다. 따라서 렌더 파이프라인의 메모리 사용량이 줄어들고 리소스 할당을 처리하기 위해 복잡한 로직을 만들 필요가 없습니다. 효율적인 메모리 관리의 또 다른 이점은 렌더 그래프가 리소스를 효율적으로 재사용할 수 있기 때문에 렌더 파이프라인을 위한 기능을 만드는 데 더 많은 리소스를 사용할 수 있다는 점입니다.

자동 동기화 포인트 생성

비동기 컴퓨팅 대기열은 일반 그래픽 워크로드와 병렬로 실행할 수 있으므로 렌더 파이프라인을 처리하는 데 걸리는 전체 GPU 시간을 줄일 수 있습니다. 그러나 비동기 컴퓨팅 대기열과 일반 그래픽 대기열 간의 동기화 지점을 수동으로 정의하고 유지하는 것은 어려울 수 있습니다. 렌더 그래프는 이 프로세스를 자동화하고 렌더 파이프라인의 상위 수준 선언을 사용하여 컴퓨팅 대기열과 그래픽 대기열 간의 올바른 동기화 지점을 생성합니다.

유지 관리 가능성

렌더 파이프라인 유지 관리에서 가장 복잡한 문제 중 하나는 리소스 관리입니다. 렌더 그래프는 내부적으로 리소스를 관리하기 때문에 렌더 파이프라인을 훨씬 쉽게 유지 관리할 수 있습니다. 렌더그래프 API를 사용하면 입력과 출력을 명시적으로 선언하고 렌더 파이프라인의 어느 곳에나 플러그인할 수 있는 효율적인 독립형 렌더링 모듈을 작성할 수 있습니다.

 

그래프 렌더링 기본 사항

이 문서에서는 렌더 그래프의 주요 원칙과 Unity가 이를 실행하는 방법에 대한 개요를 설명합니다.

주요 원칙

렌더그래프 API로 렌더 패스를 작성하려면 먼저 다음과 같은 기본 원칙을 알아야 합니다:

  • 더 이상 리소스를 직접 처리하지 않고 대신 렌더그래프 시스템별 핸들을 사용합니다. 모든 렌더그래프 API는 이러한 핸들을 사용하여 리소스를 조작합니다. 렌더 그래프가 관리하는 리소스 유형은 RTHandles, ComputeBuffers, RendererLists입니다.
  • 실제 리소스 참조는 렌더 패스의 실행 코드 내에서만 액세스할 수 있습니다.
  • 프레임워크는 렌더 패스를 명시적으로 선언해야 합니다. 각 렌더 패스는 어떤 리소스에서 읽거나 쓰는지 명시해야 합니다.
  • 렌더 그래프의 각 실행 사이에는 지속성이 없습니다. 즉, 렌더 그래프의 한 실행 내에서 생성한 리소스는 다음 실행으로 이월할 수 없습니다.
  • 지속성이 필요한 리소스(예: 한 프레임에서 다른 프레임으로)의 경우 일반 리소스처럼 렌더 그래프 외부에서 생성하여 가져올 수 있습니다. 종속성 추적 측면에서 다른 렌더 그래프 리소스처럼 작동하지만 그래프가 수명을 처리하지는 않습니다.
  • 렌더 그래프는 대부분 텍스처 리소스에 RTHandle을 사용합니다. 이는 셰이더 코드를 작성하는 방법과 설정 방법에 많은 영향을 미칩니다.

리소스 관리

렌더 그래프 시스템은 전체 프레임의 상위 수준 표현을 사용하여 각 리소스의 수명을 계산합니다. 즉, 렌더그래프 API를 통해 리소스를 생성할 때 렌더그래프 시스템은 그 시점에 리소스를 생성하지 않습니다. 대신 API는 리소스를 나타내는 핸들을 반환하며, 이 핸들은 모든 렌더그래프 API에서 사용됩니다. 렌더 그래프는 리소스를 작성해야 하는 첫 번째 패스 직전에만 리소스를 생성합니다. 이 경우 "생성"은 렌더 그래프 시스템이 반드시 리소스를 할당한다는 의미는 아닙니다. 그보다는 렌더링 패스 중에 리소스를 사용할 수 있도록 리소스를 표현하는 데 필요한 메모리를 제공한다는 의미입니다. 같은 방식으로 리소스를 읽어야 하는 마지막 패스가 끝나면 리소스 메모리도 해제합니다. 이렇게 하면 렌더 그래프 시스템은 사용자가 패스에 선언한 내용에 따라 가장 효율적인 방식으로 메모리를 재사용할 수 있습니다. 렌더 그래프 시스템이 특정 리소스가 필요한 패스를 실행하지 않으면 시스템은 해당 리소스에 대한 메모리를 할당하지 않습니다.


렌더 그래프 실행 개요

렌더 그래프 실행은 렌더 그래프 시스템이 처음부터 매 프레임마다 완료하는 3단계 프로세스입니다. 예를 들어 사용자의 동작에 따라 그래프가 프레임마다 동적으로 변경될 수 있기 때문입니다.

설정

첫 번째 단계는 모든 렌더 패스를 설정하는 것입니다. 여기에서 실행할 모든 렌더 패스와 각 렌더 패스가 사용하는 리소스를 선언합니다.

컴파일

두 번째 단계는 그래프를 컴파일하는 것입니다. 이 단계에서 렌더 그래프 시스템은 다른 렌더 패스가 해당 출력을 사용하지 않는 경우 렌더 패스를 컬링합니다. 그래프를 설정할 때 특정 로직을 줄일 수 있으므로 덜 체계적으로 설정할 수 있습니다. 그 좋은 예로 디버그 렌더 패스를 들 수 있습니다. 백 버퍼에 표시하지 않는 디버그 출력을 생성하는 렌더 패스를 선언하면 렌더 그래프 시스템에서 해당 패스를 자동으로 컬링합니다.

이 단계에서는 리소스의 수명도 계산합니다. 이를 통해 렌더 그래프 시스템은 효율적인 방식으로 리소스를 생성 및 해제하고 비동기 컴퓨팅 파이프라인에서 패스를 실행할 때 적절한 동기화 지점을 계산할 수 있습니다.

실행

마지막으로 그래프를 실행합니다. 렌더 그래프 시스템은 컬링하지 않은 모든 렌더 패스를 선언 순서대로 실행합니다. 각 렌더 패스 전에 렌더 그래프 시스템은 적절한 리소스를 생성하고 이후 렌더 패스에서 해당 리소스를 사용하지 않는 경우 렌더 패스 후에 해제합니다.

 

렌더 파이프라인 작성

이 페이지에서는 렌더그래프 API를 사용하여 렌더 파이프라인을 작성하는 방법에 대해 설명합니다. 렌더그래프 API에 대한 자세한 내용은 렌더그래프 시스템 및 렌더그래프 기본 사항을 참조하세요.

렌더 그래프 초기화 및 정리

시작하려면 렌더 파이프라인에 하나 이상의 렌더그래프 인스턴스가 유지되어야 합니다. 이것이 API의 주요 진입점입니다. 렌더 그래프 인스턴스를 두 개 이상 사용할 수 있지만, Unity는 렌더 그래프 인스턴스 간에 리소스를 공유하지 않으므로 최적의 메모리 사용을 위해 하나의 인스턴스만 사용하세요.

using UnityEngine.Rendering.RenderGraphModule;

public class MyRenderPipeline : RenderPipeline
{
    RenderGraph m_RenderGraph;

    void InitializeRenderGraph()
    {
        m_RenderGraph = new RenderGraph(“MyRenderGraph”);
    }

    void CleanupRenderGraph()
    {
        m_RenderGraph.Cleanup();
          m_RenderGraph = null;
    }
}

렌더그래프 인스턴스를 초기화하려면 생성자를 선택적 이름으로 호출하여 렌더그래프를 식별합니다. 또한 SRP 디버그 창에 렌더 그래프 전용 패널을 등록하여 렌더 그래프 인스턴스를 디버깅하는 데 유용합니다. 렌더 파이프라인을 폐기할 때는 렌더그래프 인스턴스에서 Cleanup() 메서드를 호출하여 렌더그래프에 할당된 모든 리소스를 적절히 해제합니다.

렌더 그래프 시작

렌더 그래프에 렌더 패스를 추가하기 전에 먼저 BeginRecording 메서드를 호출하여 렌더 그래프를 초기화해야 합니다. 모든 렌더 패스가 렌더 그래프에 추가되면 EndRecordingAndExecute 메서드를 호출하여 렌더 그래프를 실행할 수 있습니다.

BeginRecording 메서드의 파라미터에 대한 자세한 내용은 API documentation

var renderGraphParams = new RenderGraphParameters()
{
    scriptableRenderContext = renderContext,
    commandBuffer = cmd,
    currentFrameIndex = frameIndex
};

m_RenderGraph.BeginRecording(renderGraphParams);
// 여기에서 패스를 추가하세요.
m_RenderGraph.EndRecordingAndExecute();

 

렌더 그래프용 리소스 생성

렌더 그래프를 사용할 때는 리소스를 직접 할당하지 않습니다. 대신 렌더그래프 인스턴스가 자체 리소스의 할당 및 폐기를 처리합니다. 리소스를 선언하고 렌더 패스에서 사용하려면 리소스에 대한 핸들을 반환하는 렌더 그래프 전용 API를 사용합니다.

렌더 그래프가 사용하는 리소스에는 크게 두 가지 유형이 있습니다:

  • Internal resources: 이러한 리소스는 렌더 그래프 실행에 내부적으로 사용되며 렌더 그래프 인스턴스 외부에서는 액세스할 수 없습니다. 또한 그래프의 한 실행에서 다른 실행으로 이러한 리소스를 전달할 수도 없습니다. 렌더 그래프는 이러한 리소스의 수명을 처리합니다.
  • Imported resources: 이러한 버퍼는 보통 렌더 그래프 실행 외부에서 제공됩니다. 일반적인 예로는 백 버퍼(카메라에서 제공)나 그래프가 여러 프레임에 걸쳐 일시적 효과를 위해 사용하려는 버퍼(예: 일시적 안티앨리어싱에 카메라 컬러 버퍼 사용)가 있습니다. 이러한 리소스의 수명은 사용자가 관리해야 합니다.

리소스를 생성하거나 임포트하면 렌더 그래프 시스템은 해당 리소스를 리소스 유형별 핸들(TextureHandle, BufferHandle 또는 RendererListHandle)로 표현합니다. 이렇게 하면 렌더 그래프가 모든 API에서 내부 및 임포트된 리소스를 동일한 방식으로 사용할 수 있습니다.

public TextureHandle RenderGraph.CreateTexture(in TextureDesc desc);
public BufferHandle RenderGraph.CreateComputeBuffer(in ComputeBufferDesc desc)
public RendererListHandle RenderGraph.CreateRendererList(in RendererListDesc desc);

public TextureHandle RenderGraph.ImportTexture(RTHandle rt);
public TextureHandle RenderGraph.ImportBackbuffer(RenderTargetIdentifier rt);
public BufferHandle RenderGraph.ImportBuffer(ComputeBuffer computeBuffer);

리소스를 만드는 주요 방법은 위에 설명되어 있지만 이러한 기능에는 다양한 변형이 있습니다. 전체 목록은 API documentation. 카메라 백 버퍼를 임포트하는 데 사용할 특정 함수는 RenderTargetIdentifier입니다.

리소스를 생성하려면 각 API에 매개변수로 디스크립터 구조가 필요합니다. 이러한 구조체의 프로퍼티는 해당 구조체가 나타내는 리소스의 프로퍼티와 유사합니다(각각 RTHandle, ComputeBuffer 및 RendererLists). 그러나 일부 프로퍼티는 그래프 텍스처 렌더링에 특화되어 있습니다.

가장 중요한 것은 다음과 같습니다:

  • clearBuffer: 이 프로퍼티는 그래프가 버퍼를 생성할 때 버퍼를 지울지 여부를 그래프에 알려줍니다. 렌더 그래프를 사용할 때 텍스처를 지우는 방식입니다. 렌더 그래프는 리소스를 풀링하므로 텍스처를 생성하는 모든 패스가 정의되지 않은 콘텐츠가 있는 기존 텍스처를 가져올 수 있기 때문에 이 속성은 중요합니다.
  • clearColor: 이 속성은 해당되는 경우 버퍼를 지울 색상을 저장합니다.

렌더 그래프가 TextureDesc 생성자를 통해 노출하는 텍스처와 관련된 두 가지 개념도 있습니다:

  • xrReady: 이 부울은 그래프에 이 텍스처가 XR 렌더링용인지 여부를 나타냅니다. True이면 렌더 그래프가 텍스처를 각 XR 눈에 렌더링할 배열로 생성합니다.
  • dynamicResolution: 이 부울은 애플리케이션이 동적 해상도를 사용할 때 이 텍스처의 크기를 동적으로 조정해야 하는지 여부를 그래프에 표시합니다. 거짓이면 텍스처의 크기가 자동으로 조정되지 않습니다.

렌더링 코드가 아닌 렌더링 패스에 대한 설정 코드 내부에서 렌더링 패스 외부에 리소스를 생성할 수 있습니다.

모든 렌더 패스 외부에 리소스를 생성하는 것은 첫 번째 패스가 정기적으로 변경될 수 있는 코드의 로직에 의존하는 특정 리소스를 사용하는 경우에 유용할 수 있습니다. 이 경우 모든 패스 전에 리소스를 생성해야 합니다. 디퍼드 조명 패스 또는 포워드 조명 패스에 컬러 버퍼를 사용하는 것이 좋은 예입니다. 이 두 패스는 모두 컬러 버퍼에 쓰지만 Unity는 카메라에 대해 선택된 현재 렌더링 경로에 따라 그 중 하나만 실행합니다. 이 경우 두 패스 외부에 컬러 버퍼를 생성하고 이를 파라미터로 올바른 패스에 전달합니다.

렌더 패스 내부에 리소스를 생성하는 것은 일반적으로 렌더 패스가 자체적으로 생성하는 리소스를 위한 것입니다. 예를 들어, 블러 패스는 이미 존재하는 입력 텍스처가 필요하지만 출력 자체를 생성하여 렌더링 패스가 끝날 때 반환합니다.

이와 같은 리소스를 생성한다고 해서 매 프레임마다 GPU 메모리가 할당되는 것은 아닙니다. 대신 렌더 그래프 시스템은 풀링된 메모리를 재사용합니다. 렌더 그래프의 맥락에서 리소스 생성은 실제 할당보다는 렌더 패스의 맥락에서 데이터 흐름의 관점에서 생각하면 됩니다. 렌더 패스가 완전히 새로운 출력을 생성하는 경우 렌더 그래프에서 새로운 텍스처를 "생성"합니다.

렌더 패스 작성

Unity가 렌더 그래프를 실행하려면 먼저 모든 렌더 패스를 선언해야 합니다. 렌더 패스는 설정과 렌더링의 두 부분으로 작성합니다.

설정

설정 중에 렌더 패스와 실행에 필요한 모든 데이터를 선언합니다. 렌더 그래프는 모든 관련 프로퍼티를 포함하는 렌더 패스에 특정한 클래스로 데이터를 나타냅니다. 이러한 클래스는 일반 C# 구조체(구조체, PoD 등)와 렌더 그래프 리소스 핸들이 될 수 있습니다. 이 데이터 구조는 실제 렌더링 코드에서 액세스할 수 있습니다.

class MyRenderPassData
{
    public float parameter;
    public TextureHandle inputTexture;
    public TextureHandle outputTexture;
}

패스 데이터를 정의한 후에는 렌더링 패스 자체를 선언할 수 있습니다:

 

using (var builder = renderGraph.AddRenderPass<MyRenderPassData>("My Render Pass", out var passData))
{
        passData.parameter = 2.5f;
    passData.inputTexture = builder.ReadTexture(inputTexture);

    TextureHandle output = renderGraph.CreateTexture(new TextureDesc(Vector2.one, true, true)
                        { colorFormat = GraphicsFormat.R8G8B8A8_UNorm, clearBuffer = true, clearColor = Color.black, name = "Output" });
    passData.outputTexture = builder.WriteTexture(output);

    builder.SetRenderFunc(myFunc); // details below.
}

AddRenderPass 함수 주변의 사용 범위에서 렌더 패스를 정의합니다. 범위의 끝에서 렌더 그래프는 나중에 처리하기 위해 렌더 그래프의 내부 구조에 렌더 패스를 추가합니다.

빌더 변수는 렌더그래프 빌더의 인스턴스입니다. 이것은 렌더 패스와 관련된 정보를 빌드하는 진입점입니다. 여기에는 몇 가지 중요한 부분이 있습니다:

  • Declaring resource usage: 이는 렌더그래프 API의 가장 중요한 측면 중 하나입니다. 여기서 렌더 패스에 리소스에 대한 읽기 및/또는 쓰기 액세스 권한이 필요한지 여부를 명시적으로 선언합니다. 이를 통해 렌더 그래프가 전체 렌더링 프레임을 전체적으로 파악할 수 있으므로 다양한 렌더 패스 간의 GPU 메모리 및 동기화 지점을 최적으로 사용할 수 있습니다.
  • Declaring the rendering function: 그래픽 명령을 호출하는 함수입니다. 이 함수는 렌더 패스에 대해 정의한 패스 데이터와 렌더 그래프 컨텍스트를 매개변수로 받습니다. SetRenderFunc를 통해 렌더 패스에 대한 렌더링 함수를 설정하면 그래프가 컴파일된 후 함수가 실행됩니다.
  • Creating transient resources: 일시적 또는 내부 리소스는 이 렌더 패스 기간 동안만 생성하는 리소스입니다. 수명을 반영하기 위해 렌더 그래프 자체가 아닌 빌더에서 생성합니다. 트랜지언트 리소스를 생성할 때는 렌더그래프 API의 동등한 함수와 동일한 파라미터를 사용합니다. 이는 패스가 패스 외부에서 액세스해서는 안 되는 임시 버퍼를 사용할 때 특히 유용합니다. 트랜지언트 리소스를 선언한 패스 외부에서 해당 리소스에 대한 핸들이 유효하지 않게 되고, 이를 사용하려고 하면 Unity에서 오류가 발생합니다.

passData 변수는 패스를 선언할 때 제공하는 유형의 인스턴스입니다. 여기에서 렌더링 코드가 액세스할 수 있는 데이터를 설정합니다. 렌더 그래프는 passData의 내용을 바로 사용하는 것이 아니라 프레임 후반에 모든 패스를 등록하고 렌더 그래프가 컴파일 및 실행된 후에 사용한다는 점에 유의하세요. 즉, passData가 저장하는 모든 참조는 전체 프레임에 걸쳐 일정해야 합니다. 그렇지 않으면 렌더 패스가 실행되기 전에 콘텐츠를 변경하면 렌더 패스 중에 올바른 콘텐츠가 포함되지 않습니다. 따라서 패스의 실행이 완료될 때까지 참조가 일정하게 유지된다는 것이 확실하지 않은 한 passData에 값 유형만 저장하는 것이 가장 좋습니다.

RenderGraphBuilder API에 대한 개요는 아래 표를 참조하세요. 자세한 내용은 API 설명서를 참조하세요:

기능목적
TextureHandle ReadTexture(in TextureHandle input) 함수에 전달한 입력 텍스처에서 렌더 패스를 읽도록 선언합니다.
TextureHandle WriteTexture(in TextureHandle input) 함수에 전달한 입력 텍스처에 렌더 패스를 쓰도록 선언합니다.
TextureHandle UseColorBuffer(in TextureHandle input, int index) WriteTexture와 동일하지만 패스 시작 시 제공된 바인딩 인덱스에 텍스처를 렌더 텍스처로 자동 바인딩합니다.
TextureHandle UseDepthBuffer(in TextureHandle input, DepthAccess flags) WriteTexture와 동일하지만 함수에 전달한 액세스 플래그를 사용하여 텍스처를 뎁스 텍스처로 자동 바인딩합니다.
TextureHandle CreateTransientTexture(in TextureDesc desc) 트랜지언트 텍스처를 만듭니다. 이 텍스처는 패스 기간 동안 존재합니다.
RendererListHandle UseRendererList(in RendererListHandle input) 이 렌더 패스가 사용자가 전달한 렌더러 목록을 사용한다고 선언합니다. 렌더 패스는 RendererList.Draw 명령을 사용하여 목록을 렌더링합니다.
BufferHandle ReadComputeBuffer(in BufferHandle input) 렌더 패스가 함수에 전달한 입력 ComputeBuffer에서 읽도록 선언합니다.
BufferHandle WriteComputeBuffer(in BufferHandle input) 렌더 패스가 함수에 전달한 입력 Compute Buffer에 쓰기를 선언합니다.
BufferHandle CreateTransientComputeBuffer(in BufferDesc desc) 일시적인 컴퓨트 버퍼를 생성합니다. 이 텍스처는 컴퓨트 버퍼 기간 동안 존재합니다.
void SetRenderFunc(RenderFunc renderFunc) where PassData : class, new() 렌더 패스에 대한 렌더링 함수를 설정합니다.
void EnableAsyncCompute(bool value) 렌더 패스가 비동기 컴퓨팅 파이프라인에서 실행되도록 선언합니다.
void AllowPassCulling(bool value) Unity에서 렌더 패스를 컬링할지 여부를 지정합니다(기본값은 true). 렌더 패스에 부작용이 있어 렌더 그래프 시스템을 컬링하지 않으려는 경우에 유용할 수 있습니다.
void EnableFoveatedRasterization(bool value) 포비티드 렌더링 기능이 활성화된 상태에서 렌더 패스가 실행되도록 선언합니다.

렌더링 코드

설정을 완료한 후에는 RenderGraphBuilder의 SetRenderFunc 메서드를 통해 렌더링에 사용할 함수를 선언할 수 있습니다. 할당하는 함수는 다음 서명을 사용해야 합니다:

delegate void RenderFunc<PassData>(PassData data, RenderGraphContext renderGraphContext) where PassData : class, new();

렌더링 함수를 정적 함수 또는 람다로 전달할 수 있습니다. 람다 함수를 사용하면 렌더링 코드가 설정 코드 옆에 있기 때문에 코드가 더 명확해진다는 장점이 있습니다.

람다를 사용하는 경우 함수의 기본 범위에서 파라미터를 캡처하면 가비지가 생성되므로 나중에 가비지 수집 중에 Unity가 이를 찾아서 해제하므로 주의해야 합니다. Visual Studio에서 화살표 =>에 커서를 올리면 람다가 범위에서 캡처하는 항목이 있는지 알 수 있습니다. 멤버나 멤버 함수를 사용하면 캡처되므로 멤버나 멤버 함수에 액세스하지 마세요.

렌더링 함수에는 두 개의 매개 변수가 필요합니다:

  • PassData: 이 데이터는 렌더 패스를 선언할 때 전달한 유형입니다. 여기에서 설정 단계에서 초기화된 프로퍼티에 액세스하여 렌더링 코드에 사용할 수 있습니다.
  • RenderGraphContext renderGraphContext. 여기에는 유틸리티 함수를 제공하고 렌더링 코드를 작성할 수 있는 스크립터블 렌더 컨텍스트와 CommandBuffer에 대한 참조가 저장됩니다.

렌더 패스에서 리소스 액세스하기

렌더링 함수 내부에서 passData에 저장된 모든 렌더 그래프 리소스 핸들에 액세스할 수 있습니다. 실제 리소스로의 변환은 자동으로 이루어지므로 함수에 RTHandle, ComputeBuffer 또는 RendererList가 필요할 때마다 핸들을 전달하면 렌더 그래프가 핸들을 실제 리소스로 암시적으로 변환합니다. 렌더링 함수 외부에서 이러한 암시적 변환을 수행하면 예외가 발생한다는 점에 유의하세요. 이 예외가 발생하는 이유는 렌더링 외부에서 렌더 그래프가 아직 해당 리소스를 할당하지 않았을 수 있기 때문입니다.

렌더그래프 컨텍스트 사용

렌더그래프 컨텍스트는 렌더링 코드를 작성하는 데 필요한 다양한 기능을 제공합니다. 가장 중요한 두 가지는 모든 렌더링 명령을 호출하는 데 사용하는 스크립터블 렌더 컨텍스트와 커맨드 버퍼입니다.

RenderGraphContext에는 RenderGraphObjectPool도 포함되어 있습니다. 이 클래스는 코드 렌더링에 필요할 수 있는 임시 객체를 관리하는 데 도움이 됩니다.

임시 함수 가져오기

렌더링 패스 중에 특히 유용한 두 가지 함수는 GetTempArray와 GetTempMaterialPropertyBlock입니다.

T[] GetTempArray<T>(int size);
MaterialPropertyBlock GetTempMaterialPropertyBlock();

GetTempArray는 T 유형과 크기의 임시 배열을 반환합니다. 이는 머티리얼에 파라미터를 전달하기 위한 임시 배열을 할당하거나 배열의 수명을 직접 관리할 필요 없이 여러 렌더 타깃 설정을 생성하기 위해 RenderTargetIdentifier 배열을 생성하는 데 유용할 수 있습니다.

GetTempMaterialPropertyBlock은 머티리얼의 파라미터를 설정하는 데 사용할 수 있는 깨끗한 머티리얼 프로퍼티 블록을 반환합니다. 이는 한 머티리얼을 두 개 이상의 패스에서 사용할 수 있고 각 패스마다 다른 파라미터를 사용할 수 있기 때문에 특히 중요합니다. 렌더링 코드 실행은 명령 버퍼를 통해 지연되므로 실행 시 데이터 무결성을 유지하려면 머티리얼 프로퍼티 블록을 명령 버퍼에 복사하는 것이 필수입니다.

렌더 그래프는 패스 실행 후 이 두 함수가 반환하는 모든 리소스를 자동으로 해제하고 풀링합니다. 따라서 사용자가 직접 관리할 필요가 없으며 가비지를 생성하지 않습니다.

렌더링 패스 예시

다음 코드 예시에는 설정 및 렌더링 함수가 포함된 렌더링 패스가 포함되어 있습니다:

TextureHandle MyRenderPass(RenderGraph renderGraph, TextureHandle inputTexture, float parameter, Material material)
{
    using (var builder = renderGraph.AddRenderPass<MyRenderPassData>("My Render Pass", out var passData))
    {
        passData.parameter = parameter;
        passData.material = material;

        // 이 패스가 입력 텍스처를 읽도록 그래프에 알려줍니다.
        passData.inputTexture = builder.ReadTexture(inputTexture);

        // 출력 텍스처를 생성합니다.
        TextureHandle output = renderGraph.CreateTexture(new TextureDesc(Vector2.one, true, true)
                        { colorFormat = GraphicsFormat.R8G8B8A8_UNorm, clearBuffer = true, clearColor = Color.black, name = "Output" });
        // 이 패스가 이 텍스처를 쓸 것이며 렌더 타깃 0으로 설정해야 함을 그래프에 알립니다.
        passData.outputTexture = builder.UseColorBuffer(output, 0);

        builder.SetRenderFunc(
        (MyRenderPassData data, RenderGraphContext ctx) =>
        {
            // 렌더 타깃은 위의 UseColorBuffer를 사용하여 이미 설정되어 있습니다.
            // 빌더.WriteTexture를 사용했다면 이와 같은 작업을 수행해야 합니다:
            // CoreUtils.SetRenderTarget(ctx.cmd, data.output);

            // 렌더링용 머티리얼 설정
            var materialPropertyBlock = ctx.renderGraphPool.GetTempMaterialPropertyBlock();
            materialPropertyBlock.SetTexture("_MainTexture", data.input);
            materialPropertyBlock.SetFloat("_FloatParam", data.parameter);

            CoreUtils.DrawFullScreen(ctx.cmd, data.material, materialPropertyBlock);
        });

        return output;
    }
}

프레임 종료

 

애플리케이션이 진행되는 동안 렌더 그래프는 다양한 리소스를 할당해야 합니다. 이러한 리소스를 한동안 사용할 수 있지만 이후에는 필요하지 않을 수도 있습니다. 그래프가 이러한 리소스를 확보하려면 프레임당 한 번씩 EndFrame() 메서드를 호출합니다. 그러면 렌더 그래프가 마지막 프레임 이후 사용하지 않은 모든 리소스가 할당 해제됩니다. 또한 프레임이 끝날 때 렌더링 그래프에 필요한 모든 내부 처리를 실행합니다.

이 함수는 프레임당 한 번만 호출하고 모든 렌더링이 완료된 후(예: 마지막 카메라 렌더링 후) 호출해야 합니다. 카메라마다 렌더링 경로가 달라서 다른 리소스가 필요할 수 있기 때문입니다. 각 카메라마다 퍼지를 호출하면 다음 카메라에 필요한 리소스가 있음에도 불구하고 렌더 그래프가 너무 일찍 리소스를 해제할 수 있습니다.