在计算机图形学里面,有一个叫“渲染管线”这样的术语。在Unity3D的官方技术文档里,也有“渲染管线”这样的一个术语。其实,这是两个地方的“渲染管线”虽然名字一样,但实际上,它们却是含意完全不同的两个概念。计算机图形学里所讲的“渲染管线”是一个是物理上(GPU上)的渲染流水线,是一个硬件层的管线,而Unity技术文档里的“渲染管线”是逻辑上(CPU上)的渲染流水线,是一个应用层的管线。
1、计算机图形学中的“渲染管线”
显卡把三维场景的各种数据结构行处理成一张可显示于屏幕的图像的过程称为渲染。现代显卡一般是按现代的计算机图形学理论进行设计的。而现代计算机图形学理论,一般将图形的渲染过程清晰地拆分为几个有序的处理环节,每个环节专注解决一个特定的问题。这种设计思想称为流水线思想。计算机图形学理论一般认为,一个三维图形的渲染过程,应该包含以下几个环节:
也可以简化一下描述为以下过程:
(参考:Graphics pipeline - Wikipedia)
这个图形渲染流水线过程的所有环节中,除了Application环节外,其它所有处理环节都是在显卡里完成的。所以这个流水线是一个硬件上的流水线。
2、Unity引擎中的“渲染管线”
Unity里的渲染流水线,是在GPU渲染流水线之上进行封装、抽象、逻辑化后的一个软件层面的流水线。它包含了图形学渲染流水线中的“Application”阶段,但是不完全等于这个“Application”阶段。
Unity提出渲染管线的概念,也使用了流水线设计细想。这个思想主要体现在自定义渲染流水线(ScriptableRenderPipeline)领域。而实际上,Unity所指的这个流水线,正指是这个ScriptableRenderPipeline里的Render()函数(这个Render函数的处理过程,就是一个流水线)。按Unity官方文档里对渲染管理的解析,开发者在自定义一个Unity的渲染管线时,至少应包含以下处理环节:
参考:渲染管线简介 - Unity 手册 (unity3d.com)
过通ScriptableRenderPipeline的Render()函数,Unity向开发者提供了一个可完全自定义的逻辑层的渲染管线的能力。
根据Unity的官方倡议,开发者在自定义渲染管线并实现Render()函数时,应至少实现剔除、渲染和后处理这三个阶段,当然,开发者也可以选择不尊循这些规则,完全按自己的渲染需求来实现自己独特的逻辑层的渲染管线。
在逻辑层的渲染管线(ScriptableRenderPipeline)里,真正实现渲染指令的是CommandBuffer。同时,Unity还提供了其它的很多类和组件帮住开发者快速实现自己的自定义渲染管线。例如:ScriptableRenderContext,RenderingData,ScriptableRenderer,ScriptableRenderPipelineAsset,RenderPass,RendererFeature等。
3、一个简单的Unity自定义渲染管线
根据Unity自定义渲染管线的规则,我们可以自定义一条简单的渲染管线ScriptableRenderPipeline。
/*
这是自定义可编程渲染管线的简化示例。
它演示基本渲染循环的工作方式。
它演示最清晰的工作流程,而不是最高效的运行时性能。
*/
using UnityEngine;
using UnityEngine.Rendering;
public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}
protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
// 创建并调度命令以清除当前渲染目标
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
// 指示图形 API 执行所有调度的命令
context.Submit();
}
}
剔除
剔除是过滤掉对摄像机不可见的几何体的过程。
要在可编程渲染管线中进行剔除,请执行以下操作:
- 使用有关摄像机的数据填充 ScriptableCullingParameters 结构;为此,请调用 Camera.TryGetCullingParameters。
- 可选:手动更新
ScriptableCullingParameters
结构的值。 - 调用 ScriptableRenderContext.Cull,并将结果存储在一个
CullingResults
结构中。
此示例代码扩展了上面的示例,演示如何清除渲染目标,然后执行剔除操作:
/*
这是自定义可编程渲染管线的简化示例。
它演示基本渲染循环的工作方式。
它演示最清晰的工作流程,而不是最高效的运行时性能。
*/
using UnityEngine;
using UnityEngine.Rendering;
public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}
protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
// 创建并调度命令以清除当前渲染目标
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
// 遍历所有摄像机
foreach (Camera camera in cameras)
{
// 从当前摄像机获取剔除参数
camera.TryGetCullingParameters(out var cullingParameters);
// 使用剔除参数执行剔除操作,并存储结果
var cullingResults = context.Cull(ref cullingParameters);
}
// 指示图形 API 执行所有调度的命令
context.Submit();
}
}
绘制
绘制是指示图形 API 使用给定设置绘制一组给定几何体的过程。
要在 SRP 中进行绘制,请执行以下操作:
- 如上所述执行剔除操作,并将结果存储在
CullingResults
结构中。 - 创建和配置 FilteringSettings 结构,它描述如何过滤剔除结果。
- Create and configure a DrawingSettings struct, which describes which geometry to draw and how to draw it.
- 可选:默认情况下,Unity 基于 Shader 对象设置渲染状态。如果要覆盖即将绘制的部分或所有几何体的渲染状态,可以使用 RenderStateBlock 结构执行此操作。
- 调用 ScriptableRenderContext.DrawRenderers,并将创建的结构作为参数进行传递。Unity 根据设置绘制过滤后的几何体集。
此示例代码基于上面的示例进行构建,演示如何清除渲染目标,执行剔除操作,然后绘制生成的几何体:
/*
这是自定义可编程渲染管线的简化示例。
它演示基本渲染循环的工作方式。
它演示最清晰的工作流程,而不是最高效的运行时性能。
*/
using UnityEngine;
using UnityEngine.Rendering;
public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}
protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
// 创建并调度命令以清除当前渲染目标
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
// 遍历所有摄像机
foreach (Camera camera in cameras)
{
// 从当前摄像机获取剔除参数
camera.TryGetCullingParameters(out var cullingParameters);
// 使用剔除参数执行剔除操作,并存储结果
var cullingResults = context.Cull(ref cullingParameters);
// 基于当前摄像机,更新内置着色器变量的值
context.SetupCameraProperties(camera);
// 基于 LightMode 通道标签值,向 Unity 告知要绘制的几何体
ShaderTagId shaderTagId = new ShaderTagId("ExampleLightModeTag");
// 基于当前摄像机,向 Unity 告知如何对几何体进行排序
var sortingSettings = new SortingSettings(camera);
// 创建描述要绘制的几何体以及绘制方式的 DrawingSettings 结构
DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);
// 告知 Unity 如何过滤剔除结果,以进一步指定要绘制的几何体
// 使用 FilteringSettings.defaultValue 可指定不进行过滤
FilteringSettings filteringSettings = FilteringSettings.defaultValue;
// 基于定义的设置,调度命令绘制几何体
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
// 在需要时调度命令绘制天空盒
if (camera.clearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null)
{
context.DrawSkybox(camera);
}
// 指示图形 API 执行所有调度的命令
context.Submit();
}
}
}