Universal Render Pipeline

Table of Contents

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

1 URP的常见问题回答

  1. URP和HDRP(High Definition Render Pipeline)不能混用。
  2. 可以从内建render pipeline转到URP。可以用升级器(upgrade)升级内建shader到URP shader。如果自定义的shader的话,需要手动去upgrade。
  3. 不能在运行期换pipeline asset。
  4. URP和HDRP之间不能换用。
  5. 通过Package Manager获取URP的package
  6. 在URP Asset的【Player Setting】界面上可以找到Dynamic Batching的checkbox
  7. 选中材质球时,在Inspector面板中,找到Render Face选项,选择Both选项,即可开启Double Sided Global Illumination
  8. URP适用于各种平台,包括PC和mobile
  9. Built-in RP和URP功能对照表中,标识为Not Supported的功能,表示没有且不会在后续版本中实现。
  10. 如果在build URP工程时很慢,检查以下shader stripping是否设置对,要strip掉可以strip掉的shader。
  11. 缺省地,URP是在linear space中工作,可以在Player Setting中将其设置为在gamma space中工作。
  12. 通过创建ScriptableRendererFeature脚本,使用scriptable render pass可以扩展URP的功能。

2 URP Asset

URP版本:8.2.0 源网页

要使用URP asset,需要创建一个URP asset,并在【Graphic settings】界面将其赋值使用。URP asset控制图形渲染的功能,设置图形渲染的质量等级。URP asset本身是一个scriptable object。它继承自RenderPipelineAsset类。在一个工程中可以使用多个URP asset,并且可以在它们之间切换使用,比如有个URP asset是启用了阴影,另一个URP asset关闭了阴影。

URP有以下几大类的控制选项,如下:

  • General
  • Quality
  • Lighting
  • Shadows
  • Post-processing
  • Advanced

下面的图显示了这几个控制选项的UI:

2.1 General

这是一些用于控制管道渲染框架的核心部分常规设置

属性名 描述 对应的编辑器变量名称
Depth Texture 当在一个摄像机对象的inspector界面上启用此项时,URP将会在shader中创建一个深度纹理_CameraDepthTexture。缺省地URP将把这个深度纹理交给你场景中的所有的摄像机去使用之。 m_RequireDepthTextureProp
Opaque Texture 当在一个摄像机对象的inspector界面上启用此项时,URP将会在shader中创建一个不透明的深度纹理_CameraOpaqueTexture。缺省地,URP将这个深度纹理交给你场景中的所有的摄像机使用。这个深度纹理起的作用,就很类似于built-in render pipeline中的GrabPass。在URP渲染任意的透明状网格(transparent mesh)时,该纹理将会提供一个当前场景的一个快照(snapshot) m_RequireOpaqueTextureProp
Opaque Downsampling 设置Opaque Texture属性的降采样方式,有None(不采样)2倍线性过滤(2x Bilinear),、4倍线性过滤(4x Bilinear),、4倍盒状过滤(4x Bilinear),其中4倍盒状过滤产生一种轻柔羽化的效果 m_OpaqueDownsamplingProp
Terrain Holes 禁用此项的话,在build包的时候,URP将会移出所有的Terrain hole shader变体,将会减少build包的时间

2.2 Quality

Qulity项相关管的属性控制URP的渲染质量级别。通过控制这些属性,可以在低端硬件上提高性能,在高端硬件上提高图形质量。如果要为不同的硬件设置不同的设置,则可以在多个Universal Render Pipeline资源中配置这些设置,然后根据需要在这些设置中切换。

属性名 描述 对应的编辑器变量名称
HDR 启用此选项后,默认情况下,场景中的每个摄像机都可以在HDR中进行渲染。如果定位的是低端硬件,则可以禁用此选项以跳过HDR计算并获得更好的性能。当在一个摄像机对象的inspector界面上,为这个摄像机对象关闭使用HDR。 m_HDR
MSAA 当开启此项,在渲染时,默认情况下对场景中的每个摄像机都使用多重采样反走样(Multi Sample Anti-aliasing)。这样可以使几何图形的边缘变柔和,因此不会出现锯齿或闪烁。在下拉菜单中,选择每个像素要使用多少个样本:2x,4x或8x。选择的样本越多,对象边缘越平滑。如果您想跳过MSAA计算,或者在2D游戏中不需要它们,请选择Disable。当在一个摄像机对象的inspector界面上,为这个摄像机对象关闭使用MSAA。 m_MSAA
Render Scale 此滑块缩放渲染目标分辨率(render target resolution)(而不是当前设备的分辨率)。如果出于性能原因要以较小的分辨率进行渲染,或者要放大渲染以提高质量,请使用此选项。这只会缩放游戏渲染。UI呈现保留为设备的原始分辨率。 m_RenderScale

