본문내용 바로가기 주메뉴 바로가기
닫기

유니티 스퀘어

Unity 6에서 URP용 Render Graph 사용하기

관련주제
  • #Unity6
  • #Rendering
  • #Render Graph
  • #URP
  • #Optimization
2024.10.08

Render Graph는 기존에 HDRP에서만 활용 가능했던 기능이었으나, 현재 Unity 6에서는 URP용 Render Graph가 새롭게 도입되었습니다. 이에 대한 상세 정보는 최신 CoreRP Library 매뉴얼에서도 확인할 수 있습니다.
이번 아티클에서는 URP용 Render Graph에 대해 상세히 설명하고자 합니다. 해당 기능을 활용하기 위해서는 다음과 같은 준비 사항이 필요합니다.
· Unity 6
· URP 17 이상 버전

Render Graph란?

Render Graph는 Rendering Pipeline에서 사용되는 리소스를 효율적으로 자동 관리해 주는 시스템입니다. Render pass가 사용할 리소스를 미리 선언해두어 Render Graph에서 불필요한 리소스의 할당을 막고, GPU에 보내는 데이터 양을 줄이는 것으로 퍼포먼스 향상을 기대할 수 있습니다. 

 본 시스템은 Unity 6에서 기본적으로 활성화되어 있습니다. ScriptableRenderPass 작성 시, Render Graph API를 사용하여 작성해야 하며, 기존 프로젝트를 Unity 6로 업그레이드 할 경우, 기존 ScriptableRenderPass를 Render Graph에 대응되도록 수정해야 합니다. 또는 CompatibilityMode(Render Graph Disabled) 옵션을 통해 Render Graph를 무효화하여 구 ScriptableRenderPass API를 계속해서 사용할 수 있습니다.
※Note: CompatibilityMode를 사용하여 Render Graph를 무효화할 수 있지만 관련 이점들을 활용할 수 없으며, 추후 해당 기능이 Deprecate 될 가능성이 있으므로 가능한 Render Graph를 사용하는 것을 추천합니다.

Render Graph라는 명칭만으로는 Shader Graph나 VFX Graph와 같은 Visualized Graph Tool이라고 생각할 수 있지만, Render Graph는 Visualized Graph Tool이 아닌 코드 기반 시스템 입니다. 

Render Graph는 다음과 같은 다양한 최적화 방법을 통해 사용함에 있어 이점을 제공합니다.
해당 방법들은 Render Graph에 의해 자동으로 이루어집니다.

· 리소스 관리 자동화

- Render pass 내에서 사용할 리소스들을 직접 다루지 않고 Handle(TextureHandle 등 …)을 통해 사용할 리소스를 선언
- Render Graph에서 Handle을 가지고 리소스의 할당과 수명을 관리해 주기 때문에 직접 Allocate/Dispose 해줄 필요가 없음
- 프레임동안 사용되지 않는 리소스는 할당하지 않음

· 메모리 최적화

- 위의 리소스 관리 자동화로 불필요한 메모리 사용과 메모리 누수 방지 가능
- 동일한 속성(해상도, 포맷 등)의 Texture가 복수 선언되어 있을 경우, 자동으로 하나의 Texture로 관리하여 리소스 중복을 방지

· Pass Merging

- Render Graph가 Render pass의 모든 리소스를 관리하고 있으며, 리소스에의 Read/Write 접근 권한에 따른 의존 관계와 Render pass 실행 순에 의거하여 복수의 Render pass를 하나의 Native Render pass로 Merge
- 결과적으로 메모리 대역폭 및 렌더링 소요 시간 감소 가능
- Pass Merging의 경우, Tile-Based Deferred Rendering(TBDR) 기기와 Native Render pass를 대응하고 있는 Graphics API(Vulkan, Metal, DX12)에서만 이루어짐

· Pass Culling

- Render pass의 출력을 다른 pass에서 참조하고 있지 않거나 해당 Render pass의 출력을 Write하기 위한 Render Texture 지정이 없을 경우, 해당 Render pass를 Culling

Pass Merging

Pass Merging이 이루어지는 상황에 대해서 살펴보도록 하겠습니다.

