Unity6 URP17使用初探

HONT發表於2024-11-01

1.簡介

隨著Unity6的釋出,URP17也已經可以上手使用,相對舊的版本改動較大的是加入了

RenderGraph、STP、Foveated rendering、GPU Resident Drawer等功能,部分功能只需要開關引數即可使用,

而GRD更像是Gpu driven管線下的SRP Batches升級,RenderGraph相較於HDRP之前使用的版本換了一套API。

最大的不同是,使用URP17編寫Feature時,必須依賴於RenderGraph進行編寫,接下來就來介紹一下。

1.1 相關Demo

目前URP17比較容易找到的學習Demo如下:

  • URP17 Package內自帶的Samples
  • Fantasy Kingdom (https://assetstore.unity.com/packages/essentials/tutorial-projects/fantasy-kingdom-in-unity-6-urp-298128)
  • 在Unity Hub可以直接下載的Universal 3D sample(包含3個場景)

2.RenderGraph

開啟任意URP的示例場景檢視,RenderGraphView上各圖示含義如下:

  1. 說明這是一個外部置入的RenderTexture
  2. 紅色方塊說明存在寫入操作
  3. 綠色方塊指存在讀取操作(紅綠方塊說明讀寫操作)
  4. 該圖示說明標記了全域性RenderTexture

而頂部表明當前渲染一幀的各個Pass,左側是各類RT。

URP17同時保留了舊的Feature邏輯與RenderGraph邏輯(開啟任意pass檔案為例):

public class DistortTunnelPass_Tunnel : ScriptableRenderPass
{
    class PassData
    {
        public Renderer tunnelObject;
        public Material tunnelMaterial;
    }


#pragma warning disable 618, 672 // Type or member is obsolete, Member overrides obsolete member

    // Unity calls the Configure method in the Compatibility mode (non-RenderGraph path)
    public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescripor)
    {
    }

    // Unity calls the Execute method in the Compatibility mode
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
    }

#pragma warning restore 618, 672

    // Unity calls the RecordRenderGraph method to add and configure one or more render passes in the render graph system.
    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
    }
}

參考時忽略掉Configure和Execute的邏輯,執行邏輯關注RecordRenderGraph函式。

2.1 操作方式的改變

在RenderGraph中,之前的RTHandle由於不在該系統中託管,進入RenderGraph的材質都需要呼叫API進行轉換,

轉換為RendeGraph的RT後,無需考慮釋放操作:

RenderTextureDescriptor textureProperties = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.Default, 0);
TextureHandle textureHandle = UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "My texture", false);

相關文件:

https://docs.unity3d.com/Manual/urp/render-graph-create-a-texture.html

此外RenderGraph對於空呼叫的pass,也會剔除進行最佳化,使用者需要手動標記以防止被剔除。

2.1 RecordRenderGraph

在該函式內可組織渲染邏輯,pass相關的邏輯需放在對應的程式碼塊中,例如:

using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out _))
{
    builder.UseTexture(rt1);
    builder.SetRenderAttachment(resourceData.activeColorTexture, 0);

    builder.SetRenderFunc<PassData>((data, context) =>
    {
        MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
        materialPropertyBlock.SetTexture("_BlitTexture", rt1);
        materialPropertyBlock.SetVector("_BlitScaleBias", new Vector4(1, 1, 0, 0));

        context.cmd.DrawProcedural(Matrix4x4.identity, material, 0, MeshTopology.Triangles, 3, 1, materialPropertyBlock);
    });
}

URP提供了多種RenderPass,例如處理光柵化相關邏輯使用RasterRenderPass組織相關邏輯。

在RenderPass的程式碼塊中可使用builder物件配置RenderTarget、標記材質的讀寫等

而具體的pass繪製邏輯則在SetRenderFunc程式碼塊中。

RecordRenderGraph內可以呼叫多次AddRenderPass,但URP並沒有整理舊API的程式碼和相關工具類,

以至於容易使用舊的API導致報錯,這點需要注意。

3.編寫Feature

3.1 Blit與SetTarget

從前有句俗話“切RT的效能消耗相當於半個pass”,Unity SRP在幾個版本的升級都在逐漸強調不切RenderTarget直接繪製,

如Cockpit Demo的螢幕空間描邊。

3.2 螢幕模糊Demo

下面透過螢幕模糊Demo案例,演示URP17下pass的編寫。