2.3 Lighting

Lighting相关的属性这些设置会影响场景中的灯光。如果禁用其中一些设置,将会从Shader代码中删除相关的keyword。如果您确定某些keyword肯定不会在程序中使用上,则可以将其禁用以提高性能并减少构建时间

属性名 描述 对应的编辑器变量名称
Main Light 这些设置会影响场景中的directional light。 您可以通过在Lighting的Inspector面板中将其分配为Sun Source来选择它。 如果您不指定太阳源,则URP会将场景中最亮的directional light光视为主光。您可以在Pixel LightingNone这两个选项中进行选择。 如果您选择None,则即使您设置了光源,URP也不会用主光源参与渲染计算 m_MainLightRenderingModeProp
Cast Shadows 当勾选此项时,Main Light将会在场景产生影子 m_MainLightShadowsSupportedProp
Shadow Resolution 这可以控制主光源的阴影贴图纹理的大小。高分辨率可提供更清晰,更详细的阴影。 如果内存或渲染时间有问题,请尝试使用较低的分辨率。 m_MainLightShadowmapResolutionProp
Additional Lights 在这里,您可以选择使用其他光源来补充您的main light。在Per VertexPer PixelDisabled之间进行选择 m_AdditionalLightsRenderingModeProp
Per Object Limit 每一个game object中,可以被多少个additional light所照亮。此滑块用来设置这个个数 m_AdditionalLightsPerObjectLimitProp
Cast Shadows 勾选此选项,可以控制additional lights是否能在场景中投下阴影。 m_AdditionalLightShadowsSupportedProp
Shadow Resolution 附加光投射有向阴影的所使用的阴影贴图纹理的大小。这是一个纹理集,最多可容纳16个阴影贴图。高分辨率可提供更清晰,更详细的阴影。 如果内存或渲染时间有问题,请尝试使用较低的分辨率。 m_AdditionalLightShadowmapResolutionProp

2.4 Shadows

这些设置会影响阴影的外观和行为。它们还会影响性能,因此您可以在此处进行调整,以在视觉质量和阴影渲染速度之间取得最佳平衡。

属性名 描述 对应的编辑器变量名称
Distance 这个属性将指定一个值,当某个game object离摄像机的距离大于此值时,URP将不会渲染这个object的阴影 m_ShadowDistanceProp
Cascades 选择阴影的 层叠(cascade) 数。大量的层叠为您提供了更接近相机的阴影。选项包括:NoneTwoFour如果您遇到性能问题,请尝试减少级联数量。还可以在设置下方的部分中配置阴影的距离。距离相机越远,阴影变得越不细腻。 m_ShadowCascadesProp m_ShadowCascade2SplitProp m_ShadowCascade4SplitProp
Soft Shadows 如果为main lightadditional light启用了阴影,则可以启用这个Soft Shadows选项,以在阴影贴图上添加更平滑的过滤效果。这样可以使阴影上的边缘平滑。如果禁用此功能,则渲染速度会更快,但阴影边缘会更锐利(可能会像素化)。 m_SoftShadowsSupportedProp

2.5 Post-processing

本部分允许您 微调(fine-tune) 全局后处理设置。

属性名 描述 对应的编辑器变量名称
Grading Mode 选择用于项目的颜色分级(color grading,通常是指对最终的画面进行颜色和亮度的改变或矫正)的模式。分为两种:•高动态范围(HDR):类似于电影制作工作流程,此模式最适用于高精度分级。 Unity在色调映射(tone mapping)之前应用颜色分级。•低动态范围:此模式遵循更经典的工作流程。 在色调映射之后,Unity会应用有限范围的颜色分级。 m_ColorGradingMode
LUT Size URP会使用 查找纹理(look-up texture,LUT) 来进行color grading。查找纹理可分为 内部(internal)外部(external) 本属性项就是用来指定LUT的的大小。 较大的大小值可提供更高的精度,但可能会降低性能。增加内存的使用。在开始color grading之前就需要确定好尺寸。本选项的默认值为32,这是一个能在速度和质量之间的取得良好平衡的值。 m_ColorGradingLutSize

