URP主要源码解析
大体结构
RenderPipelineAsset > RenderPipelines > Renderer > RenderPass
另外,RenderFeature通过配置RenderPassEvent,与持有RenderPass的实例,并将其注入到Renderer中对应的时机进行执行。
具体分析
UniversalRenderPipelineAsset类
作用:渲染管线的配置文件,可以利用它创建出多个RenderPipelines
要使用URP,需要先创建一个URP Asset,并且将它赋给Graphics Settings。URP Asset便是 UniversalRenderPipelineAsset
这个类的实例,其保存了URP的一些设置。
其中有一个十分重要的方法:CreatePipeline()
,它继承于父类 RenderPipelineAsset
,在方法中URP Asset创建了管线对象 UniversalRenderPipeline
。
// 该方法可以理解为URP管线的总入口函数
protected override RenderPipeline CreatePipeline()
{
...
return new UniversalRenderPipeline(this);
}
UniversalRenderPipeline类
作用:具体的渲染管线
管线类 UniversalRenderPipeline
继承自 RenderPipeline
,其核心方法为 Render(ScriptableRenderContext context, Camera[] cameras)
。
// 定义了该渲染管线的自定义渲染行为
// context: 可编程渲染的上下文
// cameras: 本帧所有需要渲染的相机
protected abstract void Render(ScriptableRenderContext context, Camera[] cameras);
Render()
该方法每帧都会被自动调用,在方法中,会处理本帧需要执行的所有渲染命令,来绘制本帧图像。
protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
{
// 1. BeginFrameRendering:表示该帧即将开始渲染。
BeginFrameRendering(renderContext, cameras);
...
// 2. SortCameras:根据所有要渲染的相机的depth值进行排序,depth越小越先渲染。
SortCameras(cameras);
// 3. 遍历每一个相机。
for (int i = 0; i < cameras.Length; ++i)
{
var camera = cameras[i];
if (IsGameCamera(camera))
{
// 4. 如果当前相机是主相机(也就是cameraType == CameraType.Game且renderType != CameraRenderType.Overlay)
// 则调用RenderCameraStack,这一步在接下来会详细描述。
RenderCameraStack(renderContext, camera);
}
else
{
// 5.如果当前相机不是游戏相机,比如SceneView相机、预览相机等,则调用相机渲染的通常步骤
// BeginCameraRendering → UpdateVolumeFramework → InitializeCameraData → RenderSingleCamera → EndCameraRendering
// 这些步骤暂且称作“相机渲染常规步骤”,后续再详细描述
BeginCameraRendering(renderContext, camera);
UpdateVolumeFramework(camera, null);
RenderSingleCamera(renderContext, camera);
EndCameraRendering(renderContext, camera);
}
}
// 6. 遍历相机完成。
// 7. EndFrameRendering:表示该帧渲染结束,提交后备缓冲区。
EndFrameRendering(renderContext, cameras);
}
RenderCameraStack()
RenderCameraStack()
方法主要是去遍历主相机的CameraStack里的每一个Overlay相机,并且把主相机和所有生效的Overlay相机全部渲染出来。
static void RenderCameraStack(ScriptableRenderContext context, Camera baseCamera)
{
// 1. 初始化anyPostProcessingEnabled和lastActiveOverlayCameraIndex,用于当作参数传入接下来所有生效相机的“相机渲染常规步骤”里面。
baseCamera.TryGetComponent<UniversalAdditionalCameraData>(out var baseCameraAdditionalData);
List<Camera> cameraStack = baseCameraAdditionalData.cameraStack;
bool anyPostProcessingEnabled = renderer.supportedRenderingFeatures.cameraStacking;
int lastActiveOverlayCameraIndex = -1;
// 2. 遍历主相机的CameraStack的每一个相机
// 如果其中任何一个生效的相机需要渲染后期效果(Post-Processing),则anyPostProcessingEnabled为true,
// 将最后一个相机的遍历序号i记录在lastActiveOverlayCameraIndex变量里。
// 遍历每一个相机时候都会去检查它的“生效”性,如果不是“生效”的相机就会提示警告。
// “生效”的检查范围包括:是否Active,是否Enabled,是否是Overlay相机,是否scriptableRenderer类型和主相机保持一致。
if (cameraStack != null && cameraStack.Count > 0)
{
for (int i = 0; i < cameraStack.Count; ++i)
{
Camera currCamera = cameraStack[i];
//<更新设置anyPostProcessingEnabled和lastActiveOverlayCameraIndex>...</>//
}
}
// 3. 如果没有遍历访问到任何Overlay相机,则lastActiveOverlayCameraIndex依然为-1,反之则大于-1。
// 根据lastActiveOverlayCameraIndex的值就可以判断有没有后续生效相机需要渲染,需不需要继续遍历CameraStack的所有相机,
// 将这个判断得出的bool值保存到变量isStackedRendering里。
bool isStackedRendering = lastActiveOverlayCameraIndex != -1;
// 4. 对主相机做“相机渲染常规步骤”,传入参数为“是否是最后一个相机”和“是否需要处理后期效果”。
// 如果isStackedRendering == false则说明主相机已经是最后一个相机了,后面没有可以渲染的相机了;
// 如果anyPostProcessingEnabled == true说明需要处理后期效果。
BeginCameraRendering(context, baseCamera);
UpdateVolumeFramework(baseCamera, baseCameraAdditionalData);
InitializeCameraData(baseCamera, baseCameraAdditionalData, out var baseCameraData);
RenderSingleCamera(context, baseCameraData, !isStackedRendering, anyPostProcessingEnabled);
EndCameraRendering(context, baseCamera);
// 5. 如果CameraStack里没有相机了,就返回吧。
if (!isStackedRendering)
return;
// 6. 开始遍历CameraStack里每一个相机。
for (int i = 0; i < cameraStack.Count; ++i)
{
var currCamera = cameraStack[i];
if (!currCamera.isActiveAndEnabled)
continue;
currCamera.TryGetComponent<UniversalAdditionalCameraData>(out var currCameraData);
if (currCameraData != null)
{
// 7. 对CameraStack里每一个相机做“相机渲染常规步骤”,传入参数也是“是否是最后一个相机”和“是否需要处理后期效果”。
// 如果当前相机的遍历序号i等于lastActiveOverlayCameraIndex,那就说明这个相机已经是最后一个相机了,后面没有可以渲染的相机了;
// 如果anyPostProcessingEnabled == true说明需要处理后期效果。
bool lastCamera = i == lastActiveOverlayCameraIndex;
BeginCameraRendering(context, currCamera);
UpdateVolumeFramework(currCamera, currCameraData);
InitializeAdditionalCameraData(currCamera, currCameraData, ref overlayCameraData);
RenderSingleCamera(context, overlayCameraData, lastCamera, anyPostProcessingEnabled);
EndCameraRendering(context, currCamera);
}
}
// 8. 结束遍历CameraStack里每一个相机。
}
“相机渲染常规步骤”
BeginCameraRendering()
:RenderPipeline的protected
方法,表示某一个相机即将开始渲染,渲染相机的固定调用。UpdateVolumeFramework()
:更新当前相机是否在某一个后期效果的Volume内,如果在Volume内则触发对应的后期效果。InitializeCameraData()
:首先根据官方文档,在URP里相机上会绑一个叫做UniversalAdditionalCameraData
的脚本。该脚本包含的变量,描述了该相机是否有某些渲染特性,比如是否需要渲染DepthTexture,是否需要渲染OpaqueTexture等。InitializeCameraData()
就是将这些变量值从当前相机的UniversalAdditionalCameraData
脚本里提取出来,供相机内的渲染使用。RenderSingleCamera()
:该方法用于渲染一个相机,其过程主要包括剪裁、设置渲染器、执行渲染器三步。后续会详细描述。EndCameraRendering()
:RenderPipeline的protected方法,表示某一个相机已经结束渲染,渲染相机的固定调用。
RenderSingleCamera()
/// <summary>
/// 渲染一个相机,其过程主要包括剪裁、设置渲染器、执行渲染器三步。
/// </summary>
/// <param name="context">渲染上下文用于记录执行过程中的命令。</param>
/// <param name="cameraData">相机渲染数据,里面可能包含了继承自相机的一些参数</param>
/// <param name="requiresBlitToBackbuffer">如果这是相机渲染栈里的最后一个相机则为true,否则为false</param>
/// <param name="anyPostProcessingEnabled">如果相机需要做后期效果处理则为true,否则为false</param>
static void RenderSingleCamera(ScriptableRenderContext context, CameraData cameraData, bool requiresBlitToBackbuffer, bool anyPostProcessingEnabled)
{
// 1. 获取当前相机的渲染器renderer,其基类类型是ScriptableRenderer,可以继承以作扩展。
var renderer = cameraData.renderer;
// 2. 获得当前相机的剪裁参数,保存在变量cullingParameters里。
if (!camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters))
return;
// 3. 申请一个CommandBuffer来执行渲染命令。
CommandBuffer cmd = CommandBufferPool.Get(sampler.name);
// 4. 清空渲染器,也就是重置里面的一些数据。
renderer.Clear(cameraData.renderType);
// 5. 根据相机再去修改一下变量cullingParameters里的信息。
renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);
// 6. 执行当前的渲染命令。
context.ExecuteCommandBuffer(cmd);
// 7. 清空CommandBuffer,以供接下来渲染使用。
cmd.Clear();
// 8. 根据剪裁参数cullingParameters执行相机剪裁,并将剪裁结果储存在cullResults里面。
var cullResults = context.Cull(ref cullingParameters);
// 9. 根据当前帧的剪裁结果、灯光状态等每帧可能会改变的数据,来初始化本帧渲染需要用到的渲染数据renderingData。
InitializeRenderingData(asset, ref cameraData, ref cullResults, requiresBlitToBackbuffer, anyPostProcessingEnabled, out var renderingData);
// 10. 调用渲染器的Setup()
// 主要是根据当前渲染数据,去设置本帧渲染需要用到的渲染过程到队列中
// 这些渲染过程在这里被命名为Pass,其基类类型为ScriptableRenderPass,可以继承扩展。后面会针对前向渲染器(ForwardRenderer)作详细描述。
renderer.Setup(context, ref renderingData);
// 11. 调用渲染器的Execute(),执行已经在队列中的渲染过程。后面会针对前向渲染器(ForwardRenderer)作详细描述。
renderer.Execute(context, ref renderingData);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
context.Submit();
}
ForwardRenderer类
作用:维护了一个
ScriptableRenderPass
的列表,每一帧都会利用SetUp()
往列表里加入Pass,帧中执行Pass得到每一个过程的渲染结果,帧末清空列表,等待下一帧的填充。
ForwardRenderer
继承于 ScriptableRenderer
,这个渲染器被所有支持URP的平台所支持。它渲染的资源被序列化成 ScriptableRendererData
。
ScriptableRenderer
里面最核心的两个方法是 Setup()
和 Execute()
,这两个方法在每一帧里都会被执行。
-
Setup()
:根据渲染数据,将本帧要执行的Pass加入到ScriptableRenderPass
的列表中 -
Execute()
:从ScriptableRenderPass
的列表中将Pass按照渲染时序分类(即RenderPassEvent
)取出来,并执行这个过程
Setup()
该方法在 ScriptableRenderer
里面是一个虚方法,任何继承于 ScriptableRenderer
的子渲染器都需要去实现它。
实现它的过程也就是将Pass加入队列的过程,由于队列是FIFO的,所以这个入队的过程也就是本帧内渲染的过程。
public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
{
// 1. 如果当前相机是主相机
// 判断是否需要渲染到DepthTexture, 如果需要就设置当前深度缓冲为m_CameraDepthAttachment,否则就渲染到相机默认渲染目标;
// 判断是否需要渲染到ColorTexture,如果需要就设置当前颜色缓冲为m_CameraColorAttachment,否则就渲染到相机默认渲染目标。
// 需要渲染到ColorTexture的条件包括:打开MSAA、打开RenderScale、打开HDR、打开Post-Processing、打开渲染到OpaqueTexture、添加了自定义ScriptableRendererFeature等
// 需要渲染到DepthTexture的条件主要是打开渲染到DepthTexture。
if (cameraData.renderType == CameraRenderType.Base)
{
m_ActiveCameraColorAttachment = (createColorTexture) ? m_CameraColorAttachment : RenderTargetHandle.CameraTarget;
m_ActiveCameraDepthAttachment = (createDepthTexture) ? m_CameraDepthAttachment : RenderTargetHandle.CameraTarget;
...
}
else
{
m_ActiveCameraColorAttachment = m_CameraColorAttachment;
m_ActiveCameraDepthAttachment = m_CameraDepthAttachment;
}
ConfigureCameraTarget(m_ActiveCameraColorAttachment.Identifier(), m_ActiveCameraDepthAttachment.Identifier());
// 2. 将所有自定义的ScriptableRendererFeature加入到ScriptableRenderPass的队列中。
for (int i = 0; i < rendererFeatures.Count; ++i)
{
if(rendererFeatures[i].isActive)
rendererFeatures[i].AddRenderPasses(this, ref renderingData);
}
// 3. 将各种通用Pass根据各自条件加入到ScriptableRenderPass的队列中。
if (mainLightShadows)
EnqueuePass(m_MainLightShadowCasterPass);
if (additionalLightShadows)
EnqueuePass(m_AdditionalLightsShadowCasterPass);
...
EnqueuePass(m_RenderOpaqueForwardPass);
...
// 如果创建了DepthTexture,我们需要复制它,否则我们可以将它渲染到renderbuffer。
if (!requiresDepthPrepass && renderingData.cameraData.requiresDepthTexture && createDepthTexture)
{
m_CopyDepthPass.Setup(m_ActiveCameraDepthAttachment, m_DepthTexture);
EnqueuePass(m_CopyDepthPass);
}
if (renderingData.cameraData.requiresOpaqueTexture)
{
Downsampling downsamplingMethod = UniversalRenderPipeline.asset.opaqueDownsampling;
m_CopyColorPass.Setup(m_ActiveCameraColorAttachment.Identifier(), m_OpaqueColor, downsamplingMethod);
EnqueuePass(m_CopyColorPass);
}
...
EnqueuePass(m_RenderTransparentForwardPass);
...
// 4. 如果当前相机是本帧最后一个渲染的相机,则将一些需要最后Blit的Pass加入到ScriptableRenderPass的队列中。
if (lastCameraInTheStack)
{
// Post-processing将得到最终的渲染目标,不需要final blit pass。
if (applyPostProcessing)
{
m_PostProcessPass.Setup(...);
EnqueuePass(m_PostProcessPass);
}
...
// 执行FXAA或任何其他可能需要在AA之后运行的Post-Processing效果。
if (applyFinalPostProcessing)
{
m_FinalPostProcessPass.SetupFinalPass(sourceForFinalPass);
EnqueuePass(m_FinalPostProcessPass);
}
...
// 我们需要FinalBlitPass来得到最终的屏幕。
if (!cameraTargetResolved)
{
m_FinalBlitPass.Setup(cameraTargetDescriptor, sourceForFinalPass);
EnqueuePass(m_FinalBlitPass);
}
}
else if (applyPostProcessing)
{
m_PostProcessPass.Setup(...);
EnqueuePass(m_PostProcessPass);
}
}
Execute()
该方法在 ScriptableRenderer
里面是一个不用重写的公共方法,由于各个Pass的执行顺序在 Setup()
里已经确定,所以该方法已经没有重写的必要了。不过还是可以看一下 Execute()
里面发生了什么。以下为该方法的主要调用。
public void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
...
// 1. 每一个ScriptableRenderPass里都有一个RenderPassEvent字段,
// FillBlockRanges()根据这个字段,将ScriptableRenderPass分配到不同的Block里。
// 也就是给Pass根据渲染阶段进行了一下分类,这样开发者可以比较直观地在某一个阶段插入渲染过程。
FillBlockRanges(blockEventLimits, blockRanges);
...
// 2. 根据不同的渲染阶段,取出这个阶段所有Pass依次执行其中的渲染过程。
ExecuteBlock(RenderPassBlock.BeforeRendering, blockRanges, context, ref renderingData);
...
// Opaque blocks...
ExecuteBlock(RenderPassBlock.MainRenderingOpaque, blockRanges, context, ref renderingData, eyeIndex);
// Transparent blocks...
ExecuteBlock(RenderPassBlock.MainRenderingTransparent, blockRanges, context, ref renderingData, eyeIndex);
// Draw Gizmos...
DrawGizmos(context, camera, GizmoSubset.PreImageEffects);
// In this block after rendering drawing happens, e.g, post processing, video player capture.
ExecuteBlock(RenderPassBlock.AfterRendering, blockRanges, context, ref renderingData, eyeIndex);
}
RenderPass
作用:实现具体渲染逻辑。
public abstract class ScriptableRenderPass
{
...
// 在渲染相机之前被Renderer调用
// 若需要配置渲染目标和他们的Clear状态,或创建一个临时的渲染对象贴图,可以重写该方法
public virtual void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { }
// 在执行Render pass会调用该方法
public virtual void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) { }
// 当相机渲染完成后调用,可以使用该回调来释放该Pass创建的任何资源
// camera stack 中的每个相机都会调用
public virtual void OnCameraCleanup(CommandBuffer cmd) { }
// 当一个camera stack渲染完成后调用
// 该方法仅在camera stack中的最后一个相机渲染完成后调用一次
public virtual void OnFinishCameraStackRendering(CommandBuffer cmd) { }
// 执行pass渲染。可以自定义渲染内容
public abstract void Execute(ScriptableRenderContext context, ref RenderingData renderingData);
...
}
RenderFeature
作用:只是“空壳”,配置
RenderPassEvent
,与持有ScriptableRenderPass
的实例,并将其注入到ScriptableRenderer
。
public abstract class ScriptableRendererFeature : ScriptableObject, IDisposable
{
// 初始化RendererFeature资源,每次序列化发生的时候调用
public abstract void Create();
...
// 该方法在ScriptableRenderer的SetUp()执行时触发,将一个或多个ScriptableRendererPass插入到渲染队列中
public abstract void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData);
...
}
七块君:Tech-Artist 学习笔记:URP 中比 RendererFeature 更灵活的自定义 Pass 插入小技巧)
总结
以上所述,即为URP的主体代码,详细代码(比如每一个 ScriptableRenderPass
,不同的渲染器,细节的渲染过程)可以细读 com.unity.render-pipelines.universal 里面的代码.
参考链接: