永远相信美好的事情即将发生 😊!

URP 学习笔记(长期更新)

学习 Mavis 28℃ 0评论

参考:

https://zhuanlan.zhihu.com/p/84908168

https://docs.unity3d.com/cn/2021.1/Manual/scriptable-render-pipeline-introduction.html

https://zhuanlan.zhihu.com/p/338786562

Render Pipeline(RP)

将物体显示到屏幕上的一组技术的概述,高度概括一下主要包括三种技术:Culling、Rendering Objects、Post processing

根据每一种技术的不同实现还有更细的划分,例如Rendering Objects可以由不同的方式实现:

  • Multi-pass rendering
    • one pass per object per light
  • Single-pass
    • one pass per object
  • Deferred
    • Render surface properties to a g-buffer, perform screen space lighting

需要好好考虑使用哪些技术来完成渲染:

  1. HDR or LDR ?
  2. MSAA or PPAA ?
  3. PBR or Simple Materials?
  4. Lighting?
  5. Shadows?
  6. ……

four rendering pipeline options

Scriptable Render Pipeline (SRP)

可编程渲染管线的使用层设计

使用:

  1. 直接使用开源内置管线
  2. 在内置管线基础上修改
  3. 直接编写定制化的管线

具体:Render Pipeline在工程中生成特定Assert,该Assert序列化管线中的一些公共设置变量,并在运行时创建实际的渲染上下文,该Assert的设置变量运行中发生变化后,引擎销毁当前上下文并重新创建Render Pipeline。

编写custom SRP的时候,需要权衡各种技术的优劣,选择合适的技术去实现。

SRP是thin API layer,允许使用C#脚本计划和配置渲染命令,Unity将这些命令传递给它的低级图形体系结构,然后它将指令发送给图形API。

SRP->(URP、HDRP、Custom SRP)

基于SRP的渲染管线,都有两个关键定制元素:

  1. Render Pipeline Asset:是Unity项目中存储有关渲染管道的配置数据的资源。
  2. Render Pipeline Instance:继承自RenderPipeline(定义一系列描述 Unity 如何渲染帧的命令和设置),其中的Render()方法是SRP的主要入口。

Custom SRP

项目应该包含:

  1. 一个继承自 RenderPipelineAsset 并重写 CreatePipeline() 方法的脚本,用于定义渲染管线资源。
  2. 一个继承自RenderPipeline并重写Render()方法的脚本,该脚本定义了“渲染管线实例”,用于编写自定义渲染代码。
  3. 一个从 RenderPipelineAsset 脚本创建的渲染管线资源。此资源充当渲染管线实例的工厂类(定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行,适合复杂类的创建)

创建一个基本的Render Pipeline Asset和Render Pipeline Instance:

1、创建一个继承自 RenderPipelineAsset 的C#脚本

using UnityEngine;
using UnityEngine.Rendering;

// CreateAssetMenu 属性让您可以在 Unity Editor 中创建此类的实例。
[CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
public class ExampleRenderPipelineAsset : RenderPipelineAsset
{
    // Unity 在渲染第一帧之前调用此方法。
    // 如果渲染管线资源上的设置改变,Unity 将销毁当前的渲染管线实例,并在渲染下一帧之前再次调用此方法。
    protected override RenderPipeline CreatePipeline() {
        // 实例化此自定义 SRP 用于渲染的渲染管线。
        return new ExampleRenderPipelineInstance();
    }
}

2、创建一个继承自 RenderPipeline C#脚本

using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipelineInstance : RenderPipeline
{
    public ExampleRenderPipelineInstance() {
    }
    // 对于当前正在渲染的每个 CameraType,Unity 每帧调用一次此方法。
    protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
        // 可以在此处编写自定义渲染代码。通过自定义此方法可以自定义 SRP。
    }
}

3、创建一个Render Pipeline Asset脚本的实例

创建一个可配置的Render Pipeline Asset和Render Pipeline Instance:

Render Pipeline Asset存储要用于渲染的渲染管线实例的信息,以及在编辑器中使用的默认材质和Shader。在Render Pipeline Asset的脚本中可以扩展该资源用于存储额外的信息,在项目中可以存在多个不同配置的Render Pipeline Asset,例如,可以使用Render Pipeline Asset来保存每个不同硬件层的配置数据。

1、创建一个继承自 RenderPipelineAsset C#脚本

using UnityEngine;
using UnityEngine.Rendering;

// The CreateAssetMenu attribute lets you create instances of this class in the Unity Editor.
[CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
public class ExampleRenderPipelineAsset : RenderPipelineAsset
{
    // This data can be defined in the Inspector for each Render Pipeline Asset
    public Color exampleColor;
    public string exampleString;

        // Unity calls this method before rendering the first frame.
       // If a setting on the Render Pipeline Asset changes, Unity destroys the current Render Pipeline Instance and calls this method again before rendering the next frame.
    protected override RenderPipeline CreatePipeline() {
        // Instantiate the Render Pipeline that this custom SRP uses for rendering, and pass a reference to this Render Pipeline Asset.
        // The Render Pipeline Instance can then access the configuration data defined above.
        return new ExampleRenderPipelineInstance(this);
    }
}

2、创建一个继承自 RenderPipeline C#脚本

using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipelineInstance : RenderPipeline
{
    // Use this variable to a reference to the Render Pipeline Asset that was passed to the constructor
    private ExampleRenderPipelineAsset renderPipelineAsset;

    // The constructor has an instance of the ExampleRenderPipelineAsset class as its parameter.
    public ExampleRenderPipelineInstance(ExampleRenderPipelineAsset asset) {
        renderPipelineAsset = asset;
    }

    // Unity calls this method once per frame for each CameraType that is currently rendering.
    protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
        // This is an example of using the data from the Render Pipeline Asset.
        Debug.Log(renderPipelineAsset.exampleString);

            // This is where you can write custom rendering code. Customize this method to customize your SRP.
    }
}

