找回密码
 立即注册
查看: 627|回复: 0

[笔记] [Universal RP]Unity通用渲染管线学习

[复制链接]
发表于 2020-11-25 09:34 | 显示全部楼层 |阅读模式
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 中的最后一个例子,半透明渲染。
  1. using UnityEngine;
  2. using UnityEngine.Rendering;
  3. using UnityEngine.Experimental.Rendering;
  4. // RenderPipelineAsset继承自ScriptableObject,用于提供序列化的渲染参数、并根据参数生成管线实例
  5. [ExecuteInEditMode]
  6. public class TransparentAssetPipe : RenderPipelineAsset
  7. {
  8. #if UNITY_EDITOR
  9.     // 编辑器中创建ScriptableObject
  10.     [UnityEditor.MenuItem("SRP-Demo/03 - Create Transparent Asset Pipeline")]
  11.     static void CreateBasicAssetPipeline()
  12.     {
  13.         var instance = ScriptableObject.CreateInstance<TransparentAssetPipe>();
  14.         UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/3-TransparentAssetPipe/TransparentAssetPipe.asset");
  15.     }
  16. #endif
  17.     // 创建渲染管线实例的方法
  18.     protected override IRenderPipeline InternalCreatePipeline()
  19.     {
  20.         return new TransparentAssetPipeInstance();
  21.     }
  22. }
  23. public class TransparentAssetPipeInstance : RenderPipeline
  24. {
  25.     // 渲染入口点
  26.     public override void Render(ScriptableRenderContext context, Camera[] cameras)
  27.     {
  28.         base.Render(context, cameras);
  29.         // 遍历相机
  30.         foreach (var camera in cameras)
  31.         {
  32.             // 剔除
  33.             // 新建一个结构体,用于存储剔除参数
  34.             ScriptableCullingParameters cullingParams;
  35.             // 填充来自摄像机的剔除参数
  36.             if (!CullResults.GetCullingParameters(camera, out cullingParams))
  37.                 continue;
  38.             // 执行剔除操作,并存储剔除结果
  39.             CullResults cull = CullResults.Cull(ref cullingParams, context);
  40.             // 设置渲染相机(设置渲染目标, view/projection 矩阵和每相机内置shader变量).
  41.             context.SetupCameraProperties(camera);
  42.             // 清理深度缓存
  43.             var cmd = new CommandBuffer();
  44.             cmd.ClearRenderTarget(true, false, Color.black);
  45.             context.ExecuteCommandBuffer(cmd);
  46.             cmd.Release();
  47.             // 使用一个指定的着色器通道,新建渲染设置
  48.             var settings = new DrawRendererSettings(camera, new ShaderPassName("BasicPass"));
  49.             settings.sorting.flags = SortFlags.CommonOpaque;
  50.             // 获取不透明渲染过滤器设置
  51.             var filterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque };
  52.             // 绘制所有渲染器
  53.             context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
  54.             // 绘制天空盒
  55.             context.DrawSkybox(camera);
  56.             // 绘制半透明物体(着色器通道还是绘制不透明物体时用到的)
  57.             settings.sorting.flags = SortFlags.CommonTransparent;
  58.             filterSettings.renderQueueRange = RenderQueueRange.transparent;
  59.             context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
  60.             
  61.             // 提交渲染上下文
  62.             context.Submit();
  63.         }
  64.     }
  65. }
复制代码
渲染入口点为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方法
  1.         public static void RenderSingleCamera(ScriptableRenderContext context, Camera camera)
  2.         {
  3.             if (!camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters))
  4.                 return;
  5.             var settings = asset;
  6.             UniversalAdditionalCameraData additionalCameraData = null;
  7.             if (camera.cameraType == CameraType.Game || camera.cameraType == CameraType.VR)
  8. #if UNITY_2019_3_OR_NEWER
  9.                 camera.gameObject.TryGetComponent(out additionalCameraData);
  10. #else
  11.                 additionalCameraData = camera.gameObject.GetComponent<LWRPAdditionalCameraData>();
  12. #endif
  13.             InitializeCameraData(settings, camera, additionalCameraData, out var cameraData);
  14.             SetupPerCameraShaderConstants(cameraData);
  15.             ScriptableRenderer renderer = (additionalCameraData != null) ? additionalCameraData.scriptableRenderer : settings.scriptableRenderer;
  16.             if (renderer == null)
  17.             {
  18.                 Debug.LogWarning(string.Format("Trying to render {0} with an invalid renderer. Camera rendering will be skipped.", camera.name));
  19.                 return;
  20.             }
  21. #if UNITY_EDITOR
  22.             string tag = camera.name;
  23. #else
  24.             string tag = k_RenderCameraTag;
  25. #endif
  26.             CommandBuffer cmd = CommandBufferPool.Get(tag);
  27.             using (new ProfilingSample(cmd, tag))
  28.             {
  29.                 renderer.Clear();
  30.                 renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);
  31.                 context.ExecuteCommandBuffer(cmd);
  32.                 cmd.Clear();
  33. #if UNITY_EDITOR
  34.                 // Emit scene view UI
  35.                 if (cameraData.isSceneViewCamera)
  36.                     ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
  37. #endif
  38.                 var cullResults = context.Cull(ref cullingParameters);
  39.                 InitializeRenderingData(settings, ref cameraData, ref cullResults, out var renderingData);
  40.                 renderer.Setup(context, ref renderingData);
  41.                 renderer.Execute(context, ref renderingData);
  42.             }
  43.             context.ExecuteCommandBuffer(cmd);
  44.             CommandBufferPool.Release(cmd);
  45.             context.Submit();
  46.         }
