了解Unity中的URP Renderer Features:Object Renderer Features

4 阅读14分钟

了解Unity中的URP Renderer Features:Object Renderer Features

骚话王又来分享知识了!今天咱们聊聊Unity URP中的Renderer Features,这可是渲染管线中的"瑞士军刀",能让你的游戏画面从平平无奇变成炫酷炸裂。

什么是Renderer Features

在Unity的Universal Render Pipeline(URP)中,Renderer Features就像是一个个独立的渲染模块,你可以把它们想象成乐高积木,每个积木都有自己独特的功能。当你把这些积木组合在一起时,就能构建出复杂的渲染效果。

Renderer Features允许你在渲染管线的特定阶段插入自定义的渲染逻辑,比如后处理效果、自定义阴影、屏幕空间反射等等。这就像是给Unity的渲染引擎装上了各种"插件",让原本标准化的渲染流程变得灵活多变。

为什么需要Renderer Features

如果你的游戏需要实现以下效果:

  • 角色身上的描边效果
  • 屏幕空间的环境反射
  • 自定义的深度预通道
  • 特殊的后处理滤镜

如果没有Renderer Features,你可能需要修改整个渲染管线,这就像是要重新设计一辆汽车的发动机。但有了Renderer Features,你只需要在合适的位置插入相应的模块,就像给汽车加装一个导航仪或者音响系统一样简单。

Renderer Features的核心在于它的执行时机。每个Feature都会在渲染管线的特定阶段被调用,比如:

  • 在渲染不透明物体之前
  • 在渲染透明物体之后
  • 在最终的后处理之前

这种精确的时机控制让你能够实现各种复杂的渲染效果,而不会影响其他渲染步骤。

使用Renderer Features的最大优势在于它的模块化设计。每个Feature都是独立的,你可以根据需要启用或禁用特定的效果,而不会影响其他功能。这种设计让代码更容易维护,也让团队协作变得更加高效。

另外,Renderer Features的性能开销是可控的。你可以精确地控制每个效果的执行时机和条件,避免不必要的计算,这对于移动平台尤其重要。

接下来的内容中,我们将深入探讨Renderer Features的具体实现方式、常用技巧以及性能优化策略。如果你觉得有用就收藏点赞,咱们一起把Unity渲染技术玩出花来!

Renderer Features资源的添加方式

首先在urp管线中,我们知道有urp asset和urp data两种资源, urp asset也就是设置管线的时候的基础资源,没有它管线将无法正常工作。

URP Asset和URP Data的关系就像是"配置表"和"执行器"的关系。URP Asset存储了渲染管线的各种设置参数,比如阴影质量、后处理效果、光照设置等,它定义了"要做什么"。而URP Data(通常指Universal Renderer Data)则负责"怎么做",它包含了具体的渲染器配置,其中就包括Renderer Features的列表。

这种分离设计的好处是,你可以在同一个项目中创建多个不同的URP Data资源,每个都可以有不同的Renderer Features组合,然后根据不同的场景或平台选择使用不同的渲染器配置。

URP Data作为渲染器数据资源,它内部维护着一个Renderer Features的列表,这个列表决定了在渲染过程中会执行哪些自定义的渲染步骤。

具体来说:

  • URP Data:作为Renderer Features的容器,管理着所有启用的Features
  • Renderer Features:作为具体的渲染工具,每个Feature负责实现特定的渲染效果

当你需要在URP Data中添加Renderer Features时,实际上就是在向这个容器中添加新的渲染工具。每个Feature都会在渲染管线的特定阶段被调用,按照它们在列表中的顺序依次执行。

这种设计让渲染流程变得非常灵活,你可以根据需要组合不同的Features,比如在一个场景中同时使用描边效果、屏幕空间反射和自定义后处理,而在另一个场景中只使用其中的一部分效果。

所以编辑urp data,在这里就可以添加Renderer Features了。

Render Objects Renderer Feature的核心作用

URP在DrawOpaqueObjects和DrawTransparentObjects这两个渲染通道中绘制物体。但有时候,你可能需要在帧渲染的不同时间点绘制物体,或者以不同的方式解释和写入渲染数据(比如深度和模板缓冲)。这就是Render Objects Renderer Feature发挥作用的地方。

为什么需要Render Objects Renderer Feature

想象一下这样的场景:

  • 你需要在所有不透明物体渲染完成之后,再渲染一些特殊的物体
  • 你想要为特定图层的物体使用不同的材质或着色器
  • 你需要自定义深度缓冲的写入方式
  • 你想要在透明物体渲染之前插入一些特殊的渲染步骤

这些需求都无法通过标准的URP渲染流程来实现,而Render Objects Renderer Feature就是解决这些问题的"万能钥匙"。

