Scriptable Render Pipeline


Scriptable Render Pipeline

SRP的核心是一堆API集合,使得整個渲染過程及相關配置暴露給用戶,使得用戶可以精確地控制項目的渲染流程。
SRP API為原有的Unity構件提供了一些新的接口(interface):

  • Lights
  • Materials
  • Cameras
  • Command Buffers

但是和Unity交互的方式變了。由於性能原因,當自定義SRP時,通常是在使用一組renderers(這里的render應該是指MeshRender之類的),而不是一個。

What is a Render Pipeline

什么是渲染管線?“Render Pipeline”,渲染管線,是許多技術的總稱,用於把物體(Objects,比如三維物體)顯示到屏幕上。概括性講,渲染管線包括:

  • Culling
  • Rendering Objects
  • Post processing

除了上面這些高級概念,渲染管線的每一個環節或者子任務,還可以進一步分解,你可以選擇如何去完成它。例如:可以使用一下方式渲染物體:

  • Multi-pass rendering - one pass per object per light 即一個物體被"渲染多次(多個pass)",每一個影響到該物體的light,都在一個單獨的pass中渲染
  • Single-pass - one pass per object 即每個物體中一個pass中渲染,也即一個物體只“渲染”一次
  • Deferred - Render surface properties to a g-buffer, perform screen space lighting 即先在g-buffer pass中把法線等幾何屬性“渲染”到g-buffer紋理中,然后在Lighting pass中進行屏幕空間的光照計算

當自定義SRP時,上面這些就是需要你做出決策的地方。每種技術都有許多權衡(trade offs)需要考慮,沒有一種技術是完美的。

What is a Scriptable Render Pipeline

如果Render Pipeline是為了完成渲染而需要執行的許多步驟,則Scriptable Render Pipeline是一個可以讓用戶使用Unity的C#腳本控制的pipeline,用於以用戶定義的方式完成渲染。

The Problem (Built-in pipelines存在的問題)

之前Unity已經提供了一些built-in pipelines,一些適合用於手游或者VR游戲(如 forward renderer),另一些用於高級終端游戲上,如主機游戲 (如 deferred renderer)。這些Unity內置的拿來即用的渲染方案具有通用性,“黑盒性”,但也有一些缺陷:

  • 只能用於做它們設計好的任務
  • 它們被設計的非常具有通用性,功能過於寬泛,意味着它們可以完成任何需求,但沒有任何完美精通或者適用的地方。(But they need to do everything, they are masters at nothing)
  • 它們不是那可配置的。(black box with injection points)
  • 擴展和修改容易產生錯誤(小的內部改變可能產生大的外部影響)
  • 有許多bugs無法修復(因為擅自“修復”可能會破壞項目)。

The Solution (用SRP來解決)

SRP API的出現就是為了解決上面的問題。它把渲染部分從Unity內置的“黑盒”概念 轉變成了用戶可控的每個項目可腳本控制的概念。也就是說,使用SRP,每一個項目都可以有一個符合自己的特色的渲染管線。使用SRP api可以對how rendering進行精細粒度的自定義,從低層到高層(form the low level to the high level)。

SRP 概述

從高層次用戶角度來看,SRP可以分為兩部分:SRP asset和SRP instance。在制作custom rendering pipeline時,兩者都很重要。

SRP Asset

SRP Asset是project asset,表示渲染管線的一個具體配置,例如:

  • 是否支持陰影?
  • 使用什么級別的shader quality?
  • shadow distance值是多少?
  • 默認材質配置

用戶想要控制的東西可以保存為配置的一部分,基本上是任何需要序列化的東西。SRP Asset表示SRP的類型(type)和其中的設置(settings)。

SRP Instance

SRP Instance是實際完成渲染的類(class)。當Unity發現SRP被啟用時,Unity會查看當前選擇的SRP Asset並且要求它提供一個渲染實例("rendering instance"),即SRP asset要返回一個實例包含一個'Render'函數。通常這個實例會緩存許多對應SRP asset中的配置信息。

SRP instance表示一個已知的渲染管線配置(pipeline configuration)。從渲染器角度看,調用動作可能如下:

  • Clearing the framebuffer
  • Performing scene culling
  • Rendering sets of objects
  • Doing blits from one frame buffer to another
  • Rendering shadows
  • Applying post process effects

SRP instance表示一個實際將要被實施的渲染(actual rendering)。

SRP Asset

SRP Asset包含一個接口,用戶用它配置渲染管線。當Unity第一次開始渲染時,Unity會調用 SRP Asset上的InternalCreatePipeline函數,然后,SRP Asset會返回一個可用的SRP instance。

