英文原文:https://resources.unity.com/games/introduction-universal-render-pipeline-for-advanced-unity-creators?ungated=true
SRP 的一个重要功能是您可以使用 C# 脚本在渲染过程的几乎任何阶段添加代码。 脚本可以在以下阶段注入:
- Rendering shadows
- Rendering prepasses
- Rendering G-buffer
- Rendering Deferred lights
- Rendering opaques
- Rendering Skybox
- Rendering transparents
- Rendering post-processing
您可以通过通用渲染器数据资产检查器中的添加渲染器功能选项在渲染过程中注入脚本。 请记住,在使用 URP 时,有一个 Universal Renderer Data 对象和一个 URP Asset。 URP Asset有一个渲染器列表,其中至少分配了一个通用渲染器数据对象。 它是您在 Project Settings > Graphics > Scriptable Render Pipeline Settings 中分配的资产。
如果您正在尝试针对不同场景使用多个设置资源,那么将以下脚本附加到您的主摄像机可能会很有用。 在 Inspector 中设置 Pipeline Asset。 然后它会在加载新场景时切换资产。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
[ExecuteAlways]
public class AutoLoadPipelineAsset : MonoBehaviour
{
public UniversalRenderPipelineAsset pipelineAsset;
// Start is called before the first frame update
void OnEnable()
{
if (pipelineAsset)
{
GraphicsSettings.renderPipelineAsset = pipelineAsset;
}
}
}
下一节介绍两种不同类型的渲染器功能,一种适用于艺术家,另一种适用于有经验的程序员。
渲染对象
游戏中的一个常见问题是当玩家角色消失在环境物体后面时,他们会看不到他们的视线。 您可以尝试移动相机以使角色始终在视野中,或者将环境调整为尽可能开放。 但这样的选择并不总是可用的。 一个很好的技巧是当环境模型出现在角色和相机之间时显示角色的轮廓,如下图所示。
以下是创建此轮廓的方法:
- 首先,您需要在角色被蒙版时使用的材质。 创建材质并将着色器设置为 Universal Render Pipeline > Lit 或 Unlit(上图显示了 Lit 选项)。 设置 Surface Inputs > Base Map 颜色。 在此示例中,材质称为 Character。
- 为了避免渲染角色的次数过多,让我们将它放在一个特殊的图层上。 选择角色,将 SeeBehind 图层添加到图层列表并为角色选择它。
3. 选择 URP 资产使用的渲染器数据对象。 转到不透明图层蒙版并排除 SeeBehind 图层。 然后角色就会消失。
4. 单击添加渲染器功能并选择渲染对象(实验)。
5. 填写此渲染对象通道的设置。 给它一个名字并选择何时触发渲染。 在这个例子中,它被称为 AfterRenderingOpaques。
将图层蒙版设置为 SeeBehind 图层,这是为角色选择的图层。 展开 Overrides 并设置在步骤 1 中创建的材质。您将希望在渲染时使用 Depth,而不必通过写入来更新深度缓冲区。 将 Depth Test 设置为 Greater,以便仅当到渲染像素的距离比当前存储在深度缓冲区中的距离更远时,才会渲染此 Pass。
6. 在这个阶段,你只能看到角色在另一个物体后面时的轮廓。 当它在全视图中时,你根本看不到这个角色。 要解决此问题,请添加另一个渲染对象功能。 这次您不需要更新 Overrides 面板。 此 Pass 将在未被其他对象遮盖时绘制角色。
剪影技巧是使用 URP 工作流程添加效果的一个很好的例子,由于依赖于编码,因此使用内置渲染管道工作流程难以实现。
渲染器功能
可以在 URP 的任何阶段使用渲染器功能来影响最终渲染。 让我们来看一个添加后处理效果的简单示例。 在使用内置渲染管道的项目中,您必须添加一个 Graphics。 使用 OnRenderImage 回调进行 Blit。 此示例使用带有材质的函数版本来处理图像中的每个像素。
- 首先在项目 Assets 文件夹中找到合适的文件夹。 右键单击并选择Create > Rendering > URP Renderer Feature。 将其命名为 TintFeature。
- 双击默认的 TintFeature 文件。 它是一个包含渲染器功能样板的 C# 脚本。
- 将这些属性添加到 CustomRenderPass 类。 该材质将包含您应用于渲染图像当前状态的着色器。
private Material material;
private RenderTargetIdentifier source;
private RenderTargetHandle tempTexture;
- 将构造函数添加到 CustomRenderPass 以初始化当前渲染的源纹理以及处理此纹理时要使用的材质。 您还需要初始化一个临时纹理以存储使用您的材质处理当前渲染纹理的结果。
public CustomRenderPass(Material material) : base()
{
this.material = material;
tempTexture.Init("_TempTintTexture");
}
- 添加一个SetSource方法来初始化CustomRenderPass类的source属性。
public void SetSource(RenderTargetIdentifier source)
{
this.source = source;
}
- 创建一个新的 Shader Graph 着色器。 将其命名为 Tint 并设置节点,如下图所示。 它使用两个属性,纹理和颜色。 将纹理的引用设置为 _MainTex 很重要。 请参阅下面的第二张图片。 这可确保 TintFeature 中的代码找到正确的渲染纹理。
- 将以下代码添加到 Create 方法。 创建 TintFeature 时调用此函数。 它将用于使用从 Tint 着色器创建的新材质初始化 CustomRenderPass 类的新实例。 将材质传递给自定义构造函数。 您还需要使用 ScriptableRendererFeature 的 renderPassEvent 属性设置此功能在渲染管道中的使用位置。
var material = new Material(Shader.Find("Shader Graphs/Tint"));
m_ScriptablePass = new CustomRenderPass(material);
m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
- 现在您已经创建了 CustomRenderPass 的实例,将其添加到渲染队列中。 在 AddRenderPasses 方法中添加下一个代码片段。 然后将源设置为 renderer.cameraColorTarget 并将 pass 加入队列。
m_ScriptablePass.SetSource(renderer.cameraColorTarget);
renderer.EnqueuePass(m_ScriptablePass);
- 在 pass 可以做任何事情之前,它需要获取临时纹理。 将下一个代码片段添加到 OnCameraSetup:
RenderTextureDescriptor cameraTextureDesc = renderingData.cameraData.cameraTargetDescriptor;
cameraTextureDesc.depthBufferBits = 0;
cmd.GetTemporaryRT(tempTexture.id, cameraTextureDesc, FilterMode.Bilinear);
- 既然你有纹理,你需要释放它。 将此代码添加到 OnCameraCleanup:
cmd.ReleaseTemporaryRT(tempTexture.id);
- 现在一切都已初始化,您可以使用材质来执行复制当前渲染纹理的实际工作,以处理结果并将其传递回源。 将此代码添加到 Execute 方法:
CommandBuffer cmd = CommandBufferPool.Get("TintFeature");
Blit(cmd, source, tempTexture.Identifier(), material, 0);
Blit(cmd, tempTexture.Identifier(), source);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
- 要查看实际效果,请选择 Renderer Data 对象并单击 Add Renderer Feature。 TintFeature 将出现在列表中。 确保还将兼容性>中间纹理设置为始终。
- 这是完整的 TintFeature 代码,最终结果显示在以下代码示例中:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class TintFeature : ScriptableRendererFeature
{
class CustomRenderPass : ScriptableRenderPass
{
private Material material;
private RenderTargetIdentifier source;
private RenderTargetHandle tempTexture;
public CustomRenderPass(Material material) : base()
{
this.material = material;
tempTexture.Init("_TempTintTexture");
}
public void SetSource(RenderTargetIdentifier source)
{
this.source = source;
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
RenderTextureDescriptor cameraTextureDesc = renderingData.
cameraData.cameraTargetDescriptor;
cameraTextureDesc.depthBufferBits = 0;
cmd.GetTemporaryRT(tempTexture.id, cameraTextureDesc, FilterMode.Bilinear);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get("TintFeature");
Blit(cmd, source, tempTexture.Identifier(), material, 0);
Blit(cmd, tempTexture.Identifier(), source);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
// 清理在执行此渲染过程期间创建的所有已分配资源。
public override void OnCameraCleanup(CommandBuffer cmd)
{
cmd.ReleaseTemporaryRT(tempTexture.id);
}
}
CustomRenderPass m_ScriptablePass;
/// <inheritdoc/>
public override void Create()
{
var material = new Material(Shader.Find("Shader Graphs/Tint"));
m_ScriptablePass = new CustomRenderPass(material);
// 配置应该注入渲染通道的位置。
m_ScriptablePass.renderPassEvent = RenderPassEvent.
AfterRenderingOpaques;
}
// 在这里,您可以在渲染器中注入一个或多个渲染通道。
// 此方法在设置渲染器时调用一次 percamera。
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
m_ScriptablePass.SetSource(renderer.cameraColorTarget);
render.EnqueuePass(m_ScriptablePass);
}
}
TintFeature 的效果:左侧未处理,右侧着色
分配不同材质和控制在渲染管道中添加事件的更灵活的选项是使用 Settings 类,如以下代码示例所示。 然后使用 settings.material 和 settings。 Create 方法中的 renderEvent。
[System.Serializable]
public class Settings
{
public Material material;
public RenderPassEvent renderEvent = RenderPassEvent.AfterRenderingOpaques;
}
[SerializeField]
private Settings settings = new Settings();
使用 Settings 类在 Inspector 中分配属性
在本视频教程中,我们向您展示了使用渲染器功能的三个实践练习——即如何创建自定义后处理效果、模板效果和被环境遮挡的角色。
您可以找到更多社区驱动的渲染器功能最佳实践示例,包括 Ned Makes Games 关于如何控制自定义渲染器功能的视频教程。