아래 그림은 URP의 기본적인 Render pass 실행 중 일부를 보여줍니다. 왼쪽부터 Render pass가 실행되며 Draw Opaque Objects, Draw Skybox pass는 Camera Color Render Texture에 값을 Write하고, Blit Post Processing pass는 이전 Render pass들이 Camera Color Render Texture에 Write한 값을 Read하고 있는 것을 볼 수 있습니다.



Pass Merging의 경우, 의존 관계와 실행 순에 의거하여 이루어지는데, 위의 그림에서 의존 관계를 살펴보면 Draw Opaque Objects, Draw Skybox pass는 각각 Render Texture에 접근하여 값을 Write하고 있으며 서로 간의 의존 관계는 없습니다. 반면 Blit Post Processing pass는 이전 pass들이 Camera Color Render Texture에 Write한 값을 Read하고 있기에 이전 Render pass들의 값이 쓰여진 Camera Color Render Texture와 의존 관계가 있다고 볼 수 있습니다.



이 경우 Draw Opaque Objects, Draw Skybox pass는 서로 간의 의존 관계가 없기에 하나의 Native Render pass로 Merge되지만, 이전 Render pass들의 값이 쓰여져 있는 Camera Color Render Texture와 의존 관계가 있는 Blit Post Processing pass의 경우에는 Merge되지 않습니다.

Pass Culling

Pass Culling은 크게 다음과 같은 상황에서 이루어집니다.

- Render pass의 출력을 다른 pass에서 참조하지 않을 경우


Render pass의 출력을 다른 pass에서 참조하지 않을 경우 이미지

- Render pass에서 출력을 Write하기 위한 Render Texture를 설정하지 않았을 경우


Render pass에서 출력을 Write하기 위한 Render Texture를 설정하지 않았을 경우 이미지

Render Graph용 ScriptableRenderPass 작성

지금까지 Render Graph의 개념에 대해 설명하였습니다. 이어서 Render Graph API를 사용한 ScriptableRenderPass 작성에 대하여 기술하겠습니다.
앞서 말씀드린 것과 같이 Unity 6에서는 Render Graph가 기본으로 활성화되어 있으며, ScriptableRenderPass 작성 시, Render Graph에 대응되도록 작성할 필요가 있습니다.

Render pass 종류

Render pass는 다음과 같은 종류가 있습니다.

· RasterPass(Manual)

- 지정한 Render Target에 렌더링하는 용도의 Render pass
    - Blit, 오브젝트 렌더링 등
- RasterComnnadBuffer를 통해 실행
- 하나의 Render Target에만 값을 쓸 수 있음

· ComputePass(Manual)

- ComputeShader용 Render pass
- ComputeCommandBuffer를 통해 실행
- Render Target에 렌더링 불가

· UnsafePass(Manual)

- 제한 사항이 없는 Render pass
- 기존의 CommandBuffer를 통해 실행
- Pass Merging 대상 외
- 용도는 Pass Merging을 고려하지 않아도 되는 Render pass 작성, 또는 Unity 6 이전의 Render pass에 구현하였던 실행 로직(CommandBuffer)을 이식 가능

API 간이화

이전까지 ScriptableRenderPass를 작성하기 위해서는 필요에 따라 override 함수(Configure, Execute 등 )가 많았기에 번잡함이 있었지만 Unity 6에서는 RecordRenderGraph 함수로 집약되었습니다. 

ScriptableRenderPass 작성 예

예시로 CameraColor를 Custom Render Texture에 복사하는 Render pass를 작성해 보도록 하겠습니다.

ScriptableRendererFeature, ScriptableRenderPass 선언

Unity에서는 ScriptableRendererFeature와 ScriptableRenderPass를 작성하기 위한 기본적인 템플릿을 제공하고 있습니다.
- Create > Rendering > URP Renderer Feature를 통해 Script 생성

PassData 선언 및 정의

Render Graph에서 리소스는 Handle을 통해 관리되고, Render pass 실행 중에 사용하는 리소스 Handle를 전달하기 위해 PassData 클래스를 사용합니다.
PassData는 템플릿을 통해 Script 생성 시, ScriptableRenderPass 내부에 이미 선언되어 있습니다.
이번 예에서는 멤버로 Render pass실행 중에 사용할 현재 Camera Color의 TextureHandle을 변수로 가집니다.