SRP Asset是一個ScriptableObject,這意味着它可以保存為一個project asset(見下面的BasicAssetPipe)。

為了使項目啟用一個SRP asset,需要在Edit>Project Settings>Graphics的Scriptable Render Pipeline Settings欄中選擇你生成的SRP Asset。這樣設置好后,Unity就會使用SRP Asset中的配置進行rendering了,而不再使用standard Unity rendering。

An Simple Asset Example

SRP Asset的職責是包含配置信息和返回一個渲染實例。若SRP Asset上的某個設置發生了變化,則所有由這個Asset創建的實例會被銷毀,並且用新的設置創建新的實例,來進行下一幀的渲染。

下面的代碼是一個簡單的pipeline asset類。它包含一個color,由它返回的實例用來清除屏幕。CreateBasicAssetPipeline是在Editor下使用的一個工具, 在菜單欄上點SRP-Demo,再點01 - Create Basic Asset Pipeline即可創建一個BasicAssetPipe對應的SRP Asset,它在項目中的路徑為Assets/BasicAssetPipe.asset

[ExecuteInEditMode]
public class BasicAssetPipe : RenderPipelineAsset
{
    public Color clearColor = Color.green;

#if UNITY_EDITOR
    // Call to create a simple pipeline
    [UnityEditor.MenuItem("SRP-Demo/01 - Create Basic Asset Pipeline")]
    static void CreateBasicAssetPipeline()
    {
        var instance = ScriptableObject.CreateInstance<BasicAssetPipe>();
        UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/BasicAssetPipe.asset");
    }
#endif

    // Function to return an instance of this pipeline
    protected override IRenderPipeline InternalCreatePipeline()
    {
        return new BasicPipeInstance(clearColor);
    }
}

A complete asset example

除了返回實例和保存配置,SRP asset也用於做許多輔助功能,如:

  • Default material to use when creating 3d objects
  • Default material to use when creating 2d objects
  • Default material to use when creating particle systems
  • Default material to use when creating terrain systems

The Rendering entry point

SRP asset控制渲染配置,但是最終渲染是由SRP Render Pipeline instance完成的。SRP instance對應的類(class)是渲染邏輯(rendering logic)實際存在的地方。

SRP Instance的最簡單的形式僅僅包含一個Render函數。Render函數有兩個參數:

  • 一個ScriptableRenderContext類型的參數,相當於一個隊列,其中的元素是command buffer,可以把將要完成的渲染操作入隊(enqueue)。
  • 一個相機數組Camera[],表示已經啟用的,用於渲染的相機列表。

A basic pipeline

下面這個BasicPipeInstance類就是上面的BasicAssetPipeIRenderPipeline函數返回的SRP Instance。

public class BasicPipeInstance : RenderPipeline
{
    private Color m_ClearColor = Color.black;

    public BasicPipeInstance(Color clearColor)
    {
        m_ClearColor = clearColor;
    }

    public override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
        // does not so much yet :()
        base.Render(context, cameras);

        // clear buffers to the configured color
        var cmd = new CommandBuffer();
        cmd.ClearRenderTarget(true, true, m_ClearColor);
        context.ExecuteCommandBuffer(cmd);
        cmd.Release();
        context.Submit();
    }
}

上面代碼所表示的pipeline僅僅完成了簡單地用給定的顏色(由SRP Asset指定)清除屏幕的操作。注意:

  • Unity現有的CommandBuffers被用來完成許多操作(此處用於完成ClearRenderTarget)。
  • CommandBuffers是根據於對應的context進行調試的(scheduled)。
  • “渲染”的最后一步是調用Submit,它將(引起)執行所有入隊的command。

RenderPipelineRender函數就是你輸入渲染代碼,來自定義renderer的地方。Culling, Filtering, Changing render targets和Drawing操作都是在這里完成的。這就是你構造渲染器的地方!

SRP使用延遲執行的方式進行渲染。作為用戶,你的任務就是用ScriptableRenderContext構建一個命令隊列,然后去執行它們。

Culling

Culling(剔出)是指明將要在屏幕上渲染什么的過程。

Unity的Culling過程包含兩類:

  • Frustum culling: 視錐體剔出,計算在視錐體內的物體(這些物體會被渲染)。
  • Occlusion culling: 遮擋剔出,計算被別的物體遮擋的物體,這些物體將不會被渲染。

渲染開始時,首先要計算需要渲染哪些物體。這涉及到獲取相機,然后完成剔出(cull)操作(從給定相機的角度)。Culling操作返回一個對於給定相機有效的物體(objects和lights)列表,這些物體用於該相機進行后面的渲染步驟。

