[Universal RP]Unity通用渲染管线学习
Unity正式加入了Universal RP(通用渲染管线),这里会记录一些官方文档,并分析管线的代码,文中使用Unity2019.3.0b1,Universal RP 7.0.1。可编程渲染管线
为了解决仅有一个默认渲染管线,造成的可配置型、可发现性、灵活性等问题。Unity在管线设计的概念上做了转移,决定在C++端保留一个非常小的渲染内核,让C#端可以通过API暴露出更多的选择性,也就是说,Unity会提供一系列的C# API以及内置渲染管线的C#实现;这样一来,一方面可以保证C++端的代码都能严格通过各种白盒测试,另一方面C#端代码就可以在实际项目中调整,有任何问题也可以方便地进行调试。
新的管线对用户而言主要是C# 端的API以及由这些API编写的一系列定制化的内置渲染管线。而在内部实现上,引擎C++端会负责多线程实现性能关键的部分,如上图所示,而C#端负责更高层的渲染指令调度。
可编程渲染管线的使用层设计
用户可以直接使用开源的内置管线,或者在内置管线的基础上进行修改,甚至直接编写定制化的管线。具体使用上渲染管线在工程中会生成特定的Asset,如下图所示,这个Asset序列化了这条管线的一些公共设置变量,并负责在运行时创建实际的渲染上下文;当这个Asset的设置变量在运行时发生变化,引擎会销毁当前上下文然后重新创建管线。
可编程渲染管线是URP的基础,通过它我们可以知道unity中怎么实现最基本的渲染,unity对渲染管线API封装程度。
通用渲染管线Universal RP
LWRP是URP之前的名称,两者基本没有区别,URP相对LWRP的变化主要是把PostProcessing集成到内部了。
Unity轻量级渲染管线LWRP源码及案例解析,讲解了URP的使用方法和拓展方法,里面有对SRP、URP的一些说明,URP与内置管线的对比,URP的源码结构。
在继续看源码细节之前,先看看URP的主要提升(相对于内置管线),和不完善的地方。
提升:
1 开源
可编程渲染管线最大的好处就是开源,对于有能力的团队,可以选择在SRP的基础上写自己的管线。而使用Unity提供的管线模板URP或HDRP,也可以把他们从Package包中提出到Asset中做更改,上面的文章有提到修改的注意事项(其实就是记着把Shader中的include路径,和代码中的Shader.Find之类的路径改一下),但是并不推荐这么做,因为版本更新的维护是噩梦。开源意味着容易定位到问题,对调试非常友好。
2 渲染路径改为单Pass Forward Rendering
内置管线的多Pass Forward Rendering,会在多光源时对额外的光源使用新的FowardAdd Pass计算,Pass数量是影响物体的光源数量,最大值为8。
URP的单Pass Forward Renderering,会将光源一次性传入Forward Pass,但由于单Pass能够传的数据有限,现在最多支持一个直线光外加4个其他光源。
这样做虽然并不完美,但多光源场景中DrawCall数量会大量下降。
3 拓展性(非代码修改)
URP在渲染队列中嵌入了拓展入口,相当于之前的CommandBuffer的可视化操作。上面的文章中有详细的使用方法。
用新的设计取代了GrabPass的结构,在Opaque渲染之后可以截出一张RenderTexture,提供给之后使用。
4 SRP Batcher
提供了一种新的批处理方式,基于Shader的批处理。不过这个技术还不是正式功能,有些局限,不支持Skinned Meshes、Material Property Blocks。
缺点:
1 不支持多相机叠加
这是很重要的功能,如果真用到了,就去改源码吧,要不等之后更新。
2 Defferred Renderring
现在URP里面没有延迟渲染,也没有时域抗锯齿TAA。
一个SRP代码示例
我们先看 详解可编程脚本渲染管线SRP - Unity Connect 中的最后一个例子,半透明渲染。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;
// RenderPipelineAsset继承自ScriptableObject,用于提供序列化的渲染参数、并根据参数生成管线实例
public class TransparentAssetPipe : RenderPipelineAsset
{
#if UNITY_EDITOR
// 编辑器中创建ScriptableObject
static void CreateBasicAssetPipeline()
{
var instance = ScriptableObject.CreateInstance<TransparentAssetPipe>();
UnityEditor.AssetDatabase.CreateAsset(instance, &#34;Assets/SRP-Demo/3-TransparentAssetPipe/TransparentAssetPipe.asset&#34;);
}
#endif
// 创建渲染管线实例的方法
protected override IRenderPipeline InternalCreatePipeline()
{
return new TransparentAssetPipeInstance();
}
}
public class TransparentAssetPipeInstance : RenderPipeline
{
// 渲染入口点
public override void Render(ScriptableRenderContext context, Camera[] cameras)
{
base.Render(context, cameras);
// 遍历相机
foreach (var camera in cameras)
{
// 剔除
// 新建一个结构体,用于存储剔除参数
ScriptableCullingParameters cullingParams;
// 填充来自摄像机的剔除参数
if (!CullResults.GetCullingParameters(camera, out cullingParams))
continue;
// 执行剔除操作,并存储剔除结果
CullResults cull = CullResults.Cull(ref cullingParams, context);
// 设置渲染相机(设置渲染目标, view/projection 矩阵和每相机内置shader变量).
context.SetupCameraProperties(camera);
// 清理深度缓存
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, false, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
// 使用一个指定的着色器通道,新建渲染设置
var settings = new DrawRendererSettings(camera, new ShaderPassName(&#34;BasicPass&#34;));
settings.sorting.flags = SortFlags.CommonOpaque;
// 获取不透明渲染过滤器设置
var filterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque };
// 绘制所有渲染器
context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
// 绘制天空盒
context.DrawSkybox(camera);
// 绘制半透明物体(着色器通道还是绘制不透明物体时用到的)
settings.sorting.flags = SortFlags.CommonTransparent;
filterSettings.renderQueueRange = RenderQueueRange.transparent;
context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
// 提交渲染上下文
context.Submit();
}
}
}渲染入口点为RenderPipeline类中的Render函数,参数为相机列表和渲染上下文,在这个函数的最后提交渲染上下文,完成渲染。填充渲染上下文的过程中,Unity封装好了一些方法,包括剔除操作、CommandBuffer的一系列指令、绘制渲染器等。
Universal RP
推荐先熟悉Universal RP的使用,再去看代码的实现,这样效率会更高。
Universal RP的依赖Package有两个,CoreRPLibrary是URP和HDRP都用到的一些工具,ShaderGraph是shader的可视化节点编辑器,之前的PostProcessing已经集成到内部了。
Render方法
Universal RP的主要实现就在Universal RP的Runtime文件夹中,我们从渲染入口点开始看实现细节。
UniversalRenderPipeline类中的Render方法
一个Rendering Loop过程
整体结构非常简单,设置GraphicSetting参数,设置每帧Shader中的Global 变量,相机排序,相机遍历,每相机渲染。
首先看一下四个方法BeginCameraRendering、BeginFrameRendering、EndCameraRendering、EndFrameRendering,它们是基类RenderPipeline中的渲染管线回调。
RenderPipeline
GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);
更改颜色空间设置,这个设置位置在Player的OtherSetting中
GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher;
是否开启SRPBatcher(根据shader合并材质),这个功能使用还需要在Shader中将需要用CBuffer保存的变量用一段代码包含。并且有一定的限制。
按材质合并与按Shader合并
SRPBatcher使用注意事项
asset是指UniversalRenderPipelineAsset生成的管线资源,上面包含许多可调节设置,如SRPBatcher在倒数第四行,其他设置会在用到时说明。
UniversalRenderPipelineAsset中的设置
SetupPerFrameShaderConstants函数
RenderSettings.ambientProbe是环境光的2阶球谐函数表达,Unity也使用SphericalHarmonicsL2存储LightProbe数据。
RenderSettings.ambientProbe在这里设置
将系数转到相应颜色空间,把参数-环境光颜色、Subtractive模式下的阴影颜色传递到Shader,这就是每帧的shader常量。
PerFrameBuffer
PerFrameBuffer类中的参数是每帧需要的数据缓存,这些静态数据在创建管线时(或修改管线参数时)被赋值。
UniversalRenderPipeline的构造函数
继续看Render函数中的SortCameras(cameras)
按深度排序
接下来是遍历相机,我们先不关心VFX.VFXManager.ProcessCamera(camera)函数,它是为了在管线中集成Visual Effect Graph功能使用的,这里只有一个需要关心的方法RenderSingleCamera(renderContext, camera)。
RenderSingleCamera方法
public static void RenderSingleCamera(ScriptableRenderContext context, Camera camera)
{
if (!camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters))
return;
var settings = asset;
UniversalAdditionalCameraData additionalCameraData = null;
if (camera.cameraType == CameraType.Game || camera.cameraType == CameraType.VR)
#if UNITY_2019_3_OR_NEWER
camera.gameObject.TryGetComponent(out additionalCameraData);
#else
additionalCameraData = camera.gameObject.GetComponent<LWRPAdditionalCameraData>();
#endif
InitializeCameraData(settings, camera, additionalCameraData, out var cameraData);
SetupPerCameraShaderConstants(cameraData);
ScriptableRenderer renderer = (additionalCameraData != null) ? additionalCameraData.scriptableRenderer : settings.scriptableRenderer;
if (renderer == null)
{
Debug.LogWarning(string.Format(&#34;Trying to render {0} with an invalid renderer. Camera rendering will be skipped.&#34;, camera.name));
return;
}
#if UNITY_EDITOR
string tag = camera.name;
#else
string tag = k_RenderCameraTag;
#endif
CommandBuffer cmd = CommandBufferPool.Get(tag);
using (new ProfilingSample(cmd, tag))
{
renderer.Clear();
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(settings, ref cameraData, ref cullResults, out var renderingData);
renderer.Setup(context, ref renderingData);
renderer.Execute(context, ref renderingData);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
context.Submit();
}这个方法的过程如下:
初始化剔除参数 获取UniversalAdditionalCameraData 初始化CameraData 设置PerCameraBuffer(每相机使用的Shader Global变量PerCameraBuffer) 获取ScriptableRenderer 使用ScriptableRenderer继续填充剔除参数和CameraData 开始性能采样(Profiler面板) 编辑器模式下Scene相机额外显示UI 剔除 根据管线设置、CameraData、剔除结果,初始化渲染数据RenderingData 使用ScriptableRenderer根据RenderingData,Setup并Excute渲染上下文 结束性能分析 提交渲染上下文
1 初始化剔除参数
camera.TryGetCullingParameters(IsStereoEnabled(camera),outvar cullingParameters)用于获取剔除结果,IsStereoEnabled是判断是否是立体相机(VR/AR)。
2 获取UniversalAdditionalCameraData
获取摄像机的额外数据UniversalAdditionalCameraData,我还没搞懂为什么面板是空的,里面的数据能序列化,甚至还有Tooltip,估计是没开发完。
挂着Camera所在GameObject上,面板默认不暴露
3 初始化CameraData,InitializeCameraData方法
static void InitializeCameraData(UniversalRenderPipelineAsset settings, Camera camera, UniversalAdditionalCameraData additionalCameraData, out CameraData cameraData)
{
const float kRenderScaleThreshold = 0.05f;
cameraData = new CameraData();
cameraData.camera = camera;
cameraData.isStereoEnabled = IsStereoEnabled(camera);
int msaaSamples = 1;
if (camera.allowMSAA && settings.msaaSampleCount > 1)
msaaSamples = (camera.targetTexture != null) ? camera.targetTexture.antiAliasing : settings.msaaSampleCount;
cameraData.isSceneViewCamera = camera.cameraType == CameraType.SceneView;
cameraData.isHdrEnabled = camera.allowHDR && settings.supportsHDR;
cameraData.postProcessEnabled = CoreUtils.ArePostProcessesEnabled(camera)
&& camera.cameraType != CameraType.Reflection
&& camera.cameraType != CameraType.Preview
&& SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
// Disables postprocessing in mobile VR. It&#39;s not stable on mobile yet.
// TODO: enable postfx for stereo rendering
if (cameraData.isStereoEnabled && Application.isMobilePlatform)
cameraData.postProcessEnabled = false;
Rect cameraRect = camera.rect;
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.
float usedRenderScale = XRGraphics.enabled ? XRGraphics.eyeTextureResolutionScale : settings.renderScale;
cameraData.renderScale = (Mathf.Abs(1.0f - usedRenderScale) < kRenderScaleThreshold) ? 1.0f : usedRenderScale;
cameraData.renderScale = (camera.cameraType == CameraType.Game) ? cameraData.renderScale : 1.0f;
bool anyShadowsEnabled = settings.supportsMainLightShadows || settings.supportsAdditionalLightShadows;
cameraData.maxShadowDistance = (anyShadowsEnabled) ? settings.shadowDistance : 0.0f;
if (additionalCameraData != null)
{
cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f;
cameraData.requiresDepthTexture = additionalCameraData.requiresDepthTexture;
cameraData.requiresOpaqueTexture = additionalCameraData.requiresColorTexture;
cameraData.volumeLayerMask = additionalCameraData.volumeLayerMask;
cameraData.volumeTrigger = additionalCameraData.volumeTrigger == null ? camera.transform : additionalCameraData.volumeTrigger;
cameraData.postProcessEnabled &= additionalCameraData.renderPostProcessing;
cameraData.isStopNaNEnabled = cameraData.postProcessEnabled && additionalCameraData.stopNaN && SystemInfo.graphicsShaderLevel >= 35;
cameraData.isDitheringEnabled = cameraData.postProcessEnabled && additionalCameraData.dithering;
cameraData.antialiasing = cameraData.postProcessEnabled ? additionalCameraData.antialiasing : AntialiasingMode.None;
cameraData.antialiasingQuality = additionalCameraData.antialiasingQuality;
}
else
{
cameraData.requiresDepthTexture = settings.supportsCameraDepthTexture;
cameraData.requiresOpaqueTexture = settings.supportsCameraOpaqueTexture;
cameraData.volumeLayerMask = 1; // &#34;Default&#34;
cameraData.volumeTrigger = null;
cameraData.postProcessEnabled = false;
cameraData.isStopNaNEnabled = false;
cameraData.isDitheringEnabled = false;
cameraData.antialiasing = AntialiasingMode.None;
cameraData.antialiasingQuality = AntialiasingQuality.High;
}
cameraData.requiresDepthTexture |= cameraData.isSceneViewCamera || cameraData.postProcessEnabled;
var commonOpaqueFlags = SortingCriteria.CommonOpaque;
var noFrontToBackOpaqueFlags = SortingCriteria.SortingLayer | SortingCriteria.RenderQueue | SortingCriteria.OptimizeStateChanges | SortingCriteria.CanvasOrder;
bool hasHSRGPU = SystemInfo.hasHiddenSurfaceRemovalOnGPU;
bool canSkipFrontToBackSorting = (camera.opaqueSortMode == OpaqueSortMode.Default && hasHSRGPU) || camera.opaqueSortMode == OpaqueSortMode.NoDistanceSort;
cameraData.defaultOpaqueSortFlags = canSkipFrontToBackSorting ? noFrontToBackOpaqueFlags : commonOpaqueFlags;
cameraData.captureActions = CameraCaptureBridge.GetCaptureActions(camera);
cameraData.cameraTargetDescriptor = CreateRenderTextureDescriptor(camera, cameraData.renderScale,
cameraData.isStereoEnabled, cameraData.isHdrEnabled, msaaSamples);
}CameraData类
详细的设置大家阅读代码就可以得知。关于if(additionalCameraData !=null)那段设置,我没理解,现在的状态就是不添加UniversalAdditionalCameraData就会不支持后处理等效果,这里猜测这个设计的目的是为了区分两种相机模式,还希望大家能告诉我为什么。defaultOpaqueSortFlags的两种情况只差了一个是否按深度排序,其中camera.opaqueSortMode并没有被设置,在URP中始终未OpaqueSortMode.Default。captureActions只在Editor模式下生效,是录屏用的。cameraTargetDescriptor是RenderTextureDescriptor,在之后的Blit等操作里会用到,MSAA值也保存在这里。
4 设置PerCameraBuffer
SetupPerCameraShaderConstants方法
PerCameraBuffer类
这里设置的几个摄像机参数,_ScreenParams的4个向量格式在HLSL或者GLSL都会用类似的方法传递。
乘法顺序与命名顺序
读到这里,我对乘法顺序有疑问,于是去找shader中的乘法使用,以对比C#中的参数。
com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl中的本地坐标转为世界坐标
首先注意shader中通常使用矩阵在左向量在右的常规乘法。
com.unity.render-pipelines.universal\ShaderLibrary\Input.hlsl
注意红框中,UNITY_MATRIX_MV实际是UNITY_MATRIX_V × UNITY_MATRIX_M。矩阵乘法满足分配率,MVP矩阵乘向量p,与P(V(Mp))是相等的。这也是为什么C#端VP = P×V,只是命名习惯的问题。
5 获取ScriptableRenderer
ScriptableRenderer renderer = (additionalCameraData != null) ? additionalCameraData.scriptableRenderer : settings.scriptableRenderer;
ScriptableRenderer是抽象出的渲染功能实体,是UniversalRP可拓展的最外层,里面还有ScriptableRenderPass可以拓展。目前Unity提供了2个实现ForwardRenderer和Renderer2D,管线默认使用ForwardRenderer,默认资源是UniversalRP/Runtime/Data/ForwardRendererData.asset。可以每相机使用不同的ScriptableRenderer,这个设置方法和版本有关,可以看这个版本使用的addtionalCameraData.scriptableRenderer,有的版本使用camera.scriptableRenderer。
使用Custom管线
6 使用ScriptableRenderer继续填充剔除参数和CameraData
ScriptableRenderer.Clear
ScriptableRenderer.SetupCullingParameters
ForwardRenderer.SetupCullingParameters
Renderer2D.SetupCullingParameters
Clear方法相当于ScriptableRenderer的每相机初始化,SetupCullingParameters是虚方法,在ForwardRenderer和Renderer2D中有不同实现,后面以ForwardRenderer为主进行说明,ForwardRenderer中SetupCullingParameters进一步确定了阴影距离。
7 开始性能采样(Profiler面板)
CommandBufferPool是CommandBuffer对象池,ProfilingSample是封装的性能采样器,这两个类都在Core RP的Package中。
8 编辑器模式下Scene相机额外显示UI
9 剔除
var cullResults = context.Cull(ref cullingParameters),剔除是Unity封装好的一个方法,我们只能通过参数设置进行控制。
剔除参数
剔除结果的内容
10 根据管线设置、CameraData、剔除结果,初始化渲染数据RenderingData
static void InitializeRenderingData(UniversalRenderPipelineAsset settings, ref CameraData cameraData, ref CullingResults cullResults,
out RenderingData renderingData)
{
var visibleLights = cullResults.visibleLights;
int mainLightIndex = GetMainLightIndex(settings, visibleLights);
bool mainLightCastShadows = false;
bool additionalLightsCastShadows = false;
if (cameraData.maxShadowDistance > 0.0f)
{
mainLightCastShadows = (mainLightIndex != -1 && visibleLights.light != null &&
visibleLights.light.shadows != LightShadows.None);
// If additional lights are shaded per-pixel they cannot cast shadows
if (settings.additionalLightsRenderingMode == LightRenderingMode.PerPixel)
{
for (int i = 0; i < visibleLights.Length; ++i)
{
if (i == mainLightIndex)
continue;
Light light = visibleLights.light;
// LWRP doesn&#39;t support additional directional lights or point light shadows yet
if (visibleLights.lightType == LightType.Spot && light != null && light.shadows != LightShadows.None)
{
additionalLightsCastShadows = true;
break;
}
}
}
}
renderingData.cullResults = cullResults;
renderingData.cameraData = cameraData;
InitializeLightData(settings, visibleLights, mainLightIndex, out renderingData.lightData);
InitializeShadowData(settings, visibleLights, mainLightCastShadows, additionalLightsCastShadows && !renderingData.lightData.shadeAdditionalLightsPerVertex, out renderingData.shadowData);
InitializePostProcessingData(settings, out renderingData.postProcessingData);
renderingData.supportsDynamicBatching = settings.supportsDynamicBatching;
renderingData.perObjectData = GetPerObjectLightFlags(renderingData.lightData.additionalLightsCount);
bool platformNeedsToKillAlpha = Application.platform == RuntimePlatform.IPhonePlayer ||
Application.platform == RuntimePlatform.Android ||
Application.platform == RuntimePlatform.tvOS;
renderingData.killAlphaInFinalBlit = !Graphics.preserveFramebufferAlpha && platformNeedsToKillAlpha;
}RenderingData是用于存储每相机渲染所需的所有数据的结构体,这个方法的目的就是完全填充渲染数据,最后交给ScriptableRenderer使用。
RenderingData结构体
var visibleLights = cullResults.visibleLights,从剔除结果中获得所有可见光GetMainLightIndex代码如下,可以从代码中看出MainLight的定义。
GetMainLightIndex方法
下面一段是MainLight和AdditionalLights是否存在阴影的判断,可以看出URP是不支持AdditionalLights的点光源和直线光源阴影的。CullResults和CameraData直接赋值给RenderingData。LightData、ShadowData和PostProcessingData方法由独立的方法赋值。
InitializeLightData方法
最大AdditionalLight数量是4,每相机最大可见光数量是16
LightData相关设置
InitializeShadowData方法前半段
InitializeShadowData方法前半段,设置每光线的Bias值,这里UniversalAdditionalLightData和相机上挂的额外数据类似,不过这个脚本目前只有usePipelineSettings这一个字段,还是默认的true,面板同样是空的。
InitializeShadowData方法后半段
InitializeShadowData方法后半段,设置是否支持MainLight和AdditionalLights的阴影,用到了“MainLight和AdditionalLights是否存在阴影”数据,分别设置ShadowMap的尺寸,MainLight的阴影额外设置级联数据。设置是否支持软阴影、设置ShadowMap的DepthBuffer使用16bits数据。
ShadowData相关设置
InitializePostProcessingData方法
PostProcessingData设置
supportsDynamicBatching:是否支持动态批处理。
PerObjectData枚举
PerObjectData是每Object需要的数据标记,通过下图方法获取。
killAlphaInFinalBlit会在最后的Blit操作前开启ShaderKeyword:_KILL_ALPHA,把前面的数据完全覆盖。
com.unity.render-pipelines.universal\Shaders\Utils\Blit.shader的片元着色器
11 使用ScriptableRenderer根据RenderingData,Setup并Excute渲染上下文
在看具体实现之前,先看看ScriptableRender的设计。
ScriptableRender是由一个实体生成的——ScriptableRendererData。
ScriptableRendererData类
ScriptableRendererData中需要注意Create方法,它会在新建渲染管线时与管线一同创建,设置变化时重新创建ScriptableRender实例,还可以根据每相机设置来创建。ScriptableRendererData中的ScriptableRendererFeature集合,是URP提供给用户不用改代码就能增加Feature的功能,类似的,ScriptableRendererFeature继承自ScriptableObject,它可以创建出ScriptableRenderPass实例。
ScriptableRender类中定义了ScriptableRenderPass集合List<ScriptableRenderPass> m_ActiveRenderPassQueue = new List<ScriptableRenderPass>(32),ScriptableRenderPass中有抽象方法Execute,这个方法会在ScriptableRenderPass的不同子类中实现,context的渲染指令就是在ScriptableRenderPass.Execute方法中被填充的,比如context.DrawRenderers、context.DrawSkybox、context.ExecuteCommandBuffer等指令。ScriptableRenderer中的Setup方法会生成需要的m_ActiveRenderPassQueue,Excute方法会给m_ActiveRenderPassQueue排序分类、设置渲染目标、完成ScriptableRenderPass.Execute调用。
ScriptableRenderPass.Execute
ScriptableRender.Setup
ScriptableRenderer.Setup是虚方法。
ForwardRender类
ForwardRender类中定义了许多Pass结尾的变量,它们就是ScriptableRenderPass的子类。这里还有光照信息,光照在初始化和SetupLights方法中被赋值使用。
ForwardRender.Setup方法使用RenderData数据,来设置好渲染所需要的内容:管理并提交ScriptableRenderPass到m_ActiveRenderPassQueue;提前定义好这个相机要使用的颜色和深度目标,数据格式是RenderTargetIdentifier,是CommandBuffer中RenderTexture的标识;将RendererFeatures生成的ScriptableRenderPass添加到m_ActiveRenderPassQueue中;创建相机渲染目标的临时RenderTexture;设置Backbuffer的格式。
ForwardRender中初始化ScriptableRenderPass的过程
可以把渲染理解为绘制过程,这些ScriptableRenderPass子类就代表不同的过程,可以在上图中看到这些过程所处的阶段,即不同过程的绘制顺序。
VolumeBlendingPass:后处理使用的Volume融合,URP中作为第一个绘制使用MainLightShadowCasterPass:主光源的ShadowCaster贴图绘制AdditionalLightsShadowCasterPass:附加光源的ShadowCaster贴图绘制DepthOnlyPass:绘制深度贴图ScreenSpaceShadowResolvePass:使用ShadowCaster贴图和深度贴图绘制屏幕空间的阴影贴图ColorGradingLutPass:绘制用于后处理ColorGrading的Lut贴图DrawObjectsPass:绘制不透明物体或半透明物体,第一次使用处于RenderPassEvent.BeforeRenderingOpaques阶段,绘制不透明物体CopyDepthPass:复制深度贴图DrawSkyboxPass:绘制天空盒CopyColorPass:复制颜色贴图DrawObjectsPass:绘制不透明物体或半透明物体,第二次使用处于RenderPassEvent.BeforeRenderingTransparents阶段,绘制半透明物体PostProcessPass:后处理,第一次使用处于RenderPassEvent.BeforeRenderingPostProcessing阶段阶段PostProcessPass:后处理,第二次使用处于RenderPassEvent.AfterRenderingPostProcessing阶段阶段CapturePass:用于RenderPassEvent.AfterRendering阶段的截屏,前面提到非Editor模式下renderingData.cameraData.captureActions是空的FinalBlitPass:通常在没有后处理且不以相机为渲染目标的渲染中使用,比如RenderScale不为1时,用于把渲染结果重新传到屏幕
ForwardRender.Setup第1段
ForwardRender.Setup第2段
ForwardRender.Setup第3段
ForwardRender.Setup第4段
因为ScriptableRenderPass的子类内容比较多,这里不展开分析Setup方法了,先去看看ScriptableRender.Execute的功能。
ScriptableRender.Execute
ScriptableRenderer.Execute是ScriptableRenderer层的绘制执行方法,过程如下:
清理光照与阴影的ShaderKeywordm_ActiveRenderPassQueue按照ScriptableRenderPass.renderPassEvent排序时间设置与缓存按Block划分m_ActiveRenderPassQueue执行RenderPassBlock.BeforeRendering的Block初始化相机相关的属性(Shader变量)设置光照(SetupLights方法)时间设置为之前缓存值BeginXRRendering执行RenderPassBlock.MainRendering的Block绘制GizmoSubset.PreImageEffects的Gizmos执行RenderPassBlock.AfterRendering的BlockEndXRRendering绘制GizmoSubset.PostImageEffects的Gizmos清理数据
时间的设置,官方还会改动,代码中有注释说明。有个block的概念,m_ActiveRenderPassQueue被分为3个block,按照下图节点划分,FillBlockRanges为划分方法,ExecuteBlock为按Block执行。
Block划分依据
ExecuteBlock方法中进一步调用了指定Block中的ExecuteRenderPass方法,ExecuteRenderPass方法在确定ScriptableRenderPass对应的渲染目标后,调用ScriptableRenderPass.Execute设置每个渲染模块的上下文。
ScriptableRenderer.ExecuteRenderPass
下面通过Unity中的FrameDebug看看渲染目标设置的过程,Game视窗当前分辨率为1600*900。
SetRenderTarget.Clear1——VolumeBlendingPass被视为第一个以相机为渲染目标的Pass,设置了一个1600*900的<No name>SetRenderTarget.Clear2——MainLightShadowCasterPass在Configure中重新定义了渲染目标,渲染目标变更为2048*1024的TempBufferRenderMainShadowMap——在TempBuffer上绘制ShadowCaster,这里的两个绘制对应两个阴影级联SetRenderTarget.Clear——DepthOnlyPass在Configure中重新定义了渲染目标,渲染目标变更为1600*900的_CameraDepthTextureDepthPrePass——在_CameraDepthTexture中绘制深度信息SetRenderTarget.Clear——ScreenSpaceShadowResolvePass在Configure中重新定义了渲染目标,渲染目标变更为1600*900的_ScreenSpaceShadowMapTextureResolveShadows——根据_CameraDepthTexture、TempBuffer在_ScreenSpaceShadowMapTexture中绘制阴影RenderOpaques——在<No name>上绘制不透明物体,使用到了_ScreenSpaceShadowMapTexture,这里出现了渲染目标变更,但变更目标是之前存在的,所以并没有创建渲染目标RenderSkybox——在<No name>上绘制天空盒
ForwardRender的一帧渲染
ExecuteRenderPass方法中有一行ClearFlag clearFlag = GetCameraClearFlag(camera.clearFlags),这个方法获取第一个Clear的ClearFlag,在不同平台上结果有差异,大家一定要注意。
12 结束性能分析
在ProfilingSample执行Dispose时cmd设置为结束性能分析,并立刻被提交到context中。
13 提交渲染上下文
context.Submit();
由于ScriptableRenderPass内容比较多,就不详细列出RenderData设置的参数是怎么作用于不同的ScriptableRenderPass了,可以在使用相应模块时再去了解。
之后会更新Universal RP中的Lit.shader的内容,这个Shader相当于默认渲染管线中的Standard.shader。
页:
[1]