
一句話描述,我們可以把SRP分解成兩個部分,分別是SRP Asset,SRP Instance。
SRP Asset
SRP Asset是一個Unity Asset文件,用來存儲渲染管線的特定配置信息,包含的信息有:游戲物體是否應該投射陰影;使用什么樣的著色器質量級別;影子距離;默認材質配置。
此外還可以保存所有像在配置中保存和控制的信息和unity需要序列化的數據。SRP Asset定義了SRP的類型和所有配置數據的設置。
SRP Instance
SRP Instance是實際執行渲染的類,當unity發現工程使用SRP的時候,就會尋找當前使用的SRP Asset并請求一個渲染實例,該實例必須包含一個Render函數,
渲染實例表示管道配置。在渲染調用中,Unity可以執行如下操作:
- 清理framebuffer。
- 執行場景清理。
- 渲染游戲對象集。
- 從一個幀緩沖區到另一個幀緩沖區執行位塊傳輸;
- 渲染陰影。
- 應用后處理效果。
第一個SRP Asset案例
SRP Asset實際是一個繼承RenderPipelineAsset接口的類,需要實現CreatePipeline接口。當unity首次執行渲染命令的時候,則會調用這個接口并返回一個可用的渲染實例。
下面所展示的就是一個簡單的SRP Asset類。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset
{protected override RenderPipeline CreatePipeline(){return new CustomRenderPipeline();}
}
SRP Instance
在上面的敘述中,CustomRenderPipelineAsset類重寫了CreatePipeline方法,并返回了一個CustomRenderPipeline實例對象,該對象則是unity SRP渲染的入口。CustomRenderPipeline是一個繼承RenderPipeline的類,而且必須實現虛函數Render,該函數包含了兩個參數:
ScriptableRenderContext context:是一個command buffer對象,可以向其輸入您想執行的渲染指令。
Camera[] cameras:一系列用于渲染的相機。
先介紹一個簡單的例子:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;public class CustomRenderPipeline : RenderPipeline
{public CameraRender cameraRender = new CameraRender();public CustomRenderPipeline(){}protected override void Render(ScriptableRenderContext context, Camera[] cameras){foreach(var camera in cameras){cameraRender.Render(context, camera);}}
}
如上所示,我們把渲染的處理邏輯封裝到CameraRender類里,對每一個場景中的活動相機進行單獨處理。
下一步先定義CameraRender類:
using UnityEngine;
using UnityEngine.Rendering;public class CameraRenderer {ScriptableRenderContext context;Camera camera;public void Render (ScriptableRenderContext context, Camera camera) {this.context = context;this.camera = camera;}
}
然后正式定義Render函數,實現我們的自定義渲染管線。
- 繪制天空盒
在Render函數中是要繪制所有Camera可見的幾何體,我們聲明一個函數DrawVisibleGeometry用于處理一些特殊的工作,繪制天空盒也是在這個方法中處理,不過我們還另外聲明了一個方法來完成這個工作。代碼如下:
public void Render (ScriptableRenderContext context, Camera camera) {this.context = context;this.camera = camera;DrawVisibleGeometry();}void DrawVisibleGeometry () {context.DrawSkybox(camera);}
然而這樣仍不能顯示天空盒,這是因為我們調用DrawSkybox只是把命令添加到context的緩存隊列里,還需要調用context.Submit()方法才會正式執行緩存隊列里面的命令。
public void Render (ScriptableRenderContext context, Camera camera) {this.context = context;this.camera = camera;DrawVisibleGeometry();Submit();}void Submit () {context.Submit();}
最后我們可以看到下面的效果。

但是,這個渲染結果還是有問題,當我們調整相機的角度或位置時,渲染結果并沒有發生變化,這是因為我們并沒有將視角投影矩陣傳遞給Command Buffer。這個變換矩陣在Unity Shader中是unity_MatrixVP。當我們打開frame debugger面板,選擇一個draw call,這個變量就會顯示在右側的監視板上。當前,不管我們怎么調整相機的姿態,這個變量都會保持不變,通過調用SetupCameraProperties方法可以構建這個矩陣。如下所示