C/C++

public class CustomRendererFeature : ScriptableRendererFeature

{

    class CustomRenderPass : ScriptableRenderPass

    {

        private class PassData

        {

            public TextureHandle sourceTextureHandle;

        }

...

    }

    ...

}


RecordRenderGraph 함수 정의

해당 함수는 Render Graph 구성 단계에서 호출되며, 다음과 같은 구성으로 함수를 정의할 수 있습니다.

- Render pass 정의
- 사용할 리소스 선언
- 리소스 접근 권한(Read/Write) 설정
- Render pass 실행 함수 등록, 람다 함수도 가능

C/C++ 

public class CustomRendererFeature : ScriptableRendererFeature

{

    class CustomRenderPass : ScriptableRenderPass

    {

        ...

// RecordRenderGraph 함수 정의

   public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)

        {

    // Render pass 이름

            const string passName = "CustomRenderPass";

            

            // Render pass 정의, RasterPass

            using (var builder = renderGraph.AddRasterRenderPass(passName, out PassData passData))

            {

       // 사용할 리소스를 선언

                // URP의 공용 리소스에 접근하여, 읽어 올 현재 CameraColor의 Handle을 선언

                UniversalResourceData resourceData = frameData.Get();

                TextureHandle cameraColorTextureHandle = resourceData.activeColorTexture;

                

                // 복사할 Custom Render Texture의 Handle 선언

                UniversalCameraData cameraData = frameData.Get();

                RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;

                desc.msaaSamples = 1;

                desc.depthBufferBits = 0;

                TextureHandle target = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "Target Texture", false);


                // 리소스 접근 권한 지정

// CameraColor를 읽기 위해 Read로 지정

                builder.UseTexture(cameraColorTextureHandle, AccessFlags.Read);

                

                // Custom Render Texture에 값을 쓰기 위해 Write로 지정

                // Custom Render Texture를 RasterPass의 Render Target으로 지정

                // CommnadBuffer.SetRenderTarget과 비슷한 역할

                builder.SetRenderAttachment(target, 0, AccessFlags.Write);


                // Render pass 실행 중 CameraColor를 사용하기 위해 Handle을 passData에 등록                

                passData.sourceTextureHandle = cameraColorTextureHandle;


                // Render pass 실행 함수 등록, 람다 함수도 가능

                builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context));

      }

 }

    }

    ...

}


Render pass 실행 함수 작성

실행 함수는 별도로 작성하거나 SetRenderFunc에 람다 함수로 등록할 수도 있습니다.

C/C++

public class CustomRendererFeature : ScriptableRendererFeature

{

    class CustomRenderPass : ScriptableRenderPass

    {

    ...

    static void ExecutePass(PassData data, RasterGraphContext context)

          {    

        // 해당 Render pass는 RasterPass이므로 RasterCommandBuffer를 사용 

               RasterCommandBuffer cmd = context.cmd;

            

        // 대상 Render Target에 CameraColor를 Blit

               Blitter.BlitTexture(cmd, data.sourceTextureHandle, new Vector4(1, 1, 0, 0), 0, false);

           }

}

...

}


ScriptableRenderPass 등록

작성한 Render pass를 Renderer Feature를 통해 Renderer에 등록합니다.
등록 방법은 이전과 동일하며, 템플릿에서 이미 해당 Render pass를 등록하고 있습니다.

C/C++

public class CustomRendererFeature : ScriptableRendererFeature

{

    class CustomRenderPass : ScriptableRenderPass

    {

       ...

    }

    ...


    CustomRenderPass m_ScriptablePass;

    

    // Render pass를 renderer에 등록

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)

    {

        renderer.EnqueuePass(m_ScriptablePass);

    }

}


기존 ScriptableRenderPass의 Render Graph 대응 예