3、创建一个Render Pipeline Asset脚本的实例

在SRP中调度和执行渲染命令

通过使用CommandBuffers或对ScriptableRenderContext进行直接API调用,ScriptableRenderContext是一个用作渲染管道中的自定义C#代码与Unity的低级图形代码之间的接口类。

可以使用ScriptableRenderContext建立一个渲染命令列表,然后告诉Unity执行它们,然后Unity的低级图形架构向图形API发送指令。

调度渲染命令:

  1. 使用ScriptableRenderContext.ExecuteCommandBuffer()来将CommandBuffers传递给ScriptableRenderContext
  2. 直接调用API,例如 ScriptableRenderContext.Cull or ScriptableRenderContext.DrawRenderers

CommandBuffer的使用

变量

name 此命令缓冲区的名称。
sizeInBytes 此命令缓冲区的大小,以字节为单位(只读)。

构造函数

CommandBuffer 创建新的空命令缓冲区。

公共函数

BeginSample 添加命令,以开始对配置文件进行采样。
Blit 添加“对渲染纹理执行 blit 操作”命令。
BuildRayTracingAccelerationStructure Adds a command to build the RayTracingAccelerationStructure to be used in a ray tracing dispatch.
Clear 清除缓冲区中的所有命令。
ClearRandomWriteTargets 为 Shader Model 4.5 级别的像素着色器清除随机写入目标。
ClearRenderTarget 添加“清除渲染目标”命令。
ConvertTexture 转换源纹理并将其复制到具有不同格式或尺寸的目标纹理。
CopyCounterValue Adds a command to copy ComputeBuffer or GraphicsBuffer counter value.
CopyTexture 添加用于将纹理复制到其他纹理中的命令。
CreateAsyncGraphicsFence 用于调用 GommandBuffer.CreateGraphicsFence 的快捷方式,将 GraphicsFenceType.AsyncQueueSynchronization 作为第一个参数。
CreateGraphicsFence 创建一个 GraphicsFence,其传递时机是此调用前,GPU 中完成的最后一个 Blit、Clear、Draw、Dispatch 或 Texture Copy 命令之后。
DisableScissorRect 添加用于禁用硬件剪辑矩形的命令。
DisableShaderKeyword 添加用于禁用全局着色器关键字的命令。
DispatchCompute 添加用于执行 ComputeShader 的命令。
DispatchRays Adds a command to execute a RayTracingShader.
DrawMesh 添加“绘制网格”命令。
DrawMeshInstanced Adds a “draw mesh with instancing” command.如果 Material.enableInstancing 为 false,该命令不会立即失败并抛出异常,但如果检测到此类情况,则会记录错误并在每次执行命令时跳过渲染。如果当前平台不支持此 API(即,如果 GPU 实例化不可用),则会抛出 InvalidOperationException。请参阅 SystemInfo.supportsInstancing。
DrawMeshInstancedIndirect 添加“通过间接实例化绘制网格”命令。
DrawMeshInstancedProcedural 添加“通过实例化绘制网格”命令。Draw a mesh using Procedural Instancing. This is similar to Graphics.DrawMeshInstancedIndirect, except that when the instance count is known from script, it can be supplied directly using this method, rather than via a ComputeBuffer. If Material.enableInstancing is false, the command logs an error and skips rendering each time the command is executed; the command does not immediately fail and throw an exception.InvalidOperationException will be thrown if the current platform doesn’t support this API (for example, if GPU instancing is not available). See SystemInfo.supportsInstancing.
DrawOcclusionMesh 向 commandbuffer 添加命令以向当前渲染目标绘制 VR 设备的遮挡网格。
DrawProcedural 添加“绘制程序化几何体”命令。
DrawProceduralIndirect 添加“绘制程序化几何体”命令。
DrawRenderer 添加“绘制渲染器”命令。
EnableScissorRect 添加用于启用硬件剪辑矩形的命令。
EnableShaderKeyword 添加用于启用全局着色器关键字的命令。
EndSample 添加命令,以开始对配置文件进行采样。
GenerateMips 生成渲染纹理的多级渐进纹理级别。
GetTemporaryRT 添加“获取临时渲染纹理”命令。
GetTemporaryRTArray 添加“获取临时渲染纹理阵列”命令。
IncrementUpdateCount 递增纹理的 updateCount 属性。
IssuePluginCustomBlit 向本机代码插件发送用户定义的 blit 事件。
IssuePluginCustomTextureUpdateV2 向本机代码插件发送纹理更新事件。
IssuePluginEvent 向本机代码插件发送用户定义的事件。
IssuePluginEventAndData 向本机代码插件发送具有自定义数据的用户定义的事件。
ReleaseTemporaryRT 添加“释放临时渲染纹理”命令。
RequestAsyncReadback 向命令缓冲区添加异步 GPU 回读请求命令。
RequestAsyncReadbackIntoNativeArray 向命令缓冲区添加异步 GPU 回读请求命令。
RequestAsyncReadbackIntoNativeSlice 向命令缓冲区添加异步 GPU 回读请求命令。
ResolveAntiAliasedSurface 强制解析抗锯齿渲染纹理。
SetComputeBufferCounterValue Adds a command to set the counter value of append/consume buffer.
SetComputeBufferData Adds a command to set the buffer with values from an array.
SetComputeBufferParam 添加用于在 ComputeShader 中设置输入或输出缓冲区参数的命令。
SetComputeConstantBufferParam Adds a command to set a constant buffer on a ComputeShader.
SetComputeFloatParam 添加用于在 ComputeShader 中设置浮点参数的命令。
SetComputeFloatParams 添加用于在 ComputeShader 中设置多个连续浮点参数的命令。
SetComputeIntParam 添加用于在 ComputeShader 中设置整数参数的命令。
SetComputeIntParams 添加用于在 ComputeShader 中设置多个连续整数参数的命令。
SetComputeMatrixArrayParam 添加用于在 ComputeShader 中设置矩阵数组参数的命令。
SetComputeMatrixParam 添加用于在 ComputeShader 中设置矩阵参数的命令。
SetComputeTextureParam 添加用于在 ComputeShader 中设置纹理参数的命令。
SetComputeVectorArrayParam 添加用于在 ComputeShader 中设置向量数组参数的命令。
SetComputeVectorParam 添加用于在 ComputeShader 中设置向量参数的命令。
SetExecutionFlags 设置标志,描述有关如何执行命令缓冲区的意图。
SetGlobalBuffer 添加“设置全局着色器缓冲区属性”命令。
SetGlobalColor 添加“设置全局着色器颜色属性”命令。
SetGlobalConstantBuffer 添加用于绑定全局常量缓冲区的命令。
SetGlobalDepthBias 添加用于设置全局深度偏差的命令。
SetGlobalFloat 添加“设置全局着色器浮点属性”命令。
SetGlobalFloatArray 添加“设置全局着色器浮点数组属性”命令。
SetGlobalInt 为所有着色器设置给定的全局整数属性。
SetGlobalMatrix 添加“设置全局着色器矩阵属性”命令。
SetGlobalMatrixArray 添加“设置全局着色器矩阵数组属性”命令。
SetGlobalTexture 添加“设置全局着色器纹理属性”命令(引用 RenderTexture)。
SetGlobalVector 添加“设置全局着色器向量属性”命令。
SetGlobalVectorArray 添加“设置全局着色器向量数组属性”命令。
SetInstanceMultiplier 添加用于将每个绘制调用的实例数乘以特定乘数的命令。
SetInvertCulling 向缓冲区添加“设置反转剔除”命令。
SetProjectionMatrix 添加用于设置投影矩阵的命令。
SetRandomWriteTarget 为 Shader Model 4.5 级别的像素着色器设置随机写入目标。
SetRayTracingAccelerationStructure 添加命令以设置要与 RayTracingShader 一起使用的 RayTracingAccelerationStructure。
SetRayTracingBufferParam Adds a command to set an input or output buffer parameter on a RayTracingShader.
SetRayTracingConstantBufferParam Adds a command to set a constant buffer on a RayTracingShader.
SetRayTracingFloatParam 添加用于在 RayTracingShader 中设置浮点参数的命令。
SetRayTracingFloatParams 添加用于在 RayTracingShader 中设置多个连续浮点参数的命令。
SetRayTracingIntParam 添加用于在 RayTracingShader 中设置整数参数的命令。
SetRayTracingIntParams 添加用于在 RayTracingShader 中设置多个连续整数参数的命令。
SetRayTracingMatrixArrayParam 添加用于在 RayTracingShader 中设置矩阵数组参数的命令。
SetRayTracingMatrixParam 添加用于在 RayTracingShader 中设置矩阵参数的命令。
SetRayTracingShaderPass Adds a command to select which Shader Pass to use when executing ray/geometry intersection shaders.
SetRayTracingTextureParam 添加用于在 RayTracingShader 中设置纹理参数的命令。
SetRayTracingVectorArrayParam 添加用于在 RayTracingShader 中设置矢量数组参数的命令。
SetRayTracingVectorParam 添加用于在 RayTracingShader 中设置矢量参数的命令。
SetRenderTarget 添加“设置活动的渲染目标”命令。
SetShadowSamplingMode 添加“设置阴影采样模式”命令。
SetSinglePassStereo 添加用于为摄像机设置单通道立体模式的命令。
SetViewMatrix 添加用于设置视图矩阵的命令。
SetViewport 添加用于设置渲染视口的命令。
SetViewProjectionMatrices 添加用于设置视图和投影矩阵的命令。
WaitAllAsyncReadbackRequests 向 CommandBuffer 添加“AsyncGPUReadback.WaitAllRequests”命令。
WaitOnAsyncGraphicsFence 指示 GPU 等待,直至给定的 GraphicsFence 完成传递。