2.6 Advanced

本部分允许您微调较少更改的设置,这些设置会影响更深的渲染功能和Shader组合。

属性名 描述 对应的编辑器变量名称
SRP Batcher 选中此框以启用SRP Batcher。如果您有许多使用不同的材质球,但是它们使用了相同的着色器,这将很有用。SRP Batcher是一个内部循环(inner loop),可在不影响GPU性能的情况下加快CPU渲染速度。使用SRP Batcher时,它将在内部循环中替换SRP的渲染相关的代码。 m_SRPBatcher
Dynamic Batching 启用动态批处理,以使render pipeline自动批处理共享同一材质的小型动态对象。 这对于不支持GPU instancing的平台和图形API很有用。如果目标硬件确实支持GPU instancing,请禁用动态批处理。您可以在运行时更改此设置。 m_SupportsDynamicBatching
Mixed Lighting 启用混合灯光(Mixed Lighting),启用本选项时,会告诉pipeline在创建游戏时,包含上mixed lighting相关的shader variants。 m_MixedLightingSupportedProp
Debug Level 设置render pipeline生成的调试信息的等级,分为以下几种: Disabled: 不启用 Profiling: 让pipeline提供详细的调试信息,在FrameDebugger中可以看到。 m_DebugLevelProp
Shader Variant Log Level 当Unity在build完工程的时候,可以显示shader stripping和shader variants的相关信息。本属性就是控制这些信息的显示。属性值有三种:Disabled: Unity不显示任何的信息 Only Universal: 只显示和URP shader相关的信息 All: Unity显示所有的shader的信息 m_ShaderVariantLogLevel

3 Rendering in the Universal Render Pipeline

首先看一下在URP renderpipe中,如何渲染一帧的代码:

/* 
    UniversalRenderPipeline类的成员函数Render

    UniversalRenderPipeline类继承自UnityEngine.Rendering.RenderPipeline类,
    且UniversalRenderPipeline类是一个sealed类,所以它不能也不意图被继承派生
    
    UniversalRenderPipeline.Render函数重载了基类的同名函数

    UniversalRenderPipeline类有一部分定义在UniversalRenderPipeline.cs文件中,
    这个文件(7.1.1版本),通过Package Manager下载之后,其目录在:
    Library\PackageCache\com.unity.render-pipelines.universal@7.1.1\Runtime
*/
protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
{
    // BeginFrameRendering是基类RenderPipeline的静态成员函数
    // 在开始使用camera进行渲染时,执行相关的回调
    BeginFrameRendering(renderContext, cameras);

    // 判断当前活跃的颜色空间,是否是线性颜色空间,如果是的话,光源也要使用线性亮度
    GraphicsSettings.lightsUseLinearIntensity = 
        (QualitySettings.activeColorSpace == ColorSpace.Linear);

    // 是否使用SRP Batcher进行合批处理
    GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher;
    
    // 此方法是UniversalRenderPipeline类的提供的静态成员函数
    // 顾名思义,就是用来设置每一帧用到的shader constants,此函数也在
    // UniversalRenderPipeline.cs文件中定义
    SetupPerFrameShaderConstants();

    // SortCameras也是UniversalRenderPipeline类的成员方法,定义在
    // UniversalRenderPipelineCore.cs文件中。这个方法是把传递进来的camera,
    // 按其深度值进行排序,其深度值越大,离摄像机就越远,那就应该越早渲染,
    // 所以要把它排在摄像机数组的前面。
    SortCameras(cameras);
    
    // 针对每一个camera,执行该camera应该要执行的rendering操作
    foreach (Camera camera in cameras)
    {
        // BeginCameraRendering是基类RenderPipeline的静态成员函数
        // 在开始使用某一个camera进行渲染时,执行相关的回调
        BeginCameraRendering(renderContext, camera);

#if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER
        // It should be called before culling to prepare material.
        // When there isn't any VisualEffect component, this method has no effect.
        // visual effect graph相关的,网址如下:
        // https://docs.unity3d.com/Packages/com.unity.visualeffectgraph@7.1/manual/index.html
        VFX.VFXManager.PrepareCamera(camera);
#endif
        // 此方法是UniversalRenderPipeline类的提供的静态成员函数
        // 使用某一个camera进行渲染
        RenderSingleCamera(renderContext, camera);

        // EndCameraRendering是基类RenderPipeline的静态成员函数
        // 在开始使用某一个camera进行渲染时,执行相关的回调
        EndCameraRendering(renderContext, camera);
    }

    // EndFrameRendering是基类RenderPipeline的静态成员函数
    // 在开始使用camera进行渲染时,执行相关的回调
    EndFrameRendering(renderContext, cameras);
}

