URP渲染管线研究
相关类
- UniversalRenderPipeline 渲染管线
- ScriptableRenderer 渲染器,主要抽象不同的渲染路径
- ForwordRenderer urp 只有一个前向渲染器
- ScriptableRenderPass 渲染某个通道
- MainLightShadowCasterPass 主光源阴影
- DrawObjectsPass 普通物体渲染(包括透明和不透明)
- DrawSkyboxPass 天空盒
- DepthOnlyPass 深度
- PostProcessPass 后期处理
大概流程
-
为了区分,把 RenderPipeline 称为 渲染管线, ShaderPipeline 称为着色管线,RenderPass 称为渲染通道,ShaderPass 称为着色通道
-
渲染管线根据需要加入各种渲染通道,对渲染通道排序后依次调用,每个渲染通道根据需要加入各种渲染指令到指令队列中,
最后在 ScriptableRenderContext.Submit 时执行所有渲染指令
在渲染通道中有个渲染物体的通道,该通道遍历物体队列把调用Shader中的某个Pass的指令加入指令队列->Pass中通过Shader执行物体渲染过程 -
渲染管线 -> 渲染单个相机 -> 加入各种RenderPass -> RenderPass排序后依次调用 -> RenderPass执行时添加各种 Command 到 CommandBuffer-> submit 时执行 CommandBuffer
-> 执行渲染网格指令时会执行着色管线 -> 调用Shader中的某个Pass->Pass中通过Shader执行物体渲染过程
渲染目标
-
RenderTargetIdentifier
用来统一三种渲染对象:- RenderTexture 自己创建的渲染纹理
- CommandBuffer.GetTemporaryRT 通过该函数创建的临时纹理
- BuiltinRenderTextureType 内置纹理
- CurrentActive 当前目标纹理,就是通过 CommandBuffer.SetRenderTarget 设置的纹理
- CameraTarget 当前显示器纹理,渲染给它就等于在显示器显示,如果是双缓冲区也有可能是后备缓冲区
-
RenderTargetHandle
用来统一三种渲染目标:- RenderTargetIdentifier
- CameraTarget 指的就是离屏表面 m_ColorBufferSystem.GetBackBuffer();
- Shader.PropertyToID(shaderProperty); 显卡上的命名纹理
-
RenderTargetHandle.CameraTarget
相机如果是直接上屏的话,就要渲染给它 -
ScriptableRenderer.m_CameraColorTarget
当前相机渲染的目标
可以是纹理或BuiltinRenderTextureType.CameraTarget
这个是 ScriptableRenderer 执行渲染流程时使用
在 RenderPass 中通过 renderingData.cameraData.renderer.cameraColorTarget 获取的就是该目标
或直接通过 ScriptableRender.current.cameraColorTarget; 获取
该值只在渲染过程获取才有效,并且在渲染过程是会改变的-
渲染开始时,赋值为离屏表面
m_ActiveCameraColorAttachment = m_ColorBufferSystem.GetBackBuffer();
ConfigureCameraColorTarget(m_ActiveCameraColorAttachment.Identifier()); -
渲染Base类型相机时,如果有需要(比如做后期效果),会创建一个临时纹理
if (cameraData.renderType == CameraRenderType.Base)
if (intermediateRenderTexture)
CreateCameraRenderTarget(context, ref cameraTargetDescriptor, useDepthPriming); -
渲染结束时,会调用 ScriptableRender.SwapColorBuffer 切换离屏表面(似乎没调用到)
-
-
ScriptableRenderer.m_ActiveColorAttachments
当前渲染激活的目标
ScriptableRenderer 内部维护的当前激活目标,要保证跟 BuiltinRenderTextureType.CurrentActive 一致
直接通过 CommandBuffer.SetRenderTarget 或 CoreUtils.SetRenderTarget 设置并不会改变这个目标
要改变这个目标需要在 RenderPass 中重载 Configure 函数,然后通过 ConfigureTarget 设置
当 RenderPass 执行时会激活设置的目标,没有设置则激活 ScriptableRenderer.m_CameraColorTarget- 如何使用激活的渲染目标
只要用 BuiltinRenderTextureType.CurrentActive 就能代表当前激活的渲染目标
当你使用 CommandBuffer.SetRenderTarget 后当前激活的渲染目标将会改变
如果你用 RTHandle handle = RTHandles.Alloc(BuiltinRenderTextureType.CurrentActive); 绑定了激活的渲染目标
则使用 CommandBuffer.SetRenderTarget 后不影响已有的 handle 对象,它仍然指向原来的目标
- 如何使用激活的渲染目标
-
ForwardRenderer.m_CameraColorAttachment
一个临时的渲染目标,当相机要渲染到纹理或抓图时就会创建这个
这个值一般是 ForwardRenderer 内部创建 RenderPass 使用 -
ForwardRenderer.m_ActiveCameraColorAttachment
当前激活的渲染目标,根据需要可以指向 m_CameraColorAttachment 或 CameraTarget
在最开始时设置给 ScriptableRender.m_CameraColorTarget
这个值一般是 ForwardRenderer 内部创建 RenderPass 使用
CommandBuffer详解
-
CommandBuffer.GetTemporaryRT获取的RT会在Graphics.ExecuteCommandBuffer或者CameraEvent渲染完成后被回收,
这就可能导致在不同CameraEvent获取RT时,取得之前创建的RT,覆盖原有的效果。
如果是持续化显示,通过RenderTexture.GetTemporary接口可以避免这类问题。 -
RenderBufferLoadAction枚举类型(SetRenderTarget额外的参数)
RenderBufferLoadAction描述渲染目标激活(加载)时应对其执行的操作。
其中有三个值,在SetRenderTarget函数中,根据自己的RenderTexture实际的使用情况选择不同的Load策略:- Load:当RenderBuffer被激活时,载入原本已保存的内容,这个加载读取的操作对Tile base框架的GPU比较占用宽带。
- Clear:当RenderBuffer被激活时,会清理掉原本buffer里的内容。目前需要用CommandBuffer.CleanRenderTarget做为代替。
- DontCare:当RenderBuffer被激活时,GPU不会把RenderBuffer的内容加载进内存中,从而减少宽带传输压力。
RenderPass说明
-
相关代码
执行 RenderPass 逻辑: ScriptableRender.ExecuteRenderPass
抓取相机纹理的Pass: CapturePass -
激活的目标纹理问题
- URP 在渲染每个 ScriptableRenderPass 前都会调用 cmd.SetRenderTarget 重设目标纹理为 ScriptableRenderPass 需要的目标纹理
- 由于 ScriptableRenderer 不是直接使用 BuiltinRenderTextureType.CurrentActive 来判断当前目标,而是另外在 ScriptableRenderer 维护了一套变量,而这套变量只能通过 ScriptableRenderer.SetRenderTarget 进行修改,该函数是 internal ,意味着外部不能调用,底层也是调用 CoreUtils.SetRenderTarget
- 因此我们不能在 RenderPass 中调用 cmd.SetRenderTarget 或 cmd.Blit 之类的会改变当前目标的函数,如果用了就要在用完后还原回来,但更好的作法是使用 RenderPass.Blit 代替 cmd.Blit ,重载 Configure 函数,调用 ConfigureTarget 设置需要的目标纹理来代替 cmd.SetRenderTarget
- ScriptableRenderPass 的 ConfigureTarget 会设置 overrideCameraTarget=true,这样才会在 RenderPass 执行前重设目标纹理,而且 ScriptableRenderPass 的 ConfigureTarget 和 Blit 函数才会调用 ScriptableRenderer.SetRenderTarget 修改自已内部维护的当前激活的纹理目标
总结一下 - 使用 ScriptableRenderPass.Blit 替代 cmd.Blit
- 使用 ScriptableRenderPass.ConfigureTarget 替代 cmd.SetRenderTarget
- 实在要用 cmd.Blit 或 cmd.SetRenderTarget ,请在使用后调用 cmd.SetRenderTarget 还原成 ScriptableRenderer.m_ActiveColorAttachments[0],要用反射
渲染流程
```csharp
// 渲染所有相机
UniversalRenderPipeline.Render(ScriptableRenderContext renderContext, Camera[] cameras)
{
// 相机按深度排序
SortCameras(cameras);
for (int i = 0; i < cameras.Length; ++i)
{
var camera = cameras[i];
BeginCameraRendering(renderContext, camera);
UpdateVolumeFramework(camera, null);
RenderSingleCamera(renderContext, camera)
{
RenderSingleCamera(context, cameraData, cameraData.postProcessEnabled)
{
---------------------------> 接下面
}
}
EndCameraRendering(renderContext, camera);
}
}
// 渲染单个相机
UniversalRenderPipeline.RenderSingleCamera(ScriptableRenderContext context, CameraData cameraData, bool anyPostProcessingEnabled)
{
// 剪裁
camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters);
renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);
// Cull 会触发对象上的 OnWillRenderObject 调用
var cullResults = context.Cull(ref cullingParameters);
// 初始化渲染数据
InitializeRenderingData(asset, ref cameraData, ref cullResults, anyPostProcessingEnabled, out var renderingData);
// 添加 RenderPass
renderer.Setup(context, ref renderingData) // UniversalRender.Setup
{
ScriptableRenderer.AddRenderPasses(ref renderingData)
{
// 调用所有 RenderFeature 来添加 Pass,所以你可以通过扩展 RenderFeature 来实现特效
for (int i = 0; i < rendererFeatures.Count; ++i)
{
rendererFeatures[i].AddRenderPasses(this, ref renderingData);
}
}
// 有些情况不能直接渲染到目标缓存,而是要先渲染到临时纹理,最后再Blit到目标缓存
// 比如 通过 CameraCaptureBridge.AddCaptureAction 捕获相机纹理数据实现截屏
// 比如 有添加 RenderFeature 实现特殊效果
// 比如 动态分辨率
// 比如 开启HDR
bool createColorTexture = RequiresIntermediateColorTexture(ref renderingData, cameraTargetDescriptor) ||
(rendererFeatures.Count != 0 && !isRunningHololens);
if (cameraData.renderType == CameraRenderType.Base)
{
m_ActiveCameraColorAttachment = (createColorTexture) ? m_CameraColorAttachment : RenderTargetHandle.CameraTarget;
m_ActiveCameraDepthAttachment = m_CameraDepthAttachment;
if (intermediateRenderTexture)
// 有些情况需要创建临时纹理,在这里创建临时纹理
CreateCameraRenderTarget(context, ref renderingData.cameraData);
}
// 我们经常获取 Renderer.cameraColorTarget 来取得当前渲染缓冲,就是在这里设置的
// 当然,有些 Pass 也会调用 ConfigureCameraTarget 来修改当前的渲染缓冲
ConfigureCameraTarget(m_ActiveCameraColorAttachment.Identifier(), m_ActiveCameraDepthAttachment.Identifier());
// 添加 RenderFeature 的 pass
for (int i = 0; i < rendererFeatures.Count; ++i)
{
if(rendererFeatures[i].isActive)
rendererFeatures[i].AddRenderPasses(this, ref renderingData);
}
EnqueuePass(m_MainLightShadowCasterPass); // 阴影
EnqueuePass(m_AdditionalLightsShadowCasterPass);
EnqueuePass(m_DepthNormalPrepass);
EnqueuePass(m_PrimedDepthCopyPass);
EnqueuePass(m_RenderOpaqueForwardPass); // 不透明物体
EnqueuePass(m_DrawSkyboxPass); // 天空盒
// 拷贝深度值,后续着色器可通过 _CameraDepthTexture 直接访问该纹理
// 只有勾选 UniversalRenderPipelineAsset 中的 DepthTexture 或 在RenderPass.Setup 中调用 ConfigureInput(ScriptableRenderPassInput.Depth)
// 并且渲染缓存有深度值时才会执行 m_CopyDepthPass
EnqueuePass(m_CopyDepthPass);
// 拷贝当前渲染缓冲的颜色值,这里只有不透明物体和天空盒,还未渲染透明物体,后续着色器可通过 _CameraOpaqueTexture 直接访问该纹理
// 只有勾选 UniversalRenderPipelineAsset 中的 OpaqueTexture 或 在RenderPass.Setup 中调用 ConfigureInput(ScriptableRenderPassInput.Color) 才执行
EnqueuePass(m_CopyColorPass);
EnqueuePass(m_RenderTransparentForwardPass); // 渲染透明物体
EnqueuePass(m_OnRenderObjectCallbackPass); // 调用所有脚本对象上的 OnRenderObject 函数
// 所谓相机堆叠就是一个RenderType=Base的相机和多个(0-n)RenderType=Overlay相机组成的相机组
// 在场景中添加Base相机,该相机会渲染,添加Overlay相机,则只有该相机被关联到Base相机才会渲染
// 关联方法:选择一个 Base 的相机,在 Inspector 的 Stack 字段中关联Overlay相机
// 理解了相机堆叠就能明白 lastCameraInTheStack,就是指相机组中最后一个相机渲染时,lastCameraInTheStack 为 true
// 比如 某个 Base 相机没有关联 Overlay 相机,则该相机构成一个堆叠,该相机渲染时 lastCameraInTheStack 为 true
// 某个 Base 相机关联了 Overlay 相机,则 Base 相机和所有关联的 Overlay 相机构成一个堆叠,当最后一个相机渲染时 lastCameraInTheStack 才为 true
if (lastCameraInTheStack)
{
// 这里面也是最容易出问题的地方,很多 URP 版本都有问题
// 比如 7.3.1 抓取画面回调获得的是没有经过后处理的画面
// 比如 7.7.1
EnqueuePass(postProcessPass); // 后处理,最后一个相机才会调用
EnqueuePass(finalPostProcessPass); // 后处理之后的后处理,比如 FXAA
// 给外面抓取最终渲染画面的机会,比如 截图、录屏
// 可以通过 CameraCaptureBridge.AddCaptureAction 来注入回调,回调的 source 就是相机渲染到的纹理
EnqueuePass(m_CapturePass);
EnqueuePass(m_FinalBlitPass);
}
}
// 执行 RenderPass
renderer.Execute(context, ref renderingData) // ScriptableRender.Execute
{
// 调用所有 RenderPass.OnCameraSetup
InternalStartRendering(context, ref renderingData);
ClearRenderingState(cmd); // 初始化渲染状态
// 初始化全局shader变量:_Time _SinTime _CosTime unity_DeltaTime _TimeParameters
SetShaderTimeValues(cmd, time, deltaTime, smoothDeltaTime);
// 对前面加入的各种 Pass 进行排序,主要是根据 RenderPassEvent 排序
SortStable(m_ActiveRenderPassQueue);
using var renderBlocks = new RenderBlocks(m_ActiveRenderPassQueue)
{
// 对 m_ActiveRenderPassQueue 中的 Pass 按 RenderPassEvent 分组,
// blockRanges[i] 表示i组结束的 Pass 在 m_ActiveRenderPassQueue 中的索引
FillBlockRanges(blockEventLimits, out NativeArray<int> blockRanges);
}
// 设置灯光
SetupLights(context, ref renderingData);
// 按各个分组分别渲染分组中的所有 Pass
ExecuteBlock(RenderPassBlock.BeforeRendering, in renderBlocks, context, ref renderingData);
if (cameraData.renderType == CameraRenderType.Base)
{
// 设置相机相关的 shader 属性,包括 _ScaledScreenParams _WorldSpaceCameraPos _ScreenParams
// _ProjectionParams _ZBufferParams unity_OrthoParams _GlobalMipBias _ScreenSize
// unity_WorldToCamera unity_CameraToWorld unity_CameraProjection unity_CameraInvProjection
context.SetupCameraProperties(camera);
SetPerCameraShaderVariables(cmd, ref cameraData);
}
// 不透明物体
ExecuteBlock(RenderPassBlock.MainRenderingOpaque, in renderBlocks, context, ref renderingData);
// 透明物体
ExecuteBlock(RenderPassBlock.MainRenderingTransparent, in renderBlocks, context, ref renderingData);
// 后处理
ExecuteBlock(RenderPassBlock.AfterRendering, in renderBlocks, context, ref renderingData);
// 调用所有 RenderPass.OnCameraCleanup
InternalFinishRendering(context, cameraData.resolveFinalTarget);
}
// 每个 Pass 只是把渲染指令加入到指令队列中
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
// 到这里才会真正执行所有指令,而且说明是每个相机提交一次而不是所有相机提交一次
context.Submit();
}
// 渲染1组Pass
ScriptableRenderer.ExecuteBlock(int blockIndex, NativeArray<int> blockRanges,
ScriptableRenderContext context, ref RenderingData renderingData, int eyeIndex = 0, bool submit = false)
{
int endIndex = blockRanges[blockIndex + 1];
// 遍历分组中的 Pass
for (int currIndex = blockRanges[blockIndex]; currIndex < endIndex; ++currIndex)
{
var renderPass = m_ActiveRenderPassQueue[currIndex];
ExecuteRenderPass(context, renderPass, ref renderingData, eyeIndex)
{
// 实际执行 Pass 渲染的地方
renderPass.Execute(context, ref renderingData)
{
---------------------------------> 接下面
}
}
}
if (submit)
context.Submit();
}
// 重点介绍普通物体的渲染 Pass (包括透明和不透明)
DrawObjectsPass.Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
// 指定排序方式,不透明物体按离摄像机从近到远排序,这样远的物体如果被覆盖,在深度测试时就被pass掉,提高效率
// 透明物体队列物体按离摄像头从远到近排序,这样才能正确显示
var sortFlags = (m_IsOpaque) ? renderingData.cameraData.defaultOpaqueSortFlags : SortingCriteria.CommonTransparent;
// 渲染这3个 shader pass
List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward"));
m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
var drawSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, sortFlags);
// 透明和不透明物体使用不同的 RenderQueueRange
RenderQueueRange renderQueueRange = (m_IsOpaque) ? RenderQueueRange.opaque : RenderQueueRange.transparent;
// 层遮罩是在 URP 配置中配置的,在 UniversalRenderPipelineAsset_Renderer 配置的 Filtering 下
LayerMask layerMask = (m_IsOpaque) ? renderingData.opaqueLayerMask : transparentLayerMask;
var filterSettings = new FilteringSettings(renderQueueRange, layerMask);
// 渲染物体
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSettings, ref m_RenderStateBlock);
// 对于不支持urp管线的物体,渲染成红色
RenderingUtils.RenderObjectsWithError(context, ref renderingData.cullResults, camera, filterSettings, SortingCriteria.None);
}
```