透過外部EnqueuePass的方式,在場景中透過控制器指令碼新增該Pass,

MyBlurSceneController.cs:

using UnityEngine;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering;

public class MyBlurSceneController : MonoBehaviour
{
    public Material material;
    [Range(2, 15)] public int blurPasses = 3;
    [Range(0, 4)] public int downSample = 0;
    [Range(0.0f, 10f)] public float offset = 0.2f;

    public RenderPassEvent injectionPoint = RenderPassEvent.BeforeRenderingPostProcessing;
    public int injectionPointOffset = 0;
    public ScriptableRenderPassInput inputRequirements = ScriptableRenderPassInput.Color;
    public CameraType cameraType = CameraType.Game;

    private MyBlurPass mMyBlurPass;


    private void OnEnable()
    {
        SetupPass();

        RenderPipelineManager.beginCameraRendering += OnBeginCamera;
    }

    private void OnDisable()
    {
        RenderPipelineManager.beginCameraRendering -= OnBeginCamera;
    }

    public virtual void SetupPass()
    {
        mMyBlurPass = new MyBlurPass();

        mMyBlurPass.renderPassEvent = injectionPoint + injectionPointOffset;
        mMyBlurPass.material = material;

        mMyBlurPass.ConfigureInput(inputRequirements);
    }

    public virtual void OnBeginCamera(ScriptableRenderContext ctx, Camera cam)
    {
        if (mMyBlurPass == null || material == null)
            return;

        if ((cam.cameraType & cameraType) == 0) return;

        mMyBlurPass.blurPasses = blurPasses;
        mMyBlurPass.downSample = downSample;
        mMyBlurPass.offset = offset;

        cam.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(mMyBlurPass);
    }
}

MyBlurPass.cs:

using UnityEngine;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule.Util;

public class MyBlurPass : ScriptableRenderPass
{
    public class PassData
    {
        public TextureHandle tempRt1;
        public TextureHandle tempRt2;
    }

    public Material material;
    [Range(2, 15)] public int blurPasses = 3;
    [Range(1, 4)] public int downSample = 1;
    [Range(0.0f, 10f)] public float offset = 0.2f;


    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        var resourceData = frameData.Get<UniversalResourceData>();
        var passData = new PassData();

        var w = Screen.width >> downSample;
        var h = Screen.height >> downSample;

        RenderTextureDescriptor textureProperties = new RenderTextureDescriptor(w, h, RenderTextureFormat.Default, 0);
        passData.tempRt1 = UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "MyBlurPassTempRt1", false);

        textureProperties = new RenderTextureDescriptor(w, h, RenderTextureFormat.Default, 0);
        passData.tempRt2 = UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "MyBlurPassTempRt2", false);

        var rt1 = passData.tempRt1;
        var rt2 = passData.tempRt2;

        //將螢幕RT Blit到rt1上
        var para = new RenderGraphUtils.BlitMaterialParameters(resourceData.activeColorTexture, rt1, material, 0);
        renderGraph.AddBlitPass(para, "MyBlurPassBlitFirst");

        material.SetFloat("_SampleOffset", offset);

        //模糊迭代
        for (int i = 0; i < blurPasses - 1; ++i)
        {
            para = new RenderGraphUtils.BlitMaterialParameters(rt1, rt2, material, 0);
            renderGraph.AddBlitPass(para, $"MyBlurPassBlit_{i}");

            var tmp = rt1;
            rt1 = rt2;
            rt2 = tmp;
        }

        //透過直接繪製的方式,將模糊RT繪製到螢幕上
        using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out _))
        {
            builder.UseTexture(rt1);
            builder.SetRenderAttachment(resourceData.activeColorTexture, 0);

            builder.SetRenderFunc<PassData>((data, context) =>
            {
                MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
                materialPropertyBlock.SetTexture("_BlitTexture", rt1);
                materialPropertyBlock.SetVector("_BlitScaleBias", new Vector4(1, 1, 0, 0));

                context.cmd.DrawProcedural(Matrix4x4.identity, material, 0, MeshTopology.Triangles, 3, 1, materialPropertyBlock);
            });
        }
    }
}

接著在ShaderGraph中連出模糊的邏輯,注意Blit對應的引數_BlitTexture、_BlitScaleBias:

最後在場景中掛載控制器以及材質球,即可使用該模糊Pass。