示例,使用命令缓冲区来调度和执行命令以清除当前渲染目标

using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipelineInstance : RenderPipeline
{
        public ExampleRenderPipelineInstance() {
        }

    protected void Render(ScriptableRenderContext context, Camera[] cameras) {
        // 创建并调度命令以清除当前渲染目标
        var cmd = new CommandBuffer();
        cmd.ClearRenderTarget(true, true, Color.black);
        context.ExecuteCommandBuffer(cmd);
        cmd.Release();

         // 指示可编程渲染上下文告诉图形 API 执行调度的命令
        context.Submit();
    }
}

使用ScriptableRenderContext.Submit来执行已经调度好的命令,需要注意无论是使用CommandBuffer来调度命令,还是通过调用API来调度命令,都无关紧要,Unity以相同的方式调度ScriptableRenderContext上的所有渲染命令,并且在调用Submit()之前不会执行任何渲染命令

在渲染管线之外的地方,如果想要CommandBuffers立刻执行,可以调用 Graphics.ExecuteCommandBuffer

Entry points and callbacks

从RenderPipeline继承的类的Render方法是SRP的主要入口点。Unity自动调用此方法。

RenderPipelineManager的用法

静态特性

currentPipeline 返回当前RenderPipeline的实例。

Events

beginCameraRendering 委托,您可以用来在Unity渲染单个Camera之前调用自定义代码。
beginContextRendering 委托,您可以用来在RenderPipeline.Render的开始处调用自定义代码。
beginFrameRendering 委托,您可以用来在RenderPipeline.Render的开始处调用自定义代码。
endCameraRendering 委托,您可以在Unity渲染单个Camera之后用来调用自定义代码。
endContextRendering 委托,您可以用来在RenderPipeline.Render的末尾调用自定义代码。
endFrameRendering 委托,您可以用来在RenderPipeline.Render的末尾调用自定义代码。