Render Objects Renderer Feature允许你:

  • 在特定的渲染时机绘制物体
  • 只渲染指定图层的物体
  • 使用特定的材质覆盖
  • 自定义深度和模板缓冲的处理方式
  • 控制渲染顺序和时机

这种灵活性让你能够实现各种复杂的渲染效果,比如描边、深度预通道、自定义阴影等。接下来我们将通过一个具体的例子来展示如何创建自定义的渲染效果。

Render Objects Renderer Feature 配置参数

Render Objects Renderer Feature提供了丰富的参数配置,让你能够精确控制渲染行为。这些参数就像是调节器,让你能够精确地控制"什么时候渲染"、"渲染什么"以及"怎么渲染"。

基础配置参数

Name(名称) 这是Feature的标识符,就像给工具贴标签一样。一个好的名称能让团队其他成员一眼就明白这个Feature的作用,比如"角色描边"、"深度预通道"等。

Event(事件) 这个参数决定了Feature在URP渲染队列中的执行时机。就像是选择在流水线的哪个环节插入你的工序。常见的选项包括:

  • BeforeRenderingOpaques:在不透明物体渲染之前
  • AfterRenderingOpaques:在不透明物体渲染之后
  • BeforeRenderingTransparents:在透明物体渲染之前
  • AfterRenderingTransparents:在透明物体渲染之后

选择合适的时机对于实现正确的渲染效果至关重要。

过滤设置(Filters)

Queue(队列) 选择渲染不透明物体还是透明物体。这就像是选择在哪个车间工作,不同的车间有不同的工作流程。

Layer Mask(图层遮罩) 指定要渲染的图层,就像是用筛子筛选物体。只有被选中的图层中的物体才会被这个Feature处理。这个功能让你能够精确控制哪些物体需要特殊处理。

Pass Names(Pass名称) 如果着色器中的Pass有LightMode标签,这个Feature只会处理那些LightMode标签值与Pass Names中指定的值匹配的着色器。这就像是给着色器设置"通行证",只有持有正确通行证的着色器才能通过。

覆盖设置(Overrides)

Override Mode(覆盖模式) 指定材质覆盖的模式,有两种选择:

  • Material:用指定的材质完全替换物体的原始材质,所有材质属性都会被覆盖
  • Shader:只替换着色器,保持原始材质的所有属性,让覆盖的着色器能够访问这些属性

Material(材质) 当Override Mode设置为Material时,Unity会用这里指定的材质替换物体的原始材质。这就像是给物体"换衣服",完全改变它的外观。

Shader(着色器) 当Override Mode设置为Shader时,Unity会用这里指定的着色器替换物体的原始着色器,但保持材质的所有属性。这就像是给物体"换大脑",但保持它的"性格"。

深度设置(Depth)

Write Depth(写入深度) 定义这个Feature在渲染物体时是否更新深度缓冲。就像是决定是否在画布上留下痕迹。

Depth Test(深度测试) 指定深度测试的条件,决定什么时候渲染物体的像素。这就像是设置"谁在前面谁在后面"的规则。

模板设置(Stencil)

启用后,Feature会处理模板缓冲的值。模板缓冲就像是给像素设置"身份证",可以用来实现各种复杂的渲染效果,比如描边、遮罩等。

相机设置(Camera)

Field of View(视野) 渲染物体时使用的视野角度,可以覆盖相机的原始设置。这就像是调整"镜头"的焦距。

Position Offset(位置偏移) 渲染物体时应用的偏移量,可以移动物体的位置。这就像是给物体"搬家"。

Restore(恢复) 启用后,Feature在执行完渲染通道后会恢复原始的相机矩阵。这就像是"用完就还",确保不影响后续的渲染步骤。

这些参数的组合使用让你能够实现各种复杂的渲染效果。比如,你可以创建一个Feature,在特定时机渲染特定图层的物体,使用自定义的材质,并应用特殊的深度和模板处理。

脚本创建Renderer Feature

除了使用Unity编辑器中的Render Objects Renderer Feature,你还可以通过脚本来创建自定义的Renderer Feature。这种方式提供了更大的灵活性,让你能够实现更复杂的渲染逻辑。

基本结构

一个自定义的Renderer Feature脚本包含两个主要部分:

  • ScriptableRendererFeature:Feature的主要类,负责管理整个渲染过程
  • ScriptableRenderPass:具体的渲染通道,执行实际的渲染操作

ScriptableRendererFeature类

这个类继承自ScriptableRendererFeature,是整个Feature的入口点。它负责:

  • 创建和管理RenderPass
  • 将RenderPass添加到渲染队列中
  • 处理Feature的生命周期
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomRendererFeature : ScriptableRendererFeature
{
    private CustomRenderPass customPass;

    public override void Create()
    {
        // 创建RenderPass实例
        customPass = new CustomRenderPass();
        
        // 设置渲染时机
        customPass.renderPassEvent = RenderPassEvent.BeforeRenderingOpaques;
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        // 将RenderPass添加到渲染队列
        renderer.EnqueuePass(customPass);
    }
}