复制代码
这个方法的过程如下:
    初始化剔除参数 获取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方法
  1. static void InitializeCameraData(UniversalRenderPipelineAsset settings, Camera camera, UniversalAdditionalCameraData additionalCameraData, out CameraData cameraData)
  2. {
  3.     const float kRenderScaleThreshold = 0.05f;
  4.     cameraData = new CameraData();
  5.     cameraData.camera = camera;
  6.     cameraData.isStereoEnabled = IsStereoEnabled(camera);
  7.     int msaaSamples = 1;
  8.     if (camera.allowMSAA && settings.msaaSampleCount > 1)
  9.         msaaSamples = (camera.targetTexture != null) ? camera.targetTexture.antiAliasing : settings.msaaSampleCount;
  10.    
  11.     cameraData.isSceneViewCamera = camera.cameraType == CameraType.SceneView;
  12.     cameraData.isHdrEnabled = camera.allowHDR && settings.supportsHDR;
  13.     cameraData.postProcessEnabled = CoreUtils.ArePostProcessesEnabled(camera)
  14.         && camera.cameraType != CameraType.Reflection
  15.         && camera.cameraType != CameraType.Preview
  16.         && SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
  17.     // Disables postprocessing in mobile VR. It's not stable on mobile yet.
  18.     // TODO: enable postfx for stereo rendering
  19.     if (cameraData.isStereoEnabled && Application.isMobilePlatform)
  20.         cameraData.postProcessEnabled = false;
  21.     Rect cameraRect = camera.rect;
  22.     cameraData.isDefaultViewport = (!(Math.Abs(cameraRect.x) > 0.0f || Math.Abs(cameraRect.y) > 0.0f ||
  23.         Math.Abs(cameraRect.width) < 1.0f || Math.Abs(cameraRect.height) < 1.0f));
  24.     // If XR is enabled, use XR renderScale.
  25.     // Discard variations lesser than kRenderScaleThreshold.
  26.     // Scale is only enabled for gameview.
  27.     float usedRenderScale = XRGraphics.enabled ? XRGraphics.eyeTextureResolutionScale : settings.renderScale;
  28.     cameraData.renderScale = (Mathf.Abs(1.0f - usedRenderScale) < kRenderScaleThreshold) ? 1.0f : usedRenderScale;
  29.     cameraData.renderScale = (camera.cameraType == CameraType.Game) ? cameraData.renderScale : 1.0f;
  30.     bool anyShadowsEnabled = settings.supportsMainLightShadows || settings.supportsAdditionalLightShadows;
  31.     cameraData.maxShadowDistance = (anyShadowsEnabled) ? settings.shadowDistance : 0.0f;
  32.    
  33.     if (additionalCameraData != null)
  34.     {
  35.         cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f;
  36.         cameraData.requiresDepthTexture = additionalCameraData.requiresDepthTexture;
  37.         cameraData.requiresOpaqueTexture = additionalCameraData.requiresColorTexture;
  38.         cameraData.volumeLayerMask = additionalCameraData.volumeLayerMask;
  39.         cameraData.volumeTrigger = additionalCameraData.volumeTrigger == null ? camera.transform : additionalCameraData.volumeTrigger;
  40.         cameraData.postProcessEnabled &= additionalCameraData.renderPostProcessing;
  41.         cameraData.isStopNaNEnabled = cameraData.postProcessEnabled && additionalCameraData.stopNaN && SystemInfo.graphicsShaderLevel >= 35;
  42.         cameraData.isDitheringEnabled = cameraData.postProcessEnabled && additionalCameraData.dithering;
  43.         cameraData.antialiasing = cameraData.postProcessEnabled ? additionalCameraData.antialiasing : AntialiasingMode.None;
  44.         cameraData.antialiasingQuality = additionalCameraData.antialiasingQuality;
  45.     }
  46.     else
  47.     {
  48.         cameraData.requiresDepthTexture = settings.supportsCameraDepthTexture;
  49.         cameraData.requiresOpaqueTexture = settings.supportsCameraOpaqueTexture;
  50.         cameraData.volumeLayerMask = 1; // "Default"
  51.         cameraData.volumeTrigger = null;
  52.         cameraData.postProcessEnabled = false;
  53.         cameraData.isStopNaNEnabled = false;
  54.         cameraData.isDitheringEnabled = false;
  55.         cameraData.antialiasing = AntialiasingMode.None;
  56.         cameraData.antialiasingQuality = AntialiasingQuality.High;
  57.     }
  58.     cameraData.requiresDepthTexture |= cameraData.isSceneViewCamera || cameraData.postProcessEnabled;
  59.     var commonOpaqueFlags = SortingCriteria.CommonOpaque;
  60.     var noFrontToBackOpaqueFlags = SortingCriteria.SortingLayer | SortingCriteria.RenderQueue | SortingCriteria.OptimizeStateChanges | SortingCriteria.CanvasOrder;
  61.     bool hasHSRGPU = SystemInfo.hasHiddenSurfaceRemovalOnGPU;
  62.     bool canSkipFrontToBackSorting = (camera.opaqueSortMode == OpaqueSortMode.Default && hasHSRGPU) || camera.opaqueSortMode == OpaqueSortMode.NoDistanceSort;
  63.     cameraData.defaultOpaqueSortFlags = canSkipFrontToBackSorting ? noFrontToBackOpaqueFlags : commonOpaqueFlags;
  64.     cameraData.captureActions = CameraCaptureBridge.GetCaptureActions(camera);
  65.     cameraData.cameraTargetDescriptor = CreateRenderTextureDescriptor(camera, cameraData.renderScale,
  66.         cameraData.isStereoEnabled, cameraData.isHdrEnabled, msaaSamples);
  67. }