SRP Batcher

The SRP Batcher is a rendering loop that speeds up your CPU rendering in Scenes with many Materials that use the same Shader Variant.

使用:

1、必须是URP HDRP CustomSRP才可以使用

2、在URP 中使用:在URP Assert 的Inspector中,在Advanced选项中,开启SRP Batcher即可。(默认启动)

3、在HDRP中使用:默认开启,一般不应该关闭。如果调试需要关闭,在HDRP Assert的Inspector中进入Debug Mode,HDRP Assert会改变其显示的属性,就会出现SRP Batcher属性。也可以在运行时修改SRP Batcher的状态,使用C#脚本,修改下面这个全局变量:

  • GraphicsSettings.useScriptableRenderPipelineBatching = true;

实现原理:

在Unity中,可以在帧期间的任何时间修改任何材质的特性。然而,这也有一些缺点。例如,DrawCall使用新材质时有很多工作要做。因此,场景中的材质越多,设置GPU数据所需的CPU就越多。处理这个问题的传统方法是减少DrawCall的数量以优化CPU渲染成本,因为Unity在发出DrawCall之前必须设置很多东西。而真正的CPU开销来自于这个设置,而不是GPU DrawCall本身。SRP批处理程序通过批处理BindDraw GPU命令序列来减少DrawCalls之间的GPU设置

要获得最大性能,批处理必须尽可能大

可以对同一着色器使用任意多个不同材质,但必须使用尽可能少的着色器变体

在内部渲染循环期间,当Unity检测到新材质时,CPU将收集所有属性并在GPU内存中设置不同的常量缓冲区。GPU缓冲区的数量取决于着色器如何声明其cbuffer。为了加快场景使用大量不同材质但很少使用着色器变体的一般情况,SRP集成了GPU数据持久性等。

SRP批处理程序是一个低级的渲染循环,它使材质数据保持在GPU内存中。如果材料内容没有改变,SRP批处理程序不需要设置缓冲区并将其上传到GPU。相反,SRP批处理程序使用专用代码路径快速更新大型GPU缓冲区中的Unity引擎属性,如下所示:
SRP Batcher rendering workflow

这里,CPU只处理Unity引擎属性,在上图中标记为每个对象大缓冲区。所有材料都有持久的cbuffer位于GPU内存中,可以随时使用。这会加快渲染速度,因为所有材质内容现在都保留在GPU内存中。专用代码为所有对象属性管理一个大型GPU CBUFFER。