ScriptableRenderPass类

这个类继承自ScriptableRenderPass,是实际执行渲染操作的地方。它包含三个核心方法:

Configure方法 在渲染开始前调用,用于设置渲染目标和初始状态:

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
    // 创建临时渲染纹理
    int tempRT = Shader.PropertyToID("_TempRT");
    cmd.GetTemporaryRT(tempRT, cameraTextureDescriptor);
    
    // 设置渲染目标
    ConfigureTarget(tempRT);
    
    // 清除渲染目标
    ConfigureClear(ClearFlag.Color, Color.black);
}

Execute方法 执行实际的渲染操作:

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
    // 创建绘制设置
    ShaderTagId shaderTagId = new ShaderTagId("CustomPass");
    DrawingSettings drawingSettings = CreateDrawingSettings(shaderTagId, ref renderingData, SortingCriteria.CommonOpaque);
    
    // 创建过滤设置
    FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.opaque);
    
    // 执行渲染
    context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
}

FrameCleanup方法 在渲染结束后调用,用于清理资源:

public override void FrameCleanup(CommandBuffer cmd)
{
    // 释放临时渲染纹理
    cmd.ReleaseTemporaryRT(tempRT);
    
    // 禁用着色器关键字
    cmd.DisableShaderKeyword("_CUSTOM_PASS");
}

RenderPassEvent 定义了RenderPass的执行时机,常见的选项包括:

  • BeforeRenderingOpaques:不透明物体渲染前
  • AfterRenderingOpaques:不透明物体渲染后
  • BeforeRenderingTransparents:透明物体渲染前
  • AfterRenderingTransparents:透明物体渲染后

ShaderTagId 用于标识特定的着色器Pass,只有带有对应标签的着色器才会被这个RenderPass处理。

DrawingSettings和FilteringSettings

  • DrawingSettings:定义如何绘制物体(使用哪个着色器、排序方式等)
  • FilteringSettings:定义哪些物体被绘制(渲染队列范围、图层等)

使用方式

创建脚本后,你需要:

  1. 将脚本保存为.cs文件
  2. 在URP Data中添加这个Feature
  3. 确保有对应的着色器Pass来处理渲染

重要说明:脚本创建的Renderer Feature必须要在URP Data中添加才能生效。这是因为URP的渲染流程是由URP Data控制的,它维护着一个Renderer Features的列表,只有被添加到这个列表中的Feature才会在渲染过程中被执行。

如果你只是创建了脚本但没有添加到URP Data中,那么这个Feature就不会被调用,也就不会产生任何渲染效果。这就像是创建了一个工具但没有把它放到工具箱里一样,工具存在但不会被使用。

关于Feature的创建方式:不是所有的Feature都需要创建脚本。Unity URP提供了两种添加Renderer Feature的方式:

  1. 内置的Render Objects Renderer Feature:这是Unity提供的现成Feature,你可以直接在URP Data中添加,然后通过参数配置来实现各种效果,比如材质覆盖、图层过滤、深度处理等。这种方式不需要编写任何代码。

  2. 自定义脚本Feature:当你需要实现更复杂的渲染逻辑,或者内置Feature无法满足需求时,才需要创建自定义脚本。这种方式提供了更大的灵活性,但需要编写代码。

所以,如果你的需求比较简单,比如只是想要在特定时机渲染特定图层的物体,或者应用材质覆盖,那么直接使用内置的Render Objects Renderer Feature就足够了,不需要创建脚本。

这种脚本化的方式让你能够实现各种复杂的渲染效果,比如自定义的后处理、特殊的阴影处理、描边效果等。通过合理的参数配置和渲染时机选择,你可以创造出独特的视觉效果。

Render Objects Renderer Feature对应的官方源码

一般在urp包的Runtime\RendererFeatures目录下,因版本而异

现在让我们深入分析Render Objects Renderer Feature的源码实现,看看Unity是如何设计这个强大的渲染工具的。

Render Objects Renderer Feature采用了经典的配置-执行分离设计模式。整个系统分为三个主要部分:

  1. RenderObjects类:继承自ScriptableRendererFeature,是整个Feature的入口点
  2. RenderObjectsSettings类:配置数据的容器,存储所有可配置的参数
  3. RenderObjectsPass类:实际的渲染执行器(在另一个文件中实现)

这种设计让配置和逻辑分离,使得Feature既易于使用又高度可配置。

RenderObjects主类分析

[ExcludeFromPreset]
[Tooltip("Render Objects simplifies the injection of additional render passes by exposing a selection of commonly used settings.")]
[URPHelpURL("renderer-features/renderer-feature-render-objects")]
public class RenderObjects : ScriptableRendererFeature