Unity 6 이전 버전에서 작성한 ScriptableRenderPass를 Unity 6에서 사용하기 위해서는 Render Graph 대응이 필요합니다. 
본 항목에서는 기존 URP 매뉴얼에서 제공하고 있는 RedTint 예제를 사용하였습니다.
Render Graph 대응 코드를 보시기 전에 위의 매뉴얼에서 제공하고 있는 예제 코드를 확인해 주시기 바랍니다.

Shader와 ScriptableRendererFeature의 경우, 변경점이 없으므로 별도의 대응이 필요하지 않습니다.

C/C++

using UnityEngine;

using UnityEngine.Rendering;

using UnityEngine.Rendering.Universal;

using UnityEngine.Rendering.RenderGraphModule;


public class RedTintRenderPassFeature : ScriptableRendererFeature

{

    class RedTintRenderPass : ScriptableRenderPass

    {

        Material _tintMaterial;


        public RedTintRenderPass(Material material)

        {

            _tintMaterial = material;

        }


        private class PassData

        {

            // Render pass 실행 중 필요한 TextureHandle과 Material

            public TextureHandle _soruceTextureHandle;

            public Material _blitMaterial;

        }


        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)

        {

            // 사용할 리소스를 선언

            // URP의 공용 리소스에 접근하여, 현재 Camera Color의 Handle 선언

            UniversalResourceData resourceData = frameData.Get();

            TextureHandle cameraColorTextureHandle = resourceData.activeColorTexture;

            

            // 임시로 Blit할 Temp Render Texture의 Handle 선언

            // 기존의 RenderTextureDescriptor 변수가 RecordRenderGraph로 이동

            UniversalCameraData cameraData = frameData.Get();

            RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;

            desc.msaaSamples = 1;

            desc.depthBufferBits = 0;

            TextureHandle tempTextureHandle = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "_tempRT", true);

            

            // Camera color -> Temp Render Texture

            using (var builder = renderGraph.AddRasterRenderPass("Blit_ToTemp", out PassData passData))

            {

                // 리소스 접근 권한 지정

                // CameraColor를 읽기 위해 Read로 지정

                builder.UseTexture(cameraColorTextureHandle, AccessFlags.Read);

                

                // Temp Render Texture에 값을 쓰기 위해 Write로 지정

                builder.SetRenderAttachment(tempTextureHandle, 0, AccessFlags.Write);

                

                // Render pass 실행 중 필요한 handle과 material을 동록

                passData._soruceTextureHandle = cameraColorTextureHandle;

                passData._blitMaterial = _tintMaterial;

                

                builder.SetRenderFunc((PassData data, RasterGraphContext context) =>

                {

                    // CameraColor를 Temp Render Texture에 Blit

                    RasterCommandBuffer cmd = context.cmd;

                    Blitter.BlitTexture(cmd, data._soruceTextureHandle, new Vector4(1, 1, 0, 0), data._blitMaterial, 0);

                });

            }

            

            // Temp Render Texture -> Camera color

            using (var builder = renderGraph.AddRasterRenderPass("Blit_ToCamera", out PassData passData))

            {

                // 리소스 접근 권한 지정

                // 위에서 Temp Render Texture에 Blit한 결과를 읽기 위해 Read로 지정

                builder.UseTexture(tempTextureHandle, AccessFlags.Read);

                

                // Camera color에 값을 쓰기 위해 Write로 지정

                builder.SetRenderAttachment(cameraColorTextureHandle, 0, AccessFlags.Write);

                

                // Render pass 실행 중 필요한 handle과 material을 동록

                passData._soruceTextureHandle = tempTextureHandle;

                passData._blitMaterial = _tintMaterial;

                

                builder.SetRenderFunc((PassData data, RasterGraphContext context) =>

                {

                    // Temp Render Texture를 Camera color에 Blit

                    RasterCommandBuffer cmd = context.cmd;

                    Blitter.BlitTexture(cmd, data._soruceTextureHandle, new Vector4(1, 1, 0, 0), data._blitMaterial, 1);

                });

            }

        }

        

        public void Dispose()

        {

            // Render Graph에서 리소스를 자동으로 관리하므로 명시적으로 리소스를 Dispose하지 않는다

        }

    }


    RedTintRenderPass scriptablePass;

    public Material _tintMaterial;

    

    public override void Create()

    {

        scriptablePass = new RedTintRenderPass(_tintMaterial);

        scriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;

    }

    

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)

    {

        renderer.EnqueuePass(scriptablePass);

    }

}