复制代码
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
  1. static void InitializeRenderingData(UniversalRenderPipelineAsset settings, ref CameraData cameraData, ref CullingResults cullResults,
  2.     out RenderingData renderingData)
  3. {
  4.     var visibleLights = cullResults.visibleLights;
  5.     int mainLightIndex = GetMainLightIndex(settings, visibleLights);
  6.     bool mainLightCastShadows = false;
  7.     bool additionalLightsCastShadows = false;
  8.     if (cameraData.maxShadowDistance > 0.0f)
  9.     {
  10.         mainLightCastShadows = (mainLightIndex != -1 && visibleLights[mainLightIndex].light != null &&
  11.                                 visibleLights[mainLightIndex].light.shadows != LightShadows.None);
  12.         // If additional lights are shaded per-pixel they cannot cast shadows
  13.         if (settings.additionalLightsRenderingMode == LightRenderingMode.PerPixel)
  14.         {
  15.             for (int i = 0; i < visibleLights.Length; ++i)
  16.             {
  17.                 if (i == mainLightIndex)
  18.                     continue;
  19.                 Light light = visibleLights[i].light;
  20.                 // LWRP doesn't support additional directional lights or point light shadows yet
  21.                 if (visibleLights[i].lightType == LightType.Spot && light != null && light.shadows != LightShadows.None)
  22.                 {
  23.                     additionalLightsCastShadows = true;
  24.                     break;
  25.                 }
  26.             }
  27.         }
  28.     }
  29.     renderingData.cullResults = cullResults;
  30.     renderingData.cameraData = cameraData;
  31.     InitializeLightData(settings, visibleLights, mainLightIndex, out renderingData.lightData);
  32.     InitializeShadowData(settings, visibleLights, mainLightCastShadows, additionalLightsCastShadows && !renderingData.lightData.shadeAdditionalLightsPerVertex, out renderingData.shadowData);
  33.     InitializePostProcessingData(settings, out renderingData.postProcessingData);
  34.     renderingData.supportsDynamicBatching = settings.supportsDynamicBatching;
  35.     renderingData.perObjectData = GetPerObjectLightFlags(renderingData.lightData.additionalLightsCount);
  36.     bool platformNeedsToKillAlpha = Application.platform == RuntimePlatform.IPhonePlayer ||
  37.         Application.platform == RuntimePlatform.Android ||
  38.         Application.platform == RuntimePlatform.tvOS;
  39.     renderingData.killAlphaInFinalBlit = !Graphics.preserveFramebufferAlpha && platformNeedsToKillAlpha;
  40. }
复制代码
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。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-17 10:29 , Processed in 0.103263 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表