Culling in SRP

在SRP,用戶通常站在相機的視角來渲染對象。這和Unity built-in rendering一樣。SRP提供了一些有用的Culling相關的API。通常流程如下:

// Create an structure to hold the culling paramaters
ScriptableCullingParameters cullingParams;

//Populate the culling paramaters from the camera
if (!CullResults.GetCullingParameters(camera, stereoEnabled, out cullingParams))
    continue;

// if you like you can modify the culling paramaters here
cullingParams.isOrthographic = true;

// Create a structure to hold the cull results
CullResults cullResults = new CullResults();

// Perform the culling operation
CullResults.Cull(ref cullingParams, context, ref cullResults);

cullResults被用來完成接下來的rendering。

SRP Drawing

經過上面的步驟,該剔出的物體已經被剔出了,現在可把cull results渲染到屏幕上了。

需要提前做一些決策(考慮到以下因素):

  • 完成渲染的硬件
  • 想要實現的具體樣子(specific look)和風格/感覺(feel),即要實現的效果
  • 項目的類型

如,一個2D手游和一個PC上的第一人稱游戲所使用的渲染管線肯定會差異特別大。可能要做下面這些抉擇:

  • HDR vs LDR
  • Linear vs Gamma
  • MSAA vs Post Process AA
  • PBR Materials vs Simple Materials
  • Lighting vs No Lighting
  • Lighting Technique
  • Shadowing Technique

目前,我們將要展示一個簡單的沒有光照、可以渲染一些不透明物體的渲染器。

Filtering: Render Buckets and Layers

通常,渲染對象(rendering object)有一個具體的分類:透明的、不透明的、sub surface,或者別的類型。Unity使用隊列表示什么時候一個對象將會被渲染,即相同分類的對象被放在同一個隊列中,這些隊列也被稱為“桶”(bucket)。在SRP中,用戶指定使用哪些桶進行渲染。

除了“桶”的概念,標准的Unity layers也可以被使用。

這提供了額外的過濾能力。

// Get the opaque rendering filter settings
var opaqueRange = new FilterRenderersSettings();
 
//Set the range to be the opaque queues
opaqueRange.renderQueueRange = new RenderQueueRange()
{
    min = 0,
    max = (int)UnityEngine.Rendering.RenderQueue.GeometryLast,
};
 
//Include all layers
opaqueRange.layerMask = ~0;

Draw Settings: How things should be drawn

上面的filtering和culling決定了將要渲染哪些對象。接下來,我們需要決定怎樣渲染它們。SRP提供了許多可配置選項。用於配置的數據結構是DrawRenderSettings。它允許配置以下選項:

  • Sorting - 物體渲染的順序,如:從前到后(front to back)或者從后到前(back to front)。
  • Per Renderer flags - Unity應該向shader傳入什么'built in'設置,包括:per object light probes, per object light maps等。
  • Rendering flags - 使用哪種batching算法,instancing vs non-instancing。
  • Shader Pass - 當前draw call應該使用哪個shader pass
// Create the draw render settings
// note that it takes a shader pass name
var drs = new DrawRendererSettings(myCamera, new ShaderPassName("Opaque"));
 
// enable instancing for the draw call
drs.flags = DrawRendererFlags.EnableInstancing;
 
// pass light probe and lightmap data to each renderer
drs.rendererConfiguration = RendererConfiguration.PerObjectLightProbe | RendererConfiguration.PerObjectLightmaps;
 
// sort the objects like normal opaque objects
drs.sorting.flags = SortFlags.CommonOpaque;

Drawing

現在我們已經有了發起一次draw call所需要的三樣東西:

  • Culling results (剔出結果)
  • Filtering rules (過濾規則)
  • Drawing rules (繪制規則)

下面的代碼發起了一次draw call。在SRP中,你一般不渲染單獨的一個或幾個網格(individual meshes),而是發起一次draw call,一次渲染一大批網格。這能減少執行開銷。

發起一次draw call,需要結合上面我們已經構建的參數。

// draw all of the renderers
context.DrawRenderers(cullResults.visibleRenderers, ref drs, opaqueRange);

// submit the context, this will execute all of the queued up commands.
context.Submit();

這段代碼會把對象渲染到當前綁定的渲染目標上(render target)。也可以通過command buffer來切換不同的渲染目標。

參考:

  1. Scriptable Render Pipeline Wiki
  2. Unity SRP Overview: Scriptable Render Pipeline

第一次發表於我的知乎專欄:https://zhuanlan.zhihu.com/p/69046003


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM