|
一、前言
Unity SRP是一组底层API接口,允许C#脚本对渲染管进行调配,其作作用与CommandBuffer类似。但与内置管线相比,暴露了更多的底层接口,使得其可定制化程度更高。官方基于SRP,针对不同性能需求实现了通用渲染管线 (URP)、高清渲染管线 (HDRP) 两种渲染管线,在后续章节中会进行介绍。
本章节会介绍SRP的特性与基础使用,并完成一个简单的自定义管线。Unity使用2020.3.11f,Core RP Library Package使用10.5.1。
1.1 可编程管线
为什么要推出可编程管线?
- 内置管线性能差:为了兼容多平台,有很多冗余处理,难以充分利用硬件性能
- 内置管线可扩展性差:虽然内置管线有CommonBuff可以对管线进行微调,但可处理的内容较窄,而且代码维护、阅读不太方便。
可以对哪些内容进行定制?
除了Canvas - Screen Space的UI,其它渲染内容与方式都可以自行控制。具体包括
- 摄像机渲染顺序、渲染方式、渲染对象
- 场景对象的剔除、排序、渲染
- 灯光处理
- 阴影处理
- ……
1.2 SRP Batcher
SRP针对相同Shader变体的渲染做了优化处理:SRP Batch。CPU向GPU提交数据后,GPU需要设置渲染状态后进行渲染。SRP Batch针对相同的Shader变体,将这部分渲染数据提交后,只进行一次状态设置,减少了渲染状态的变化。(与Unity的其它Batcher不同,SRP并不会减少DC)
二、创建一个管线
2.1 Package
创建一个内置管线工程,SRP的使用需要Core RP Library Package。
2.2 逻辑入口与资源创建
Unity每帧调用RenderPipeline.Render方法进行画面绘制,这也是处理渲染逻辑的地方。
ScriptableRenderContext是Unity低级图形接口,可以调度渲染命令。存在两种渲染调度方式:ScriptableRenderContext.ExecuteCommandBuffer和ScriptableRenderContext静态方法。在设置完命令后,通过Submit 将命令提交到GPU缓冲区。Unity中的CommandBuffer在之前章节有过介绍
using UnityEngine;
using UnityEngine.Rendering;
public class FirstPipeline : RenderPipeline
{
public FirstPipeline() { }
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
// 创建并调度命令以清除当前渲染目标
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.red);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
// 指示可编程渲染上下文告诉图形 API 执行调度的命令
context.Submit();
}
}
RenderPipeline只定义了处理逻辑,但逻辑需要应用到实例资源才能生效。RenderPipelineAsset用于Editor中创建RenderPipeline对应的资源。
using UnityEngine;
using UnityEngine.Rendering;
[CreateAssetMenu(menuName = "Rendering/FirstPipeline")]
public class FirstPipelineAsset : RenderPipelineAsset
{
protected override RenderPipeline CreatePipeline()
{
return new FirstPipeline();
}
}
2.3 创建管线资源
创建管线资源
创建完管线资源后,可在Editor -> Project Settings -> Graphics中配置,启用自定义管线。由于此时我们只将屏幕设置为了红色,并未处理Camera,因此场景对象不会渲染(Canvas为Screen Space - Overlay不受影响)。
三、管线逻辑
在了解了管线的创建逻辑后,我们来详细处理帧处理方式,在此之前我们需要明确管线需要完成的工作。渲染的流程分为CPU准备数据、GPU设置状态、渲染,SRP处于CPU的数据处理与提交阶段。
- 场景加速优化:场景对象管理、Camera剔除
- 渲染顺序:渲染队列(Camera;不透明物体;半透明物体)、排序
- 渲染内容:灯光、阴影、场景对象、UI
3.1 RenderPipeline
除了处理渲染逻辑Render方法外,RenderPipelineManager还为管线处理生命周期提供了静态接口(需要手动在Render中调用)。
// 外部组件
void Start()
{
RenderPipelineManager.beginCameraRendering += BeginCameraRendering;
RenderPipelineManager.beginFrameRendering += BeginFrameRendering;
RenderPipelineManager.endCameraRendering += EndCameraRendering;
RenderPipelineManager.endFrameRendering += EndFrameRendering;
}
private void OnDestroy()
{
RenderPipelineManager.beginCameraRendering -= BeginCameraRendering;
RenderPipelineManager.beginFrameRendering -= BeginFrameRendering;
RenderPipelineManager.endCameraRendering -= EndCameraRendering;
RenderPipelineManager.endFrameRendering -= EndFrameRendering;
}
// 管线调用生命周期方法
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
RenderPipeline.BeginFrameRendering(context, cameras);
// 创建并调度命令以清除当前渲染目标
var cmd = new CommandBuffer(){ name = "claer"};
cmd.ClearRenderTarget(true, true, Color.red);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
// 指示可编程渲染上下文告诉图形 API 执行调度的命令
context.Submit();
RenderPipeline.EndFrameRendering(context, cameras);
}
3.2 创建SRP Shader
创建分别创建不透明Shader与半透明Shader,并生成相应材质,用于后续测试使用。
3.3 场景对象渲染
视锥体剔除
此处的剔除是Unity的内置方法,尚不清楚具体的处理方式,也没有接口能够修改剔除逻辑。若需要对剔除算法优化,只能手动管理场景对象。
// 摄像机剔除
camera.TryGetCullingParameters(out var cullingParameters);
var cullingResults = context.Cull(ref cullingParameters);
更新着色器Camera数据
context.SetupCameraProperties(camera);
渲染场景对象
// 不透明对象
RenderCamera(context, camera, "First1", SortingCriteria.CommonOpaque, RenderQueueRange.opaque);
// 半透明对象
RenderCamera(context, camera, "First2", SortingCriteria.CommonTransparent, RenderQueueRange.transparent);
private void RenderCamera(ScriptableRenderContext context, Camera camera, string TagId, SortingCriteria criteria, RenderQueueRange queue)
{
RenderPipeline.BeginCameraRendering(context, camera);
// Camera剔除
camera.TryGetCullingParameters(out var cullingParameters);
var cullingResults = context.Cull(ref cullingParameters);
// 基于当前摄像机,更新内置着色器变量的值
context.SetupCameraProperties(camera);
// 渲染命令:队列、排序、光照Tag
ShaderTagId shaderTagId = new ShaderTagId(TagId);
var sortingSettings = new SortingSettings(camera) { criteria = criteria };
DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);
FilteringSettings filteringSettings = new FilteringSettings(queue);
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
// 绘制天空盒
if (camera.clearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null && queue != RenderQueueRange.transparent)
{
context.DrawSkybox(camera);
}
context.Submit();
RenderPipeline.EndCameraRendering(context, camera);
}
创建两个Cube,一个使用不透明材质,另一个使用半透明材质。此时场景渲染存在4个批次:清空渲染目标;渲染一个不透明Cube;渲染天空盒;渲染半透明Cube。
3.4 Sence视图中显示UI
若Canvas类型为Screen Space,此时UI并不在我们的管线逻辑中,却仍然可以正常显示。但Sence视图下无法显示UI,因为Sence视图是以场景空间绘制UI的。此时需要我们将Game视图中的几何体提交到Sence绘制,并且UI的Shader也替换为自定义Shader,参考默认的UI-Shader逻辑处理。
Sence视图Canvas处理
参考
- Scriptable Render Pipeline fundamentals
- Creating a custom render pipeline
- Scriptable Render Pipeline Batcher
- SamUncle:关于静态批处理/动态批处理/GPU Instancing /SRP Batcher的详细剖析
- SRP Core | Core RP Library | 10.5.1
- Catlike Coding - Custom Pipeline
- Catlike Coding - Draw Calls
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|