特性注解说明

  • [ExcludeFromPreset]:防止这个Feature被包含在预设中,确保每个项目都能独立配置
  • [Tooltip]:在Unity编辑器中显示提示信息
  • [URPHelpURL]:提供官方文档链接,方便开发者查阅

RenderObjectsSettings配置类

这个类是整个Feature的配置中心,包含了所有可配置的参数:

[System.Serializable]
public class RenderObjectsSettings
{
    public string passTag = "RenderObjectsFeature";
    public RenderPassEvent Event = RenderPassEvent.AfterRenderingOpaques;
    public FilterSettings filterSettings = new FilterSettings();
    public Material overrideMaterial = null;
    public int overrideMaterialPassIndex = 0;
    public Shader overrideShader = null;
    public int overrideShaderPassIndex = 0;
    public OverrideMaterialMode overrideMode = OverrideMaterialMode.Material;
    public bool overrideDepthState = false;
    public CompareFunction depthCompareFunction = CompareFunction.LessEqual;
    public bool enableWrite = true;
    public StencilStateData stencilSettings = new StencilStateData();
    public CustomCameraSettings cameraSettings = new CustomCameraSettings();
}

passTag:用于性能分析器的标识符,帮助开发者识别这个Feature在性能分析中的表现。

Event:控制渲染时机,默认值是AfterRenderingOpaques,这是一个安全的选择,确保相机已经正确设置。

overrideMode枚举

public enum OverrideMaterialMode
{
    None,      // 不进行任何覆盖
    Material,  // 使用材质覆盖
    Shader     // 使用着色器覆盖
}

这个枚举提供了三种覆盖模式,让开发者能够灵活选择覆盖策略。

FilterSettings过滤设置

[System.Serializable]
public class FilterSettings
{
    public RenderQueueType RenderQueueType;
    public LayerMask LayerMask;
    public string[] PassNames;
    
    public FilterSettings()
    {
        RenderQueueType = RenderQueueType.Opaque;
        LayerMask = 0;
    }
}

RenderQueueType枚举

public enum RenderQueueType
{
    Opaque,      // 不透明物体
    Transparent  // 透明物体
}

这个设计让开发者能够精确控制要渲染的物体类型,避免不必要的渲染开销。

CustomCameraSettings相机设置

[System.Serializable]
public class CustomCameraSettings
{
    public bool overrideCamera = false;
    public bool restoreCamera = true;
    public Vector4 offset;
    public float cameraFieldOfView = 60.0f;
}

这个类允许开发者临时修改相机的参数,比如视野角度和位置偏移,实现特殊的渲染效果。

Create方法的核心逻辑

public override void Create()
{
    FilterSettings filter = settings.filterSettings;

    // 安全检查:确保渲染时机不会过早
    if (settings.Event < RenderPassEvent.BeforeRenderingPrePasses)
        settings.Event = RenderPassEvent.BeforeRenderingPrePasses;

    // 创建RenderPass实例
    renderObjectsPass = new RenderObjectsPass(settings.passTag, settings.Event, filter.PassNames,
        filter.RenderQueueType, filter.LayerMask, settings.cameraSettings);

    // 配置材质/着色器覆盖
    switch (settings.overrideMode)
    {
        case RenderObjectsSettings.OverrideMaterialMode.None:
            renderObjectsPass.overrideMaterial = null;
            renderObjectsPass.overrideShader = null;
            break;
        case RenderObjectsSettings.OverrideMaterialMode.Material:
            renderObjectsPass.overrideMaterial = settings.overrideMaterial;
            renderObjectsPass.overrideMaterialPassIndex = settings.overrideMaterialPassIndex;
            renderObjectsPass.overrideShader = null;
            break;
        case RenderObjectsSettings.OverrideMaterialMode.Shader:
            renderObjectsPass.overrideMaterial = null;
            renderObjectsPass.overrideShader = settings.overrideShader;
            renderObjectsPass.overrideShaderPassIndex = settings.overrideShaderPassIndex;
            break;
    }

    // 配置深度状态
    if (settings.overrideDepthState)
        renderObjectsPass.SetDetphState(settings.enableWrite, settings.depthCompareFunction);

    // 配置模板状态
    if (settings.stencilSettings.overrideStencilState)
        renderObjectsPass.SetStencilState(settings.stencilSettings.stencilReference,
            settings.stencilSettings.stencilCompareFunction, settings.stencilSettings.passOperation,
            settings.stencilSettings.failOperation, settings.stencilSettings.zFailOperation);
}

AddRenderPasses方法

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
    renderer.EnqueuePass(renderObjectsPass);
}

这个方法非常简单,就是将创建的RenderPass添加到渲染队列中。让Feature能够无缝集成到URP的渲染流程中。