void SortCameras(Camera[] cameras)
{
    Array.Sort(cameras, (lhs, rhs) => (int)(lhs.depth - rhs.depth));
}

public static void RenderSingleCamera(ScriptableRenderContext context, Camera camera)
{
    // 获取到和本摄像机有关的拣选相关的参数
    if (!camera.TryGetCullingParameters(IsStereoEnabled(camera),
                                 out var cullingParameters))
        return;

    var settings = asset;
    UniversalAdditionalCameraData additionalCameraData = null;

    if (camera.cameraType == CameraType.Game || 
                     camera.cameraType == CameraType.VR)
        camera.gameObject.TryGetComponent(out additionalCameraData);

    // InitializeCameraData函数是UniversalRenderPipeline类的静态成员函数。
    // 根据传递进来的各种参数最后返回一个CameraData类型变量,这个就是当前
    // 渲染使用的摄像机的各种数据
    InitializeCameraData(settings, camera, additionalCameraData, out var cameraData);

    // SetupPerCameraShaderConstants函数是UniversalRenderPipeline类的静态成员函数。
    // 根据当前的camera的数据属性,设置逐帧的着色器常量
    SetupPerCameraShaderConstants(cameraData);

    ScriptableRenderer renderer = (additionalCameraData != null) ? 
            additionalCameraData.scriptableRenderer : 
            settings.scriptableRenderer;

    if (renderer == null)
    {
        Debug.LogWarning(string.Format(
        "Trying to render {0} with an invalid renderer. Camera rendering will be skipped.",
        camera.name));
        return;
    }

    string tag = (asset.debugLevel >= PipelineDebugLevel.Profiling)
                            ? camera.name: k_RenderCameraTag;

    // 从command buffer 池中根据tag取得CommandBuffer对象
    CommandBuffer cmd = CommandBufferPool.Get(tag);
    
    using (new ProfilingSample(cmd, tag))
    {
        renderer.Clear();
        renderer.SetupCullingParameters(
                    ref cullingParameters, ref cameraData);

        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();

#if UNITY_EDITOR

        // Emit scene view UI
        if (cameraData.isSceneViewCamera)
            ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
#endif

        var cullResults = context.Cull(ref cullingParameters);
        InitializeRenderingData(settings, ref cameraData,
                        ref cullResults, out var renderingData);

        renderer.Setup(context, ref renderingData);
        renderer.Execute(context, ref renderingData);
    }

    context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
    context.Submit();
}

从上面的代码中可以看出:在一个 前向渲染器(forward renderer) 中。每渲染一帧的做法流程如下:

当在【Graphics Settings】面板中指定选中了URP render pipeline的时候,Unity将会使用URP去渲染所有的摄像机所观察的东西,包括game窗口,scene窗口,反射探针,inspector面板上的预览窗口。

3.1 Camera loop

从上面的代码中可以看出,在一个 摄像机循环(camera loop) 内,执行以下三步的操作:

  1. 拣选出你场景中需要渲染的objects。
  2. 创建渲染器所需的数据。
  3. 执行渲染操作,向framebuffer渲染出一个image。

再进一步细化上面的三步为:

  1. 配置用于确定拣选系统如何拣选灯光和阴影的参数。您可以使用自定义渲染器覆盖渲染管道的这一部分。
  2. 使用上一步中的拣选选参数,计算可见的渲染器, 阴影投射器(shadow caster) 和灯光的列表。 拣选参数和相机的 层距离(layer distances) 会影响拣选和渲染性能。
  3. 根据拣选操作的输出(culling output),URP asset,摄像机的质量设置(quality settings)。以及当前运行的平台来捕获信息,以构建渲染数据(RenderingData类对象),然后那渲染数据通知到渲染器
  4. 生成渲染通路列表(list of render pass),并根据渲染数据将它们排队以执行。 您可以使用自定义渲染器,覆盖掉URP提供的render pipe中的这一部分
  5. 执行队列中的每个渲染通路。渲染器将Camera拍摄到的图像,输出到帧缓冲区。

参考链接

kumakoko avatar
kumakoko
pure coder
comments powered by Disqus