위의 코드를 통해 기존의 ScriptableRenderPass가 Unity 6의 Render Graph 상에서도 동작한다는 것을 확인할 수 있습니다.
참고로 Vulkan과 Metal의 경우, Framebuffer fetch를 사용하여 렌더링 최적화를 할 수 있습니다.

Render Graph Viewer

Render Graph Viewer는 Window > Analysis > Render Graph Viewer로 접근할 수 있으며, 실행되고 있는 Render pass들과 사용되고 있는 리소스들을 확인할 수 있습니다.



왼쪽 열이 사용되고 있는 리소스들의 목록, 위의 행은 실행 순으로 나열되어 있는 Render pass들의 목록입니다.



예로 위의 그림에서 다음 항목들에 대해서 확인할 수 있습니다.

· 리소스 접근

- Draw Opaque, Draw Transparent, Draw Skybox가  _CameraTargetAttachmentA에 Write(Red)로 접근
- Setup Post FX passes는 _CameraTargetAttachmentA에 접근하지 않음(Grey)
- Blit Post Processing가 _CameraTargetAttachmentA에 Read(Green)로 접근

· Pass Merging

- Draw Opaque, Draw Transparent, Draw Skybox는 Render pass간에 의존 관계를 가지지 않으므로 Pass Merging이 이루어 질 수 있으며, Render pass 밑에 Blue bar로 표시
- Blit Post Processing은 이전에 Render pass에서 쓰여진 값을 Read하여 사용하기 때문에 의존 관계를 가지므로 Pass Merging이 이루어지지 않음

· Pass Culling

- Blit_CustomRT는 Pass Culling 되어, Render pass 밑에 Black으로 표시

Pass Merging의 경우, Render Graph Viewer에서 Blue bar로 표시됩니다. 이는 Merge될 가능성이 있는 Render pass들을 나타내며, 구현 시 Pass Merging 여부를 참고할 수 있습니다. 

이 외에도 Render Graph Viewer를 통하여 다양한 정보들을 확인할 수 있습니다.

정리

지금까지 Unity 6에서 사용할 수 있는 URP용 Render Graph에 대해서 알아보았습니다.
Render Graph를 통해 퍼포먼스 향상과 개발 편의성 증대를 기대할 수 있습니다. Unity 6에서 이를 적극적으로 활용해 보시기 바랍니다. 
Render Graph의 보다 자세한 내용은 관련 매뉴얼 참고해 주시기 바랍니다.










 














Unity Square 로그인
Unity MWU Korea Awards 2021 TOP 36 투표와 관련하여, 본인의 개인정보를 유니티테크놀로지스코리아 유한회사(이하 ‘회사‘)에서 수집 및 이용하는 것에 대해 동의합니다.

- 단, 관계법령의 규정에 의하여 보전할 필요가 있는 경우, 일정 기간 동안 개인정보를 보관할 수 있습니다. 그 밖의 사항은 회사의 개인정보취급방침을 준수합니다.
- 개인정보 수집/이용에 동의하지 않을 수 있으나, 미동의시 이벤트에 참여가 불가능합니다.
개인정보 수집 항목 이름, 휴대폰번호, 이메일
수집 목적 어뷰징 등을 통한 부정 투표 방지 및 이벤트 당첨, 경품 발송
보유기간 투표 종료 후 3개월 이내 파기
본 이벤트의 당첨자 추첨 및 배송, 응모 및 당첨자 경품 배송관련 상담 업무 등은 슈퍼와이 주식회사, 피엠지 아시아에 위탁됩니다.

- 개인정보 수집/이용에 동의하지 않을 수 있으나, 미동의시 이벤트에 참여가 불가능합니다.
위탁업체명 위탁업무
슈퍼와이 주식회사 TOP 36 투표 참여자 정보 처리 및 관리
피엠지 아시아 TOP 36 투표 참여자 문의/답변 대응 및 경품 발송
확인 발표자료 신청하기
닫기