但是,這個渲染結果還是有問題,當我們調整相機的角度或位置時,渲染結果并沒有發生變化,這是因為我們并沒有將視角投影矩陣傳遞給Command Buffer。這個變換矩陣在Unity Shader中是unity_MatrixVP。當我們打開frame debugger面板,選擇一個draw call,這個變量就會顯示在右側的監視板上。當前,不管我們怎么調整相機的姿態,這個變量都會保持不變,通過調用SetupCameraProperties方法可以構建這個矩陣。如下所示
public void Render (ScriptableRenderContext context, Camera camera) {this.context = context;this.camera = camera;Setup();DrawVisibleGeometry();Submit();
}void Setup () {context.SetupCameraProperties(camera);
}
2. Command buffer
前面已經提到,context只有在調用Submit方法時才會執行渲染指令,在這之前都是將指令添加到渲染隊列中。像天空盒可以通過專業的方法添加指令,但是像其他任務則不能通過這樣添加,需要通過一個單獨的command buffer間接添加。因此我們需要另建一個buffer用來繪制其他的幾何對象。
const string bufferName = "Render Camera";CommandBuffer buffer = new CommandBuffer {name = bufferName
};
在項目開發中,profiler是一個非常強大和有用的工具,這里我們可以把我們的Command Buffer加入到profiler樣例中,這個樣例可以同時顯示在profiler和frame debugger面板,方法是在適當的位置調用BeginSample和EndSample方法,在我們的案例中將會分別放在Setup()方法和Submit()方法的開頭位置。BeginSample和EndSample方法必須使用相同的字符串參數作為樣例的名字,這里使用的是buffer的名字。
void Setup () {buffer.BeginSample(bufferName);context.SetupCameraProperties(camera);
}void Submit () {buffer.EndSample(bufferName);context.Submit();
}
執行Command Buffer中的命令只需要調用context的ExecuteCommandBuffer方法,并把buffer作為參數傳遞進去。
void Setup () {buffer.BeginSample(bufferName);ExecuteBuffer();context.SetupCameraProperties(camera);
}void Submit () {buffer.EndSample(bufferName);ExecuteBuffer();context.Submit();
}void ExecuteBuffer () {context.ExecuteCommandBuffer(buffer);buffer.Clear();
}
這時候打開frame debugger我們就可以看到渲染的案例如下:

3. 清除渲染目標
不管我們想要繪制什么東西,最終都會渲染到相機的渲染目標中。相機的渲染目標一般是幀緩存區,也可以是渲染紋理。渲染目標一般不會自動清除,也就是說之前渲染的結果會一直保存在渲染目標中,這樣會干擾我們渲染新的圖像。為了保證正確的渲染,我們必須清除渲染目標,以擺脫其舊的內容。通過調用ClearRenderTarget方法可以實現這個目的。
ClearRenderTarget函數需要至少三個參數,前兩個參數指定是否清理顏色緩存和深度緩存,一般都設為true,第三個參數指定清理緩存的顏色,這里使用Color.clear。
void Setup () {buffer.BeginSample(bufferName);buffer.ClearRenderTarget(true, true, Color.clear);ExecuteBuffer();context.SetupCameraProperties(camera);
}

打開frame debugger,可以看到清理渲染目標活動對應了一個叫DrawGL的樣例。而且這個案例被鑲嵌在另一個Render Camera樣例中,這是因為 ClearRenderTarget將清除任務包裝在了一個使用Command buffer的名字命名的樣例中。為了避免這種情況,可以調整一下清除目標的代碼的位置。
void Setup () {context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(bufferName);ExecuteBuffer();//context.SetupCameraProperties(camera);
}

現在我們可以看到Clear(color+Z+stencil),這表明顏色跟深度緩沖都沒清除了,stencil數據是同一緩存區的一部分。