渲染循环是指在单一帧中发生的所有渲染操作的术语。本页面包含有关在基于 Unity 可编程渲染管线的自定义渲染管线中创建简单渲染循环的信息。
本页面上的代码示例演示使用可编程渲染管线的基本原则。可以使用此信息构建自己的自定义可编程渲染管线,或了解 Unity 的预构建可编程渲染管线如何工作。
开始为渲染循环编写代码之前,必须准备好项目。
步骤如下所示:
1.创建与 SRP 兼容的着色器。 2.创建一个或多个要渲染的游戏对象。 3.创建自定义 SRP 的基本结构。 4.可选:如果计划扩展简单自定义 SRP 以添加更复杂的功能,请安装 SRP Core 包。SRP Core 包中包含 SRP Core 着色器库(可以用于使着色器与 SRP Batcher 兼容)以及用于常见操作的实用程序函数。有关更多信息,请参阅 SRP Core 包文档。
在可编程渲染管线中,使用 LightMode
通道标签确定如何绘制几何体。有关通道标签的更多信息,请参阅 ShaderLab:向通道分配标签。
此任务演示如何创建非常简单的无光照 Shader 对象,其 LightMode 通道标签值为 ExampleLightModeTag
。
1.在项目中创建一个新着色器资源。有关创建着色器资源的说明,请参阅着色器资源。 2.在 Project 视图中,双击着色器资源以在文本编辑器中打开着色器源代码。 3.将现有代码替换为以下内容:
// 这定义一个与自定义可编程渲染管线兼容的简单无光照 Shader 对象。
// 它应用硬编码颜色,并演示 LightMode 通道标签的使用。
// 它不与 SRP Batcher 兼容。
Shader "Examples/SimpleUnlitColor"
{
SubShader
{
Pass
{
// LightMode 通道标签的值必须与 ScriptableRenderContext.DrawRenderers 中的 ShaderTagId 匹配
Tags { "LightMode" = "ExampleLightModeTag"}
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
float4x4 unity_MatrixVP;
float4x4 unity_ObjectToWorld;
struct Attributes
{
float4 positionOS : POSITION;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
};
Varyings vert (Attributes IN)
{
Varyings OUT;
float4 worldPos = mul(unity_ObjectToWorld, IN.positionOS);
OUT.positionCS = mul(unity_MatrixVP, worldPos);
return OUT;
}
float4 frag (Varyings IN) : SV_TARGET
{
return float4(0.5,1,0.5,1);
}
ENDHLSL
}
}
}
要测试渲染循环是否可正常工作,必须创建要渲染的内容。此任务演示如何在场景中放置使用在上一个任务中创建的 SRP 兼容着色器的游戏对象。
1.在 Unity 项目中创建一个新材质资源。有关说明,请参阅材质。 2.将着色器资源分配给材质资源。有关说明,请参阅材质。 3.在场景中创建一个立方体。有关说明,请参阅原始对象。 4.将材质分配给它。有关说明,请参阅材质。
准备的最后阶段是创建自定义 SRP 所需的基本源文件,并告知 Unity 开始使用自定义 SRP 进行渲染。
1.按照创建渲染管线实例和渲染管线资源中的说明,创建一个继承自 RenderPipeline
的类和一个兼容渲染管线资源。
2.按照设置激活的渲染管线资源中的说明,设置激活的渲染管线资源。Unity 会立即开始使用自定义 SRP 进行渲染,这意味着 Scene 视图和 Game 视图会为空白,直到向自定义 SRP 添加代码。
在简单渲染循环中,基本操作有:
清除意味着移除在最后一帧期间绘制的内容。渲染目标通常是屏幕;但是,也可以渲染到纹理以创建“画中画”效果。这些示例演示如何渲染到屏幕,这是 Unity 的默认行为。
要清除可编程渲染管线中的渲染目标,请执行以下操作:
1.使用 Clear
命令配置 CommandBuffer
。
2.将 CommandBuffer
添加到 ScriptableRenderContext
上的命令队列;为此,请调用 ScriptableRenderContext.ExecuteCommandBuffer。
3.指示图形 API 执行 ScriptableRenderContext
上的命令队列;为此,请调用 ScriptableRenderContext.Submit。
与所有可编程渲染管线操作一样,使用 RenderPipeline.Render 方法作为此代码的入口点。此示例代码演示如何执行此操作:
/*
这是自定义可编程渲染管线的简化示例。
它演示基本渲染循环的工作方式。
它演示最清晰的工作流程,而不是最高效的运行时性能。
*/
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();
}
}
剔除是过滤掉对摄像机不可见的几何体的过程。
要在可编程渲染管线中进行剔除,请执行以下操作:
1.使用有关摄像机的数据填充 ScriptableCullingParameters 结构;为此,请调用 Camera.TryGetCullingParameters。
2.可选:手动更新 ScriptableCullingParameters
结构的值。
3.调用 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 中进行绘制,请执行以下操作:
1.如上所述执行剔除操作,并将结果存储在 CullingResults
结构中。
2.创建和配置 FilteringSettings 结构,它描述如何过滤剔除结果。
3.创建和配置 DrawingSettings 结构,它描述要绘制的几何体以及如何进行绘制。
4.可选:默认情况下,Unity 基于 Shader 对象设置渲染状态。如果要覆盖即将绘制的部分或所有几何体的渲染状态,可以使用 RenderStateBlock 结构执行此操作。
5.调用 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();
}
}
}