unityURP管线学习+后处理
Posted 我要吐泡泡了哦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unityURP管线学习+后处理相关的知识,希望对你有一定的参考价值。
unityURP管线学习+后处理
一,前置知识
RenderPipeline
默认管线RenderPipeline
- 一些流程是由代码控制开关的,比如Depth Texture这一步,如果设置了depthTextureMode,则会把所有LightMode为ShadowCaster的Pass执行一遍并存储到Depth Texture中。
- 一些流程是由Shader 中的Tags “LightMode” “RenderType” “Queue”标注控制的。渲染时unity根据这些标签,吧Pass放在图中对应顺序运行,因此才可以实现透明物体的“先渲染不透明,再渲染透明”的操作。
Scriptable Render Pipeline可编程渲染管线
-
unity提供的自定义渲染方案,可以灵活根据需求定制。也就是说可以自己随意设定上述谁先谁后的渲染流程,以满足定制化需求。除此以外SRP还有SRP Batcher等优化。
-
而URP就是Unity在SRP基础上定义出的一个适配大部分情况的管线。
二,URP渲染流程(代码向)
Render函数
com.unity.render-pipelines.universal@7.7.1/Runtime/UniversalRenderPipeline.cs
protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
//固定调用,表示一个相机即将开始渲染
BeginFrameRendering(renderContext, cameras);
//设置是否是线性空间、是否用SRPBatcher
GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);
GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher;
//设置没开环境反射时的默认SH、阴影颜色等
SetupPerFrameShaderConstants();
……
SortCameras(cameras);//根据相机深度把相机排序
for (int i = 0; i < cameras.Length; ++i)
var camera = cameras[i];
……
if (IsGameCamera(camera))
//遍历主相机的CameraStack里的每一个Overlay相机,并全部渲染出来
RenderCameraStack(renderContext, camera);
else
BeginCameraRendering(renderContext, camera);
……
//看当前相机是否在后期Volume内,如果在则触发对应的后期效果
UpdateVolumeFramework(camera, null);
//渲染一个相机(剪裁、设置渲染器、执行渲染器)
RenderSingleCamera(renderContext, camera);
//固定调用,表示一个相机已经结束渲染
EndCameraRendering(renderContext, camera);
EndFrameRendering(renderContext, cameras);
- 这里包含了相机渲染常规流程,以后会常见到:
BeginCameraRendering(context, currCamera);//开始
UpdateVolumeFramework(currCamera, currCameraData);//如果有后处理则触发
//从相机的UniversalAdditionalCameraData里提取设置参数
InitializeCameraData(baseCamera, baseCameraAdditionalData, out var baseCameraData);
//渲染耽搁相机:剪裁、设置渲染器、执行渲染器
RenderSingleCamera(context, overlayCameraData, lastCamera, anyPostProcessingEnabled);
EndCameraRendering(context, currCamera);//结束
RenderSingleCamera函数
其中最关键的是RenderSingleCamera函数:
/// <summary>
/// 渲染一个相机,其过程主要包括剪裁、设置渲染器、执行渲染器三步。
/// </summary>
/// <param name="context">渲染上下文用于记录执行过程中的命令。</param>
/// <param name="cameraData">相机渲染数据,里面可能包含了继承自基础相机的一些参数</param>
/// <param name="anyPostProcessingEnabled">如果相机需要做后期效果处理则为true,否则为false.</param>
static void RenderSingleCamera(ScriptableRenderContext context, CameraData cameraData, bool anyPostProcessingEnabled)
Camera camera = cameraData.camera;
//获取当前相机的渲染器renderer,URP中默认使用的是ForwardRenderer
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;
//获取相机视锥裁剪参数,保存在变量cullingParameters里
if (!camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters))
return;
ScriptableRenderer.current = renderer;
bool isSceneViewCamera = cameraData.isSceneViewCamera;
//申请一个CommandBuffer来执行渲染命令
ProfilingSampler sampler = (asset.debugLevel >= PipelineDebugLevel.Profiling) ? new ProfilingSampler(camera.name): _CameraProfilingSampler;
CommandBuffer cmd = CommandBufferPool.Get(sampler.name);
using (new ProfilingScope(cmd, sampler))
//重置渲染对象及执行状态,清空pass队列
renderer.Clear(cameraData.renderType);
//根据cameraData设置好cullingParameters,包含shadowDistance等
renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);
context.ExecuteCommandBuffer(cmd);//执行渲染命令
cmd.Clear();//清空CommandBuffer
……
//根据剪裁参数计算剪裁结果cullResults,后续要从cullResults中筛选要渲染的元素。
var cullResults = context.Cull(ref cullingParameters);
//用剪裁结果cullResults、灯光等可变数据 初始化渲染数据renderingData
InitializeRenderingData(asset, ref cameraData, ref cullResults, anyPostProcessingEnabled, out var renderingData);
……
//根据渲染数据renderingData,筛选需要的pass进队列
renderer.Setup(context, ref renderingData);
//执行队列中的渲染pass
renderer.Execute(context, ref renderingData);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
context.Submit();
ScriptableRenderer.current = null;
其中的InitializeRenderingData函数
static void InitializeRenderingData(UniversalRenderPipelineAsset settings, ref CameraData cameraData, ref CullingResults cullResults,
bool anyPostProcessingEnabled, out RenderingData renderingData)
//这里主要处理了要不要阴影,并且addlight里只支持SpotLight的阴影
……
renderingData.cullResults = cullResults;
renderingData.cameraData = cameraData;
//
InitializeLightData(settings, visibleLights, mainLightIndex, out renderingData.lightData);
InitializeShadowData(settings, visibleLights, mainLightCastShadows, additionalLightsCastShadows && !renderingData.lightData.shadeAdditionalLightsPerVertex, out renderingData.shadowData);
……
- InitializeLightData设置了最大灯光数maxPerObjectLights,GLES 2最多四盏,其余最多八盏,如下图。
- InitializeShadowData看到:
- 默认光照在pipelineasset设置,特殊光照在光源的UniversalAdditionalLightData组件设置
- 屏幕空间阴影需要设备GLES 2以上才支持
- 阴影质量由shadowmap分辨率和cascade数共同决定
其中的ForwardRenderer类
ForwardRenderer继承于ScriptableRenderer,它维护了一个ScriptableRenderPass的列表,在每帧前王列表里新增pass,然后执行pass渲染画面,每帧结束再清空列表。它的渲染资源被序列化为ScriptableRendererData。
ScriptableRenderer里的核心函数Setup和Execute每帧都会执行,其中Setup会把要执行的pass加入列表,Execute将列表里的pass按渲染顺序分类提取并执行。
ForwardRenderer下Setup函数(重点)
主要是将需要的pass加入渲染队列中。
public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
……
//1,渲染深度纹理的相机,只加入RenderFeature、不透明物体、天空盒、半透明物体的Pass
bool isOffscreenDepthTexture = cameraData.targetTexture != null && cameraData.targetTexture.format == RenderTextureFormat.Depth;
if (isOffscreenDepthTexture)
……
for (int i = 0; i < rendererFeatures.Count; ++i)
if(rendererFeatures[i].isActive)
rendererFeatures[i].AddRenderPasses(this, ref renderingData);
EnqueuePass(m_RenderOpaqueForwardPass);
EnqueuePass(m_DrawSkyboxPass);
……
EnqueuePass(m_RenderTransparentForwardPass);
return;
//获取cameraData、UniversalRenderPipeline.asset、renderingData中的各种参数供判断用
……
// 2,如果需要ColorTexture就设置颜色缓冲为m_CameraColorAttachment;如果需要DepthTexture就设置深度缓冲为m_CameraDepthAttachment
//需要渲染到ColorTexture的条件包括:打开MSAA、打开RenderScale、打开HDR、打开Post-Processing、打开渲染到OpaqueTexture、添加了自定义ScriptableRendererFeature等
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());
//3,将所有自定义的ScriptableRendererFeature加入到ScriptableRenderPass的队列中
for (int i = 0; i < rendererFeatures.Count; ++i)
if(rendererFeatures[i].isActive)
rendererFeatures[i].AddRenderPasses(this, ref renderingData);
//4,将各种通用Pass根据各自条件加入到ScriptableRenderPass的队列中
if (mainLightShadows)
EnqueuePass(m_MainLightShadowCasterPass);
if (additionalLightShadows)
EnqueuePass(m_AdditionalLightsShadowCasterPass);
if (requiresDepthPrepass)
m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
EnqueuePass(m_DepthPrepass);
if (generateColorGradingLUT)
m_ColorGradingLutPass.Setup(m_ColorGradingLut);
EnqueuePass(m_ColorGradingLutPass);
EnqueuePass(m_RenderOpaqueForwardPass);
……
if (camera.clearFlags == CameraClearFlags.Skybox && (RenderSettings.skybox != null || cameraSkybox?.material != null) && !isOverlayCamera)
EnqueuePass(m_DrawSkyboxPass);
// 如果创建了DepthTexture,我们需要复制它,otherwise我们可以将它渲染到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);
……
if (transparentsNeedSettingsPass)
EnqueuePass(m_TransparentSettingsPass);
EnqueuePass(m_RenderTransparentForwardPass);
EnqueuePass(m_OnRenderObjectCallbackPass);
……
//5,如果是最后一个渲染的相机,则将一些需要最后Blit的Pass加入到ScriptableRenderPass的队列中。
if (lastCameraInTheStack)
// 后处理得到最终的渲染目标,不需要 final blit pass.
if (applyPostProcessing)
m_PostProcessPass.Setup(……);
EnqueuePass(m_PostProcessPass);
if (renderingData.cameraData.captureActions != null)
m_CapturePass.Setup(m_ActiveCameraColorAttachment);
EnqueuePass(m_CapturePass);
……
// 执行FXAA或者需要在AA之后做的后处理.
if (applyFinalPostProcessing)
m_FinalPostProcessPass.SetupFinalPass(sourceForFinalPass);
EnqueuePass(m_FinalPostProcessPass);
// We need final blit to resolve to screen
if (!cameraTargetResolved)
m_FinalBlitPass.Setup(cameraTargetDescriptor, sourceForFinalPass);
EnqueuePass(m_FinalBlitPass);
else if (applyPostProcessing)
m_PostProcessPass.Setup(……);
EnqueuePass(m_PostProcessPass);
……
可以看到可用的pass有MainLightShadowCasterPass、AdditionalLightsShadowCasterPass,DepthPrePass,ScreenSpaceShadowResolvePass,ColorGradingLutPass,RenderOpaqueForwardPass,DrawSkyboxPass,CopyDepthPass,CopyColorPass,TransparentForwardPass,RenderObjectCallbackPass,PostProcessPass,CapturePass,FinalPostProcessPass,FinalBlitPass等等。
- 注意这里比默认管线多了CopyColor和CopyDepth两个步骤(水面折射时抓取color用,但无法多重折射)
ForwardRenderer下Execute函数(重点)
用于执行各个队列里的pass
public void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
……
//1,把m_ActiveRenderPassQueue中的pass进行排序.
SortStable(m_ActiveRenderPassQueue);
……
//2,按照每个pass的renderPassEvent字段大小把ScriptableRenderPass分配到不同block.
FillBlockRanges(blockEventLimits, blockRanges);
……
//设置shader光照常量和光照相关keyword
SetupLights(context, ref renderingData);
//3,根据不同的渲染block,取出这个block所有Pass依次执行其中的渲染过程。ExecuteRenderPass+submit
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);
//4,cleanup所有的pass,释放RT,重置渲染对象,清空pass队列
InternalFinishRendering(context, cameraData.resolveFinalTarget);
……
RenderPassEvent字段大小:
public enum RenderPassEvent
BeforeRendering = 0,
BeforeRenderingShadows = 50, // 0x00000032
AfterRenderingShadows = 100, // 0x00000064
BeforeRenderingPrepasses = 150, // 0x00000096
AfterRenderingPrePasses = 200, // 0x000000C8
BeforeRenderingOpaques = 250, // 0x000000FA
AfterRenderingOpaques = 300, // 0x0000012C
BeforeRenderingSkybox = 350, // 0x0000015E
AfterRenderingSkybox = 400, // 0x00000190
BeforeRenderingTransparents = 450, // 0x000001C2
AfterRenderingTransparents = 500, // 0x000001F4
BeforeRenderingPostProcessing = 550, // 0x00000226
AfterRenderingPostProcessing = 600, // 0x00000258
AfterRendering = 1000, // 0x000003E8
三,后处理
- URP后处理是有4部分组成,分别是渲染器(Forward Renderer)— 后处理(Volume) — Pass模块 —Shader:
3.1 RenderFeature后处理
- RenderFeature是用来拓展Pass的,依附于ForwardRenderer,可以在渲染的某个时机插入一次渲染命令(例如渲染不透明后描边、渲染半透明后滤镜等),因此一般的全屏渲染后处理可以使用RenderFeature处理。
- 「注意:在URP里原MonoBehaviour里的OnRenderImage函数被取消了,需要使用ScriptableRenderPass 来完成类似功能」
RenderFeature在URPTest_Renderer.asset文件的面板下可以看到,写好类后通过“Add Renderer Feature”新增:
按照下图步骤creat RenderFeature类文件:
-
创建RenderFeature后处理类需要继承ScriptableRendererFeature类,再加两个类组成简单逻辑:
- CustomRenderPass类,继承ScriptableRenderPass类,包含核心渲染逻辑。
- XXXSettings类,用于在RenderFeature面板上传参。
-
大致代码结构如下:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class XXXTest : ScriptableRendererFeature
[System.Serializable]
public class XXXSettings
//指定该RendererFeature在渲染流程的哪个时机插入
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
//需要搭载一个材质设定
public Material material = null;
public class CustomRenderPass : ScriptableRenderPass
//在渲染执行前被调用,可以在这里配置render target,初始化状态,创建临时的渲染纹理
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
//后处理的逻辑和渲染核心函数,基本相当于内置管线的OnRenderImage函数
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
//完成渲染相机后调用,用于释放本次渲染流程创建的分配资源
public override void FrameCleanup ( CommandBuffer)
public XXXSettings settings = new XXXSettings();
CustomRenderPass scriptablePass;
public override void Create()
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
- 其中Create()进行初始化操作,可以把settings里的参数从面板上赋予给CustomRenderPass:
public override void Create()
scriptablePass = new CustomRenderPass();
scriptablePass.material = settings.material;
scriptablePass.renderPassEvent = settings.renderPassEvent;
scriptablePass.Scale = settings.Scale;
- AddRenderPasses()将CustomRenderPass加入队列,也可以在这里把相机输出给到CustomRenderPass(需要增加Setup函数)。
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
scriptablePass.Setup(renderer.cameraColorTarget);
renderer.EnqueuePass(scriptablePass);
- Renderer Pass里核心是Execute函数,基本相当于内置管线的OnRenderImage函数
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
//另一种获取相机tex的方法
RenderTargetIdentifier cameraColorTexture = new RenderTargetIdentifier("_CameraColorTexture");
material.SetFloat("_Distance", _Distance)以上是关于unityURP管线学习+后处理的主要内容,如果未能解决你的问题,请参考以下文章
0基础UnityURP渲染管线之阴影ShadowCaster-ShadowMask-Map傻傻分不清楚(代码向)