兼容性:

  1. 渲染对象必须是网格或者蒙皮网格,不能是粒子
  2. Shader必须与SRP Batcher 兼容,HDRP和URP中的Lit-Unlit Shaders都兼容(除了这些Shaders的粒子版本)
  3. 渲染对象不能使用MaterialPropertyBlocks

      Shader必须与SRP Batcher 兼容:

      1. 必须在一个名为 “UnityPerDraw”的CBUFFER中声明所有内置引擎属性,例如:unity_ObjectToWorld, or unity_SHAr
      2. 必须在一个名为UnityPerMaterial的CBUFFER 中声明所有材质属性
      3. 可以在Shader的inspector中查看和SRP Batcher的兼容性

      使用SRP Batcher的性能分析

      添加一个C#脚本(SRPBatcherProfiler.cs),运行脚本后,F8可以切换显示,F9可以切换SRP Batcher开启状态。

      Frame Debugger中的SRP Batcher data

      1. Window > Analysis > Frame Debugger > Render Camera > Render Opaques, and expand the RenderLoopNewBatcher.Draw list
      2. Click on the SRP Batch

      SRP Batch details显示了使用了多少draw calls,以及为什么特定的draw calls没有与前一个draw calls一起批处理。如果SRP Batch的draw calls数较少,则很可能使用了太多的着色器变体。

      HDRP和URP

      HDRP和URP是unity已经构建了的两个使用SRP框架的可编程渲染管线,都支持Shader Graph 和Post-processing。HDRP 和URP 和bulit-in-RP互不兼容。

      URP

      URP的依赖:

      URP的主要实现:

      从渲染入口开始看实现细节

      一个Rendering Loop过程

              protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
              {
                  BeginFrameRendering(renderContext, cameras);
                  GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);
                  GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher;
                  SetupPerFrameShaderConstants();
      #if ENABLE_VR && ENABLE_XR_MODULE
                  SetupXRStates();
                  if(xrSkipRender)
                      return;
      #endif
                  SortCameras(cameras);
                  for (int i = 0; i < cameras.Length; ++i)
                  {
                      var camera = cameras[i];
                      if (IsGameCamera(camera))
                      {
                          RenderCameraStack(renderContext, camera);
                      }
                      else
                      {
                          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.
                          VFX.VFXManager.PrepareCamera(camera);
      #endif
                          UpdateVolumeFramework(camera, null);
                          RenderSingleCamera(renderContext, camera);
                          EndCameraRendering(renderContext, camera);
                      }
                  }
                  EndFrameRendering(renderContext, cameras);
              }

      整体结构分析设置GraphicSetting参数,设置每帧Shader中的Global 变量,相机排序,相机遍历,每相机渲染。

      每一步具体分析

      BeginFrameRendering(renderContext, cameras);

      可以用来在RenderPipeline.Render的开始处调用自定义代码。

      GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);

      如果是线性空间,则将光强度乘以线性颜色值。如果不是,则使用伽玛颜色值。

      GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher;

      根据UniversalRenderPipelineAsset中是否开启了SRPBatcher来设置是否使用SRP批处理

      SetupPerFrameShaderConstants();

              static void SetupPerFrameShaderConstants()
              {
                  // When glossy reflections are OFF in the shader we set a constant color to use as indirect specular
                  SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe;
                  Color linearGlossyEnvColor = new Color(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity;
                  Color glossyEnvColor = CoreUtils.ConvertLinearToActiveColorSpace(linearGlossyEnvColor);
                  Shader.SetGlobalVector(PerFrameBuffer._GlossyEnvironmentColor, glossyEnvColor);
                  // Used when subtractive mode is selected
                  Shader.SetGlobalVector(PerFrameBuffer._SubtractiveShadowColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.subtractiveShadowColor));
              }

      SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe;

      SphericalHarmonicsL2存储LightProbe数据,RenderSettings.ambientProbe是环境光的2阶球谐函数表达。

      Color linearGlossyEnvColor = new Color(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity;

      ambientSH[0, 0]这将使用给定的RGB颜色通道(0..2)和索引(0..8)访问SH系数。RenderSettings.reflectionIntensity is How much the skybox / custom cubemap reflection affects the Scene.

      Color glossyEnvColor = CoreUtils.ConvertLinearToActiveColorSpace(linearGlossyEnvColor);

      将提供的线性颜色转换为当前使用的颜色空间。

      Shader.SetGlobalVector(PerFrameBuffer._GlossyEnvironmentColor, glossyEnvColor);

      把计算得到的环境光颜色传递到Shader,每帧的shader得到常量_GlossyEnvironmentColor

      Shader.SetGlobalVector(PerFrameBuffer._SubtractiveShadowColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.subtractiveShadowColor));

      把Subtractive模式下的阴影颜色传递到Shader,每帧的shader得到常量_SubtractiveShadowColor

      PerFrameBuffer类中的参数是每帧需要的数据缓存,这些静态数据在创建管线时(或修改管线参数时)被赋值。


      #if ENABLE_VR && ENABLE_XR_MODULE
      SetupXRStates();
      if(xrSkipRender)
      return;
      #endif

      VR XR 相关的,暂时不看
      SortCameras(cameras);

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

      相机按深度排序

      var camera = cameras[i];

      获取遍历的每个相机

      if (IsGameCamera(camera))
      {
      RenderCameraStack(renderContext, camera);
      }

      检查一个相机是不是game camera.如果是则调用RenderCameraStack,此方法为相机栈中的每个有效摄影机调用RenderSingleCamera。那个最后一个摄像头将最终目标解析到屏幕上

      BeginCameraRendering

      可以用来在Unity渲染单个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.
      VFX.VFXManager.PrepareCamera(camera);

      #endif

      VFX.VFXManager.ProcessCamera(camera)函数,它是为了在管线中集成Visual Effect Graph功能使用的
      UpdateVolumeFramework(camera, null);

      对volume系统的支持,核心是调用VolumeManager的Update方法
      RenderSingleCamera(renderContext, camera);

              public static void RenderSingleCamera(ScriptableRenderContext context, Camera camera)
              {
                  UniversalAdditionalCameraData additionalCameraData = null;
                  if (IsGameCamera(camera))
                      camera.gameObject.TryGetComponent(out additionalCameraData);
                  if (additionalCameraData != null && additionalCameraData.renderType != CameraRenderType.Base)
                  {
                      Debug.LogWarning("Only Base cameras can be rendered with standalone RenderSingleCamera. Camera will be skipped.");
                      return;
                  }
                  InitializeCameraData(camera, additionalCameraData, out var cameraData);
                  RenderSingleCamera(context, cameraData, true, cameraData.postProcessEnabled);
              }

      UniversalAdditionalCameraData additionalCameraData = null;
      if (IsGameCamera(camera))
      camera.gameObject.TryGetComponent(out additionalCameraData);
      if (additionalCameraData != null && additionalCameraData.renderType != CameraRenderType.Base)
      {
      Debug.LogWarning(“Only Base cameras can be rendered with standalone RenderSingleCamera. Camera will be skipped.”);
      return;
      }

      获取摄像机的额外数据UniversalAdditionalCameraData,这个组件挂载在Camera上。

      InitializeCameraData(camera, additionalCameraData, out var cameraData);

              static void InitializeCameraData(Camera camera, UniversalAdditionalCameraData additionalCameraData, out CameraData cameraData)
              {
                  cameraData = new CameraData();
                  InitializeStackedCameraData(camera, additionalCameraData, ref cameraData);
                  InitializeAdditionalCameraData(camera, additionalCameraData, ref cameraData);
              }

      cameraData = new CameraData();

      用来保存CameraData
      InitializeStackedCameraData(camera, additionalCameraData, ref cameraData);

      初始化堆栈中所有摄影机的通用摄影机数据设置。覆盖摄影机将从基础摄影机继承设置

              /// <summary>
              /// Initialize camera data settings common for all cameras in the stack. Overlay cameras will inherit
              /// settings from base camera.
              /// </summary>
              /// <param name="baseCamera">Base camera to inherit settings from.</param>
              /// <param name="baseAdditionalCameraData">Component that contains additional base camera data.</param>
              /// <param name="cameraData">Camera data to initialize setttings.</param>
              static void InitializeStackedCameraData(Camera baseCamera, UniversalAdditionalCameraData baseAdditionalCameraData, ref CameraData cameraData)
              {
                  var settings = asset;
                  cameraData.targetTexture = baseCamera.targetTexture;
                  cameraData.isStereoEnabled = IsStereoEnabled(baseCamera);
                  cameraData.isSceneViewCamera = baseCamera.cameraType == CameraType.SceneView;
                  cameraData.numberOfXRPasses = 1;
                  cameraData.isXRMultipass = false;
      #if ENABLE_VR && ENABLE_VR_MODULE
                  if (cameraData.isStereoEnabled && !cameraData.isSceneViewCamera &&
                      !CanXRSDKUseSinglePass(baseCamera) && XR.XRSettings.stereoRenderingMode == XR.XRSettings.StereoRenderingMode.MultiPass)
                  {
                      cameraData.numberOfXRPasses = 2;
                      cameraData.isXRMultipass = true;
                  }
      #endif
                  ///////////////////////////////////////////////////////////////////
                  // Environment and Post-processing settings                       /
                  ///////////////////////////////////////////////////////////////////
                  if (cameraData.isSceneViewCamera)
                  {
                      cameraData.volumeLayerMask = 1; // "Default"
                      cameraData.volumeTrigger = null;
                      cameraData.isStopNaNEnabled = false;
                      cameraData.isDitheringEnabled = false;
                      cameraData.antialiasing = AntialiasingMode.None;
                      cameraData.antialiasingQuality = AntialiasingQuality.High;
                  }
                  else if (baseAdditionalCameraData != null)
                  {
                      cameraData.volumeLayerMask = baseAdditionalCameraData.volumeLayerMask;
                      cameraData.volumeTrigger = baseAdditionalCameraData.volumeTrigger == null ? baseCamera.transform : baseAdditionalCameraData.volumeTrigger;
                      cameraData.isStopNaNEnabled = baseAdditionalCameraData.stopNaN && SystemInfo.graphicsShaderLevel >= 35;
                      cameraData.isDitheringEnabled = baseAdditionalCameraData.dithering;
                      cameraData.antialiasing = baseAdditionalCameraData.antialiasing;
                      cameraData.antialiasingQuality = baseAdditionalCameraData.antialiasingQuality;
                  }
                  else
                  {
                      cameraData.volumeLayerMask = 1; // "Default"
                      cameraData.volumeTrigger = null;
                      cameraData.isStopNaNEnabled = false;
                      cameraData.isDitheringEnabled = false;
                      cameraData.antialiasing = AntialiasingMode.None;
                      cameraData.antialiasingQuality = AntialiasingQuality.High;
                  }
                  ///////////////////////////////////////////////////////////////////
                  // Settings that control output of the camera                     /
                  ///////////////////////////////////////////////////////////////////
                  int msaaSamples = 1;
                  if (baseCamera.allowMSAA && settings.msaaSampleCount > 1)
                      msaaSamples = (baseCamera.targetTexture != null) ? baseCamera.targetTexture.antiAliasing : settings.msaaSampleCount;
                  cameraData.isHdrEnabled = baseCamera.allowHDR && settings.supportsHDR;
                  Rect cameraRect = baseCamera.rect;
                  cameraData.pixelRect = baseCamera.pixelRect;
                  cameraData.pixelWidth = baseCamera.pixelWidth;
                  cameraData.pixelHeight = baseCamera.pixelHeight;
                  cameraData.aspectRatio = (float)cameraData.pixelWidth / (float)cameraData.pixelHeight;
                  cameraData.isDefaultViewport = (!(Math.Abs(cameraRect.x) > 0.0f || Math.Abs(cameraRect.y) > 0.0f ||
                      Math.Abs(cameraRect.width) < 1.0f || Math.Abs(cameraRect.height) < 1.0f));
                  // If XR is enabled, use XR renderScale.
                  // Discard variations lesser than kRenderScaleThreshold.
                  // Scale is only enabled for gameview.
                  const float kRenderScaleThreshold = 0.05f;
                  float usedRenderScale = XRGraphics.enabled ? XRGraphics.eyeTextureResolutionScale : settings.renderScale;
                  cameraData.renderScale = (Mathf.Abs(1.0f - usedRenderScale) < kRenderScaleThreshold) ? 1.0f : usedRenderScale;
                  var commonOpaqueFlags = SortingCriteria.CommonOpaque;
                  var noFrontToBackOpaqueFlags = SortingCriteria.SortingLayer | SortingCriteria.RenderQueue | SortingCriteria.OptimizeStateChanges | SortingCriteria.CanvasOrder;
                  bool hasHSRGPU = SystemInfo.hasHiddenSurfaceRemovalOnGPU;
                  bool canSkipFrontToBackSorting = (baseCamera.opaqueSortMode == OpaqueSortMode.Default && hasHSRGPU) || baseCamera.opaqueSortMode == OpaqueSortMode.NoDistanceSort;
                  cameraData.defaultOpaqueSortFlags = canSkipFrontToBackSorting ? noFrontToBackOpaqueFlags : commonOpaqueFlags;
                  cameraData.captureActions = CameraCaptureBridge.GetCaptureActions(baseCamera);
                  bool needsAlphaChannel = Graphics.preserveFramebufferAlpha;
                  cameraData.cameraTargetDescriptor = CreateRenderTextureDescriptor(baseCamera, cameraData.renderScale,
                  cameraData.isStereoEnabled, cameraData.isHdrEnabled, msaaSamples, needsAlphaChannel);
              }

      InitializeAdditionalCameraData(camera, additionalCameraData, ref cameraData);

      初始化堆栈中每个摄影机可以不同的设置。

              /// <summary>
              /// Initialize settings that can be different for each camera in the stack.
              /// </summary>
              /// <param name="camera">Camera to initialize settings from.</param>
              /// <param name="additionalCameraData">Additional camera data component to initialize settings from.</param>
              /// <param name="cameraData">Settings to be initilized.</param>
              static void InitializeAdditionalCameraData(Camera camera, UniversalAdditionalCameraData additionalCameraData, ref CameraData cameraData)
              {
                  var settings = asset;
                  cameraData.camera = camera;
                  bool anyShadowsEnabled = settings.supportsMainLightShadows || settings.supportsAdditionalLightShadows;
                  cameraData.maxShadowDistance = Mathf.Min(settings.shadowDistance, camera.farClipPlane);
                  cameraData.maxShadowDistance = (anyShadowsEnabled && cameraData.maxShadowDistance >= camera.nearClipPlane) ?
                      cameraData.maxShadowDistance : 0.0f;
                  cameraData.viewMatrix = camera.worldToCameraMatrix;
                  // Overlay cameras inherit viewport from base.
                  // If the viewport is different between them we might need to patch the projection
                  // matrix to prevent squishing when rendering objects in overlay cameras.
                  cameraData.projectionMatrix = (!camera.orthographic && !cameraData.isStereoEnabled && cameraData.pixelRect != camera.pixelRect) ?
                      Matrix4x4.Perspective(camera.fieldOfView, cameraData.aspectRatio, camera.nearClipPlane, camera.farClipPlane) :
                      camera.projectionMatrix;
                  if (cameraData.isSceneViewCamera)
                  {
                      cameraData.renderType = CameraRenderType.Base;
                      cameraData.clearDepth = true;
                      cameraData.postProcessEnabled = CoreUtils.ArePostProcessesEnabled(camera);
                      cameraData.requiresDepthTexture = settings.supportsCameraDepthTexture;
                      cameraData.requiresOpaqueTexture = settings.supportsCameraOpaqueTexture;
                      cameraData.renderer = asset.scriptableRenderer;
                  }
                  else if (additionalCameraData != null)
                  {
                      cameraData.renderType = additionalCameraData.renderType;
                      cameraData.clearDepth = (additionalCameraData.renderType != CameraRenderType.Base) ? additionalCameraData.clearDepth : true;
                      cameraData.postProcessEnabled = additionalCameraData.renderPostProcessing;
                      cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f;
                      cameraData.requiresDepthTexture = additionalCameraData.requiresDepthTexture;
                      cameraData.requiresOpaqueTexture = additionalCameraData.requiresColorTexture;
                      cameraData.renderer = additionalCameraData.scriptableRenderer;
                  }
                  else
                  {
                      cameraData.renderType = CameraRenderType.Base;
                      cameraData.clearDepth = true;
                      cameraData.postProcessEnabled = false;
                      cameraData.requiresDepthTexture = settings.supportsCameraDepthTexture;
                      cameraData.requiresOpaqueTexture = settings.supportsCameraOpaqueTexture;
                      cameraData.renderer = asset.scriptableRenderer;
                  }
                  // Disable depth and color copy. We should add it in the renderer instead to avoid performance pitfalls
                  // of camera stacking breaking render pass execution implicitly.
                  if (cameraData.renderType == CameraRenderType.Overlay)
                  {
                      cameraData.requiresDepthTexture = false;
                      cameraData.requiresOpaqueTexture = false;
                  }
                  // Disables post if GLes2
                  cameraData.postProcessEnabled &= SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
      #if POST_PROCESSING_STACK_2_0_0_OR_NEWER
      #pragma warning disable 0618 // Obsolete
      			if (settings.postProcessingFeatureSet == PostProcessingFeatureSet.PostProcessingV2)
                  {
                      camera.TryGetComponent(out cameraData.postProcessLayer);
                      cameraData.postProcessEnabled &= cameraData.postProcessLayer != null && cameraData.postProcessLayer.isActiveAndEnabled;
                  }
                  bool depthRequiredForPostFX = settings.postProcessingFeatureSet == PostProcessingFeatureSet.PostProcessingV2
                      ? cameraData.postProcessEnabled
                      : CheckPostProcessForDepth(cameraData);
      #pragma warning restore 0618
      #else
                  bool depthRequiredForPostFX = CheckPostProcessForDepth(cameraData);
      #endif
                  cameraData.requiresDepthTexture |= cameraData.isSceneViewCamera || depthRequiredForPostFX;
              }

      RenderSingleCamera(context, cameraData, true, cameraData.postProcessEnabled);

      static void RenderSingleCamera(ScriptableRenderContext context, CameraData cameraData, bool requiresBlitToBackbuffer, bool anyPostProcessingEnabled)
              {
                  Camera camera = cameraData.camera;
                  var renderer = cameraData.renderer;
                  if (renderer == null)
                  {
                      Debug.LogWarning(string.Format("Trying to render {0} with an invalid renderer. Camera rendering will be skipped.", camera.name));
                      return;
                  }
                  if (!camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters))
                      return;
                  SetupPerCameraShaderConstants(cameraData);
                  ProfilingSampler sampler = (asset.debugLevel >= PipelineDebugLevel.Profiling) ? new ProfilingSampler(camera.name): _CameraProfilingSampler;
                  CommandBuffer cmd = CommandBufferPool.Get(sampler.name);
                  using (new ProfilingScope(cmd, sampler))
                  {
                      renderer.Clear(cameraData.renderType);
                      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(asset, ref cameraData, ref cullResults, requiresBlitToBackbuffer, anyPostProcessingEnabled, out var renderingData);
                      renderer.Setup(context, ref renderingData);
                      renderer.Execute(context, ref renderingData);
                  }
                  context.ExecuteCommandBuffer(cmd);
                  CommandBufferPool.Release(cmd);
                  context.Submit();
              }

      Camera camera = cameraData.camera;

      得到当前Camera
      var renderer = cameraData.renderer;

      得到当前相机使用的Renderer==camera. ScriptableRenderer

      ScriptableRenderer实现渲染策略。它描述了剔除和照明的工作方式以及所支持的效果。

      渲染器可以用于所有摄像机,也可以逐个摄像机覆盖。它将实现光剔除和设置,并描述ScriptableRenderPass要在框架中执行的列表。可以扩展渲染器以支持更多附加效果 ScriptableRendererFeature。渲染器的资源在中序列化ScriptableRendererData

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

      验证Renderer是否为空
      if (!camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters))
      return;

      用于获取剔除结果,IsStereoEnabled是判断是否是立体相机(VR/AR)。
      SetupPerCameraShaderConstants(cameraData);

      设置PerCameraBuffer(每相机使用的Shader Global变量PerCameraBuffer)

              static void SetupPerCameraShaderConstants(in CameraData cameraData)
              {
                  Camera camera = cameraData.camera;
                  Rect pixelRect = cameraData.pixelRect;
                  float scaledCameraWidth = (float)pixelRect.width * cameraData.renderScale;
                  float scaledCameraHeight = (float)pixelRect.height * cameraData.renderScale;
                  Shader.SetGlobalVector(PerCameraBuffer._ScaledScreenParams, new Vector4(scaledCameraWidth, scaledCameraHeight, 1.0f + 1.0f / scaledCameraWidth, 1.0f + 1.0f / scaledCameraHeight));
                  Shader.SetGlobalVector(PerCameraBuffer._WorldSpaceCameraPos, camera.transform.position);
                  float cameraWidth = (float)pixelRect.width;
                  float cameraHeight = (float)pixelRect.height;
                  Shader.SetGlobalVector(PerCameraBuffer._ScreenParams, new Vector4(cameraWidth, cameraHeight, 1.0f + 1.0f / cameraWidth, 1.0f + 1.0f / cameraHeight));
                  Matrix4x4 projMatrix = GL.GetGPUProjectionMatrix(camera.projectionMatrix, false);
                  Matrix4x4 viewMatrix = camera.worldToCameraMatrix;
                  Matrix4x4 viewProjMatrix = projMatrix * viewMatrix;
                  Matrix4x4 invViewProjMatrix = Matrix4x4.Inverse(viewProjMatrix);
                  Shader.SetGlobalMatrix(PerCameraBuffer._InvCameraViewProj, invViewProjMatrix);
              }/* Your code... */

      ProfilingSampler sampler = (asset.debugLevel >= PipelineDebugLevel.Profiling) ? new ProfilingSampler(camera.name): _CameraProfilingSampler;

      ProfilingSample是封装的性能采样器

      debuglevel:Player debug message level. 

      PipelineDebugLevel.Profiling是个枚举值,为1

      new ProfilingSampler(camera.name):创建一个名camera.name的分析采样器
      CommandBuffer cmd = CommandBufferPool.Get(sampler.name);

      CommandBufferPool是CommandBuffer对象池,获取一个新的Command Buffer 并且命名为sample.name.
      using (new ProfilingScope(cmd, sampler))

      cmd:用于添加标记和计算执行计时的命令缓冲区

      sampler:用于此范围的分析采样器

      renderer.Clear(cameraData.renderType);

      Clear方法相当于ScriptableRenderer的每相机初始化
      renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);

      使用ScriptableRenderer继续填充剔除参数和CameraDataSetupCullingParameters是虚方法,在ForwardRenderer和Renderer2D中有不同实现
      context.ExecuteCommandBuffer(cmd);

      调度自定义图形命令缓冲区的执行
      cmd.Clear();

      清除cmd
      #if UNITY_EDITOR
      // Emit scene view UI
      if (cameraData.isSceneViewCamera)
      {
      ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);

      编辑器模式下Scene相机额外显示UI,将UI几何图形放到“场景”视图中进行渲染
      }
      #endif

      var cullResults = context.Cull(ref cullingParameters);

      剔除是Unity封装好的一个方法,只能通过参数设置进行控制
      InitializeRenderingData(asset, ref cameraData, ref cullResults, requiresBlitToBackbuffer, anyPostProcessingEnabled, out var renderingData);

      根据CameraData、剔除结果,requiresBlitToBackbuffer, anyPostProcessingEnabled 初始化渲染数据RenderingData

      RenderingData是用于存储每相机渲染所需的所有数据的结构体,这个方法的目的就是完全填充渲染数据,最后交给ScriptableRenderer使用。
      renderer.Setup(context, ref renderingData);
      renderer.Execute(context, ref renderingData);

      使用ScriptableRenderer根据RenderingData,Setup并Excute渲染上下文
      }
      context.ExecuteCommandBuffer(cmd);
      CommandBufferPool.Release(cmd);
      context.Submit();

      提交渲染上下文
      EndCameraRendering

      可以在Unity渲染单个Camera之后用来调用自定义代码。
      EndFrameRendering

      可以用来在RenderPipeline.Render的末尾调用自定义代码。


      Mavis , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
      转载请注明原文链接:URP 学习笔记(长期更新)
      喜欢 (1)
      发表我的评论
      取消评论

      表情

      Hi,您需要填写昵称和邮箱!

      • 昵称 (必填)
      • 邮箱 (必填)
      • 网址