[toc]

# URP 的渲染流程小析

  • renderingData 设置
  • Unity Cull
  • Enqueue RenderFeature 的 Pass
  • UniversalRenderer 的 Setup
    • 灯光 PreSetup
    • 设置 RenderingData
    • 分配需要的贴图等数据并执行 (ExecuteCommandeBuffer)
    • 将主要的 Pass 推入 RenderQueue (但不执行)
  • Execute
    • SetupRenderfeature 的 pass
    • 执行每个 Pass 的 OnCameraSetup (同时 ExecuteCommandBuffer)
    • 设置 Shader 的 Time 变量
    • pass 排序
    • 调用 Pass 的 Configure (同时 ExecuteCommandBuffer)
    • SetupNativeRenderPassFrameData
    • SetupLights
  • Execute 实际渲染阶段
    • 先执行被标记为 BeforeRendering 的 Pass
    • 再次设置 TimeValue
    • 执行 MainRenderingOpaque 的 Pass
    • 执行 MainRendringTransparent 的 Pass
    • 绘制被标记为 PreImageEffects 的 Gizmos
    • 绘制 AfterRendering 的 Pass
    • 绘制 WireOverlay
    • 绘制被标记为 PostImageEffects 的 Gizmos
    • 调用一次 FinishRendering
  • scriptableRenderContext.Submit()

# UniversalRenderer 的 Setup 阶段

如果只需要渲染深度图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    bool isOffscreenDepthTexture = cameraData.targetTexture != null && cameraData.targetTexture.format == RenderTextureFormat.Depth;
if (isOffscreenDepthTexture)
{
ConfigureCameraTarget(k_CameraTarget, k_CameraTarget);
SetupRenderPasses(in renderingData);
EnqueuePass(m_RenderOpaqueForwardPass);

// TODO: Do we need to inject transparents and skybox when rendering depth only camera? They don't write to depth.
EnqueuePass(m_DrawSkyboxPass);
#if ADAPTIVE_PERFORMANCE_2_1_0_OR_NEWER
if (!needTransparencyPass)
return;
#endif
EnqueuePass(m_RenderTransparentForwardPass);
return;
}

先设置 RenderingLayer ,比如如果是 OpenGL 的设备,则禁止获取 RenderingLayer 然后根据渲染模式是否是延迟渲染模式以及渲染事件 (?) 是否是 Opaque 之类的 如果支持主光源阴影,则加入 m_MainLightShadowCasterPass pass
如果支持 Addtional 光源阴影,则加入 m_AdditionalLightsShadowCasterPass 如果需要分配深度图,则直接分配深度图,这点实际上是通过 cmd.SetGlobalTexture 来进行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var depthDescriptor = cameraTargetDescriptor;
if (requiresDepthPrepass && this.renderingModeActual != RenderingMode.Deferred)
{
depthDescriptor.graphicsFormat = GraphicsFormat.None;
depthDescriptor.depthStencilFormat = k_DepthStencilFormat;
depthDescriptor.depthBufferBits = k_DepthBufferBits;
}
else
{
depthDescriptor.graphicsFormat = GraphicsFormat.R32_SFloat;
depthDescriptor.depthStencilFormat = GraphicsFormat.None;
depthDescriptor.depthBufferBits = 0;
}

depthDescriptor.msaaSamples = 1;// Depth-Only pass don't use MSAA
RenderingUtils.ReAllocateIfNeeded(ref m_DepthTexture, depthDescriptor, FilterMode.Point, wrapMode: TextureWrapMode.Clamp, name: "_CameraDepthTexture");

cmd.SetGlobalTexture(m_DepthTexture.name, m_DepthTexture.nameID);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();

分配法线贴图,这里聊一下 Unity 的贴图重新分配 ReAllocateIfNeedReAllocateGBufferIfNeed

ReallocateGBufferIfNeed,首先会先对比 gbuffer 存的贴图 instanceId 和 RTHandles 里的 instanceId 是否一致,不一致则不进行分配。然后它的下一级函数也会调用 ReAllocateIfNeed ,这个函数里会进行两个事情

  1. 检查新 RT 和旧 RT 设置是否相同,如果相同没必要重新分配
  2. 不相同则重新分配

其中检查两个 RT 是否相同的方法是对比 RTDescriptor 中的每一个参数 分配贴图时会先 Release 掉旧的贴图,然后 Alloc 一个新的贴图

1
2
handle?.Release();
handle = RTHandles.Alloc(descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);

在法线贴图分配的最后,会设置一下全局贴图,而且如果是延迟管线的话,会同时将 handle 设置到 _CameraNormalsTexture

1
2
3
4
5
cmd.SetGlobalTexture(normalsTexture.name, normalsTexture.nameID);
if (this.renderingModeActual == RenderingMode.Deferred) // As this is requested by render pass we still want to set it
cmd.SetGlobalTexture("_CameraNormalsTexture", normalsTexture.nameID);
context.ExecuteCommandBuffer(cmd);
cmd.Clear();

# DepthNormalPrepass

在这个阶段进行深度与法线贴图的渲染,其中 DepthNormalPrepass 可以设置三个 handler,即 depth,normal,decal

1
2
3
4
5
6
public void Setup(RTHandle depthHandle, RTHandle normalHandle, RTHandle decalLayerHandle)
{
Setup(depthHandle, normalHandle);
this.renderingLayersHandle = decalLayerHandle;
this.enableRenderingLayers = true;
}

对于延迟渲染而言,在这个阶段和其他管线是有差别的,它在这一阶段并没有将深度和法线渲染进 m_DepthTexturem_NormalsTexture 中,而是在后面的延迟渲染 Pass 才将其加入进去

# 级联阴影 Pass

通过主要贡献光源的 Light 的 shadowMap 配置和级联数量来计算 CascadeShadow 的贴图大小

1
int shadowResolution = ShadowUtils.GetMaxTileResolutionInAtlas(renderingData.shadowData.mainLightShadowmapWidth,renderingData.shadowData.mainLightShadowmapHeight, m_ShadowCasterCascadesCount);