【Unity】可编程渲染管线——LWRP轻量级渲染管线剖析
前言
Unity2018中引入了可编程渲染管线(Scriptable Render Pipeline,简称SRP),是一种在Unity中通过C#脚本配置和执行渲染的方式。至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二个渲染管线。当然也支持自定义渲染管线。与高清晰渲染管线相比,轻量级渲染管线的开发已经比较成熟。
这篇文章主要是分析轻量级渲染管线的C#代码都做了哪些工作。
可编程渲染管线能做什么
为了解决仅有一个默认渲染管线,造成的可配置型、可发现性、灵活性等问题。Unity在管线设计的概念上做了转移,决定在C++端保留一个非常小的渲染内核,让C#端可以通过API暴露出更多的选择性,也就是说,Unity会提供一系列的C# API以及内置渲染管线的C#实现;这样一来,一方面可以保证C++端的代码都能严格通过各种白盒测试,另一方面C#端代码就可以在实际项目中调整,有任何问题也可以方便地进行调试。
新的管线对用户而言主要是C# 端的API以及由这些API编写的一系列定制化的内置渲染管线。而在内部实现上,引擎C++端会负责多线程实现性能关键的部分,如上图所示,而C#端负责更高层的渲染指令调度。[1]
可编程渲染管线的使用层设计
用户可以直接使用开源的内置管线,或者在内置管线的基础上进行修改,甚至直接编写定制化的管线。具体使用上渲染管线在工程中会生成特定的Asset,如下图所示,这个Asset序列化了这条管线的一些公共设置变量,并负责在运行时创建实际的渲染上下文;当这个Asset的设置变量在运行时发生变化,引擎会销毁当前上下文然后重新创建管线(这个操作在现有固定管线中无法做到)。
简单的渲染管线示例
可编程渲染管线的使用层设计中,最少需要两个类,一个是渲染管线资源,一个是渲染管线实例。下面是一个不透明渲染管线的C#代码查看详细:using System; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Experimental.Rendering; [ExecuteInEditMode] public class OpaqueAssetPipe : RenderPipelineAsset { #if UNITY_EDITOR [UnityEditor.MenuItem("SRP-Demo/02 - Create Opaque Asset Pipeline")] static void CreateBasicAssetPipeline() { var instance = ScriptableObject.CreateInstance<OpaqueAssetPipe>(); UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/2-OpaqueAssetPipe/OpaqueAssetPipe.asset"); } #endif protected override IRenderPipeline InternalCreatePipeline() { return new OpaqueAssetPipeInstance(); } } public class OpaqueAssetPipeInstance : 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); 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("BasicPass")); settings.sorting.flags = SortFlags.CommonOpaque; var filterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque }; context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings); context.DrawSkybox(camera); context.Submit(); } } }
这里定义了一个继承自RenderPipelineAsset的类,通过这个类创建一个渲染管线实例:继承自RenderPipeline的类。
渲染管线实例中的Render方法是渲染入口点,它需要两个参数,渲染上下文以及一个需要渲染的摄像机列表。
在Render方法中依次进行了剔除、绘制、过滤等操作,通过渲染命令缓冲向渲染上下文发送命令来实现绘制。
轻量级渲染管线
以下内容来自Unity官方在GitHub中的项目,本文将在代码中通过注释来解释渲染管线的功能。
渲染管线资源
渲染管线资源是项目中实际使用的资源,它包含一系列的可调整设置,并通过这些设置创建渲染管线实例,在设置发生变更时重新创建。
#if UNITY_EDITOR using System; using UnityEditor; using UnityEditor.ProjectWindowCallback; #endif namespace UnityEngine.Experimental.Rendering.LightweightPipeline { /// <summary> /// 阴影级联 /// </summary> public enum ShadowCascades { NO_CASCADES = 0, TWO_CASCADES, FOUR_CASCADES, } /// <summary> /// 阴影类型 /// </summary> public enum ShadowType { NO_SHADOW = 0, HARD_SHADOWS, SOFT_SHADOWS, } /// <summary> /// 阴影分辨率 /// </summary> public enum ShadowResolution { _256 = 256, _512 = 512, _1024 = 1024, _2048 = 2048, _4096 = 4096 } /// <summary> /// MSAA质量 /// MSAA是“多重采样抗锯齿”,可以使画面更加平滑。 /// 超级采样抗锯齿(Super Sampling Anti-Aliasing)的原理是把当前分辨率成倍提高,然后再把画缩放到当前的显示器上。 /// 这样的做法实际上就是在显示尺寸不变的情况提高分辨率,让单个像素变得极小,这样就能够大幅减轻画面的锯齿感了。 /// 不过是由于对整个显示画面的放大,因此它消耗的显示资源也是非常大的。 /// 不过MSAA是寻找出物体边缘部分的像素,然后对它们进行缩放处理。 /// 由于只是物体的外层像素进行缩放处理,忽略掉了不会产生锯齿的内部像素, /// 所以显卡不会像处理SSAA(超级采样抗锯齿)那样需要庞大的计算量,因此MSAA比起SSAA来更有效。 /// </summary> public enum MSAAQuality { Disabled = 1, _2x = 2, _4x = 4, _8x = 8 } /// <summary> /// 降采样,在ForwardLitPass中使用 /// </summary> public enum Downsampling { None = 0, _2xBilinear, _4xBox, _4xBilinear } /// <summary> /// 默认材质类型 /// LightweightPipelineEditorResources的资源中获得 /// </summary> public enum DefaultMaterialType { Standard = 0, Particle, Terrain, UnityBuiltinDefault } public class LightweightPipelineAsset : RenderPipelineAsset, ISerializationCallbackReceiver { // 这两个路径用于查找ScriptableObject,优先在"Assets"中查找,若不存在直接取默认 public static readonly string s_SearchPathProject = "Assets"; public static readonly string s_SearchPathPackage = "Packages/com.unity.render-pipelines.lightweight"; /// <summary> /// GetDefaultShader()方法调用默认shader的私有变量 /// </summary> Shader m_DefaultShader; // Default values set when a new LightweightPipeli ne asset is created /// <summary> /// 版本号,用于开发过程的迭代,在ISerializationCallbackReceiver接口的OnAfterDeserialize()中可以将旧版数据转换为新版使用的数据 /// </summary> [SerializeField] int k_AssetVersion = 3; #region Editor中包含的元素 /// <summary> /// 最大逐像素光源个数 /// </summary> [SerializeField] int m_MaxPixelLights = 4; /// <summary> /// 支持顶点光照 /// </summary> [SerializeField] bool m_SupportsVertexLight = false; /// <summary> /// 需要深度纹理 /// </summary> [SerializeField] bool m_RequireDepthTexture = false; /// <summary> /// 需要软粒子 /// </summary> [SerializeField] bool m_RequireSoftParticles = false; /// <summary> /// 需要不透明贴图 /// </summary> [SerializeField] bool m_RequireOpaqueTexture = false; /// <summary> /// 不透明降采样 /// </summary> [SerializeField] Downsampling m_OpaqueDownsampling = Downsampling._2xBilinear; /// <summary> /// 支持HDR /// </summary> [SerializeField] bool m_SupportsHDR = false; /// <summary> /// MSAA /// </summary> [SerializeField] MSAAQuality m_MSAA = MSAAQuality._4x; /// <summary> /// 渲染比例 /// </summary> [SerializeField] float m_RenderScale = 1.0f; /// <summary> /// 支持动态批处理 /// </summary> [SerializeField] bool m_SupportsDynamicBatching = true; /// <summary> /// 支持定向阴影 /// </summary> [SerializeField] bool m_DirectionalShadowsSupported = true; /// <summary> /// 阴影图集分辨率 /// </summary> [SerializeField] ShadowResolution m_ShadowAtlasResolution = ShadowResolution._2048; /// <summary> /// 阴影距离 /// </summary> [SerializeField] float m_ShadowDistance = 50.0f; /// <summary> /// 阴影级联 /// </summary> [SerializeField] ShadowCascades m_ShadowCascades = ShadowCascades.FOUR_CASCADES; /// <summary> /// 二级级联分界 /// </summary> [SerializeField] float m_Cascade2Split = 0.25f; /// <summary> /// 四级级联分界 /// </summary> [SerializeField] Vector3 m_Cascade4Split = new Vector3(0.067f, 0.2f, 0.467f); /// <summary> /// 支持非平行光阴影 /// </summary> [SerializeField] bool m_LocalShadowsSupported = true; /// <summary> /// 非平行光阴影图集分辨率 /// </summary> [SerializeField] ShadowResolution m_LocalShadowsAtlasResolution = ShadowResolution._512; /// <summary> /// 支持软阴影 /// </summary> [SerializeField] bool m_SoftShadowsSupported = false; #endregion //以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities [SerializeField] bool m_KeepAdditionalLightVariants = true; [SerializeField] bool m_KeepVertexLightVariants = true; [SerializeField] bool m_KeepDirectionalShadowVariants = true; [SerializeField] bool m_KeepLocalShadowVariants = true; [SerializeField] bool m_KeepSoftShadowVariants = true; /// <summary> /// 4个shader :BlitShader;CopyDepthShader;ScreenSpaceShadowShader;SamplingShader; /// </summary> [SerializeField] LightweightPipelineResources m_ResourcesAsset; // Deprecated [SerializeField] ShadowType m_ShadowType = ShadowType.HARD_SHADOWS; #if UNITY_EDITOR /// <summary> /// 三个材质 /// </summary> [NonSerialized] LightweightPipelineEditorResources m_EditorResourcesAsset; [MenuItem("Assets/Create/Rendering/Lightweight Pipeline Asset", priority = CoreUtils.assetCreateMenuPriority1)] static void CreateLightweightPipeline() { ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<CreateLightweightPipelineAsset>(), "LightweightAsset.asset", null, null); } //[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Resources", priority = CoreUtils.assetCreateMenuPriority1)] static void CreateLightweightPipelineResources() { var instance = CreateInstance<LightweightPipelineResources>(); AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineResources).Name)); } //[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Editor Resources", priority = CoreUtils.assetCreateMenuPriority1)] static void CreateLightweightPipelineEditorResources() { var instance = CreateInstance<LightweightPipelineEditorResources>(); AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineEditorResources).Name)); } /// <summary> /// 创建带初始化的窗口 /// </summary> class CreateLightweightPipelineAsset : EndNameEditAction { public override void Action(int instanceId, string pathName, string resourceFile) { var instance = CreateInstance<LightweightPipelineAsset>(); instance.m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>(); instance.m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>(); AssetDatabase.CreateAsset(instance, pathName); } } /// <summary> /// 加载ScriptableObject资源,先查找Asset文件夹,若不存在使用默认 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> static T LoadResourceFile<T>() where T : ScriptableObject { T resourceAsset = null; var guids = AssetDatabase.FindAssets(typeof(T).Name + " t:scriptableobject", new[] {s_SearchPathProject}); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path); if (resourceAsset != null) break; } // There's currently an issue that prevents FindAssets from find resources withing the package folder. if (resourceAsset == null) { string path = s_SearchPathPackage + "/LWRP/Data/" + typeof(T).Name + ".asset"; resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path); } return resourceAsset; } LightweightPipelineEditorResources editorResources { get { if (m_EditorResourcesAsset == null) m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>(); return m_EditorResourcesAsset; } } #endif LightweightPipelineResources resources { get { #if UNITY_EDITOR if (m_ResourcesAsset == null) m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>(); #endif return m_ResourcesAsset; } } /// <summary> /// 创建渲染管线的方法 /// </summary> /// <returns></returns> protected override IRenderPipeline InternalCreatePipeline() { return new LightweightPipeline(this); } /// <summary> /// 获得ScriptableObject中的材质资源 /// </summary> /// <param name="materialType"></param> /// <returns></returns> Material GetMaterial(DefaultMaterialType materialType) { #if UNITY_EDITOR if (editorResources == null) return null; switch (materialType) { case DefaultMaterialType.Standard: return editorResources.DefaultMaterial; case DefaultMaterialType.Particle: return editorResources.DefaultParticleMaterial; case DefaultMaterialType.Terrain: return editorResources.DefaultTerrainMaterial; // Unity Builtin Default default: return null; } #else return null; #endif } /// <summary> /// 获得版本号 /// </summary> /// <returns></returns> public int GetAssetVersion() { return k_AssetVersion; } /// <summary> /// 最大逐像素光源数量 /// </summary> public int maxPixelLights { get { return m_MaxPixelLights; } } /// <summary> /// 支持顶点光照 /// </summary> public bool supportsVertexLight { get { return m_SupportsVertexLight; } } /// <summary> /// 支持深度纹理 /// </summary> public bool supportsCameraDepthTexture { get { return m_RequireDepthTexture; } } /// <summary> /// 支持软粒子 /// </summary> public bool supportsSoftParticles { get { return m_RequireSoftParticles; } } /// <summary> /// 支持不透明贴图 /// </summary> public bool supportsCameraOpaqueTexture { get { return m_RequireOpaqueTexture; } } /// <summary> /// 不透明降采样 Downsampling._2xBilinear /// </summary> public Downsampling opaqueDownsampling { get { return m_OpaqueDownsampling; } } /// <summary> /// 支持HDR /// </summary> public bool supportsHDR { get { return m_SupportsHDR; } } /// <summary> /// MSAA的程度 /// </summary> public int msaaSampleCount { get { return (int)m_MSAA; } set { m_MSAA = (MSAAQuality)value; } } /// <summary> /// 渲染比例 /// </summary> public float renderScale { get { return m_RenderScale; } set { m_RenderScale = value; } } /// <summary> /// 支持动态批处理 /// </summary> public bool supportsDynamicBatching { get { return m_SupportsDynamicBatching; } } /// <summary> /// 支持定向阴影 /// </summary> public bool supportsDirectionalShadows { get { return m_DirectionalShadowsSupported; } } /// <summary> /// 定向阴影图集分辨率 /// </summary> public int directionalShadowAtlasResolution { get { return (int)m_ShadowAtlasResolution; } } /// <summary> /// 阴影距离 /// </summary> public float shadowDistance { get { return m_ShadowDistance; } set { m_ShadowDistance = value; } } /// <summary> /// 阴影级联(1、2、4) /// </summary> public int cascadeCount { get { switch (m_ShadowCascades) { case ShadowCascades.TWO_CASCADES: return 2; case ShadowCascades.FOUR_CASCADES: return 4; default: return 1; } } } /// <summary> /// 阴影级联2级 /// </summary> public float cascade2Split { get { return m_Cascade2Split; } } /// <summary> /// 阴影级联4级 /// </summary> public Vector3 cascade4Split { get { return m_Cascade4Split; } } /// <summary> /// 支持非平行光阴影 /// </summary> public bool supportsLocalShadows { get { return m_LocalShadowsSupported; } } /// <summary> /// 非平行光阴影分辨率 /// </summary> public int localShadowAtlasResolution { get { return (int)m_LocalShadowsAtlasResolution; } } /// <summary> /// 支持软阴影 /// </summary> public bool supportsSoftShadows { get { return m_SoftShadowsSupported; } } /// <summary> /// 自定义Shader变量分离,False /// </summary> public bool customShaderVariantStripping { get { return false; } } //以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities public bool keepAdditionalLightVariants { get { return m_KeepAdditionalLightVariants; } } public bool keepVertexLightVariants { get { return m_KeepVertexLightVariants; } } public bool keepDirectionalShadowVariants { get { return m_KeepDirectionalShadowVariants; } } public bool keepLocalShadowVariants { get { return m_KeepLocalShadowVariants; } } public bool keepSoftShadowVariants { get { return m_KeepSoftShadowVariants; } } /// <summary> /// Lightweight-Default.mat /// </summary> /// <returns></returns> public override Material GetDefaultMaterial() { return GetMaterial(DefaultMaterialType.Standard); } /// <summary> /// Lightweight-DefaultParticle.mat /// </summary> /// <returns></returns> public override Material GetDefaultParticleMaterial() { return GetMaterial(DefaultMaterialType.Particle); } /// <summary> /// null /// </summary> /// <returns></returns> public override Material GetDefaultLineMaterial() { return GetMaterial(DefaultMaterialType.UnityBuiltinDefault); } /// <summary> /// Lightweight-DefaultTerrain.mat /// </summary> /// <returns></returns> public override Material GetDefaultTerrainMaterial() { return GetMaterial(DefaultMaterialType.Terrain); } /// <summary> /// null /// </summary> /// <returns></returns> public override Material GetDefaultUIMaterial() { return GetMaterial(DefaultMaterialType.UnityBuiltinDefault); } /// <summary> /// null /// </summary> /// <returns></returns> public override Material GetDefaultUIOverdrawMaterial() { return GetMaterial(DefaultMaterialType.UnityBuiltinDefault); } /// <summary> /// null /// </summary> /// <returns></returns> public override Material GetDefaultUIETC1SupportedMaterial() { return GetMaterial(DefaultMaterialType.UnityBuiltinDefault); } /// <summary> /// null /// </summary> /// <returns></returns> public override Material GetDefault2DMaterial() { return GetMaterial(DefaultMaterialType.UnityBuiltinDefault); } /// <summary> /// 获取默认shader /// LightweightShaderUtils内有静态数据 /// 实际字符串为"LightweightPipeline/Standard (Physically Based)" /// Lightweight-Default.mat也使用该Shader /// </summary> /// <returns></returns> public override Shader GetDefaultShader() { if (m_DefaultShader == null) m_DefaultShader = Shader.Find(LightweightShaderUtils.GetShaderPath(ShaderPathID.STANDARD_PBS)); return m_DefaultShader; } /// <summary> /// "Hidden/LightweightPipeline/BlitShader" /// </summary> public Shader blitShader { get { return resources != null ? resources.BlitShader : null; } } /// <summary> /// "Hidden/LightweightPipeline/CopyDepthShader" /// </summary> public Shader copyDepthShader { get { return resources != null ? resources.CopyDepthShader : null; } } /// <summary> /// "Hidden/LightweightPipeline/ScreenSpaceShadows" /// </summary> public Shader screenSpaceShadowShader { get { return resources != null ? resources.ScreenSpaceShadowShader : null; } } /// <summary> /// "Hidden/LightweightPipeline/SamplingShader" /// </summary> public Shader samplingShader { get { return resources != null ? resources.SamplingShader : null; } } public void OnBeforeSerialize() { } /// <summary> /// m_ShadowType已弃用,将旧版资源的m_ShadowType转为m_SoftShadowsSupported字段 /// </summary> public void OnAfterDeserialize() { if (k_AssetVersion < 3) { k_AssetVersion = 3; m_SoftShadowsSupported = (m_ShadowType == ShadowType.SOFT_SHADOWS); } } } }下面是两个上文引用到的,用来存储资源的脚本:
using UnityEngine; public class LightweightPipelineResources : ScriptableObject { public Shader BlitShader; public Shader CopyDepthShader; public Shader ScreenSpaceShadowShader; public Shader SamplingShader; }
using System; using UnityEngine; public class LightweightPipelineEditorResources : ScriptableObject { public Material DefaultMaterial; public Material DefaultParticleMaterial; public Material DefaultTerrainMaterial; }
渲染管线实例
渲染管线实例包含运行时逻辑、静态信息(RenderTexture、Buffer等),是渲染指令的设置处。
using System; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor.Experimental.Rendering.LightweightPipeline; #endif using UnityEngine.Rendering; using UnityEngine.Rendering.PostProcessing; using UnityEngine.XR; namespace UnityEngine.Experimental.Rendering.LightweightPipeline { public partial class LightweightPipeline : RenderPipeline { /// <summary> /// 渲染管线资源 /// </summary> public LightweightPipelineAsset pipelineAsset { get; private set; } /// <summary> /// 摄像机按深度排序 /// </summary> CameraComparer m_CameraComparer = new CameraComparer(); /// <summary> /// 前向渲染类 /// </summary> LightweightForwardRenderer m_Renderer; /// <summary> /// 剔除结果 /// </summary> CullResults m_CullResults; /// <summary> /// 非平行光源编号索引,用于阴影生成 /// </summary> List<int> m_LocalLightIndices = new List<int>(); /// <summary> /// 标记逐相机渲染进行过程 /// </summary> bool m_IsCameraRendering; public LightweightPipeline(LightweightPipelineAsset asset) { pipelineAsset = asset; SetSupportedRenderingFeatures(); SetPipelineCapabilities(asset); PerFrameBuffer._GlossyEnvironmentColor = Shader.PropertyToID("_GlossyEnvironmentColor"); PerFrameBuffer._SubtractiveShadowColor = Shader.PropertyToID("_SubtractiveShadowColor"); PerCameraBuffer._ScaledScreenParams = Shader.PropertyToID("_ScaledScreenParams"); m_Renderer = new LightweightForwardRenderer(asset); // Let engine know we have MSAA on for cases where we support MSAA backbuffer if (QualitySettings.antiAliasing != pipelineAsset.msaaSampleCount) QualitySettings.antiAliasing = pipelineAsset.msaaSampleCount; Shader.globalRenderPipeline = "LightweightPipeline"; m_IsCameraRendering = false; } public override void Dispose() { base.Dispose(); Shader.globalRenderPipeline = ""; SupportedRenderingFeatures.active = new SupportedRenderingFeatures(); #if UNITY_EDITOR SceneViewDrawMode.ResetDrawMode(); #endif m_Renderer.Dispose(); } public override void Render(ScriptableRenderContext context, Camera[] cameras) { if (m_IsCameraRendering) { Debug.LogWarning("Nested camera rendering is forbidden. If you are calling camera.Render inside OnWillRenderObject callback, use BeginCameraRender callback instead."); return; } //初始化每帧数据 base.Render(context, cameras); BeginFrameRendering(cameras); GraphicsSettings.lightsUseLinearIntensity = true; SetupPerFrameShaderConstants(); // Sort cameras array by camera depth Array.Sort(cameras, m_CameraComparer); foreach (Camera camera in cameras) { BeginCameraRendering(camera); string renderCameraTag = "Render " + camera.name; CommandBuffer cmd = CommandBufferPool.Get(renderCameraTag); //using 结束或中途中断会调用()中类的Dispose()方法 using (new ProfilingSample(cmd, renderCameraTag)) { //初始化逐相机的数据 CameraData cameraData; InitializeCameraData(camera, out cameraData); SetupPerCameraShaderConstants(cameraData); //剔除 ScriptableCullingParameters cullingParameters; if (!CullResults.GetCullingParameters(camera, cameraData.isStereoEnabled, out cullingParameters)) { CommandBufferPool.Release(cmd); continue; } cullingParameters.shadowDistance = Mathf.Min(cameraData.maxShadowDistance, camera.farClipPlane); //这里执行的new ProfilingSample(cmd, renderCameraTag)中设置的cmd.BeginSample(name);命令 context.ExecuteCommandBuffer(cmd); cmd.Clear(); #if UNITY_EDITOR try #endif { m_IsCameraRendering = true; #if UNITY_EDITOR // Emit scene view UI if (cameraData.isSceneViewCamera) ScriptableRenderContext.EmitWorldGeometryForSceneView(camera); #endif //剔除结果 CullResults.Cull(ref cullingParameters, context, ref m_CullResults); List<VisibleLight> visibleLights = m_CullResults.visibleLights; RenderingData renderingData; InitializeRenderingData(ref cameraData, visibleLights, m_Renderer.maxSupportedLocalLightsPerPass, m_Renderer.maxSupportedVertexLights, out renderingData); m_Renderer.Setup(ref context, ref m_CullResults, ref renderingData); m_Renderer.Execute(ref context, ref m_CullResults, ref renderingData); } #if UNITY_EDITOR catch (Exception) { CommandBufferPool.Release(cmd); throw; } finally #endif { m_IsCameraRendering = false; } } //这里执行的ProfilingSample.Dispose()中设置的m_Cmd.EndSample(m_Name);命令 context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); context.Submit(); } } public static void RenderPostProcess(CommandBuffer cmd, PostProcessRenderContext context, ref CameraData cameraData, RenderTextureFormat colorFormat, RenderTargetIdentifier source, RenderTargetIdentifier dest, bool opaqueOnly) { context.Reset(); context.camera = cameraData.camera; context.source = source; context.sourceFormat = colorFormat; context.destination = dest; context.command = cmd; context.flip = cameraData.camera.targetTexture == null; if (opaqueOnly) cameraData.postProcessLayer.RenderOpaqueOnly(context); else cameraData.postProcessLayer.Render(context); } /// <summary> /// 设置支持的渲染特征,active是静态的 /// </summary> void SetSupportedRenderingFeatures() { #if UNITY_EDITOR SupportedRenderingFeatures.active = new SupportedRenderingFeatures() { //反射探针 reflectionProbeSupportFlags = SupportedRenderingFeatures.ReflectionProbeSupportFlags.None, //默认的光照贴图的混合烘焙模式 defaultMixedLightingMode = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive, //支持的光照贴图的混合烘焙模式 supportedMixedLightingModes = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive, //支持的光照贴图烘焙模式 supportedLightmapBakeTypes = LightmapBakeType.Baked | LightmapBakeType.Mixed, //支持的光照贴图类型 supportedLightmapsModes = LightmapsMode.CombinedDirectional | LightmapsMode.NonDirectional, //支持光照探针代理 rendererSupportsLightProbeProxyVolumes = false, //支持运动矢量 rendererSupportsMotionVectors = false, //接受阴影 rendererSupportsReceiveShadows = true, //反射探针 rendererSupportsReflectionProbes = true }; SceneViewDrawMode.SetupDrawMode(); #endif } /// <summary> /// 初始化摄像机数据 /// </summary> /// <param name="camera"></param> /// <param name="cameraData"></param> void InitializeCameraData(Camera camera, out CameraData cameraData) { //接近1的pipelineAsset.renderScale(或者XRSettings.eyeTextureResolutionScale),置为1 const float kRenderScaleThreshold = 0.05f; cameraData.camera = camera; bool msaaEnabled = camera.allowMSAA && pipelineAsset.msaaSampleCount > 1; if (msaaEnabled) cameraData.msaaSamples = (camera.targetTexture != null) ? camera.targetTexture.antiAliasing : pipelineAsset.msaaSampleCount; else cameraData.msaaSamples = 1; //场景相机 cameraData.isSceneViewCamera = camera.cameraType == CameraType.SceneView; //存在RT且不是场景相机 cameraData.isOffscreenRender = camera.targetTexture != null && !cameraData.isSceneViewCamera; cameraData.isStereoEnabled = IsStereoEnabled(camera); cameraData.isHdrEnabled = camera.allowHDR && pipelineAsset.supportsHDR; cameraData.postProcessLayer = camera.GetComponent<PostProcessLayer>(); cameraData.postProcessEnabled = cameraData.postProcessLayer != null && cameraData.postProcessLayer.isActiveAndEnabled; // PostProcess for VR is not working atm. Disable it for now. cameraData.postProcessEnabled &= !cameraData.isStereoEnabled; 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)); // Discard variations lesser than kRenderScaleThreshold. // Scale is only enabled for gameview. // In XR mode, grab renderScale from XRSettings instead of SRP asset for now. // This is just a temporary change pending full integration of XR with SRP if (camera.cameraType == CameraType.Game) { #if !UNITY_SWITCH if (cameraData.isStereoEnabled) { cameraData.renderScale = XRSettings.eyeTextureResolutionScale; } else #endif { cameraData.renderScale = pipelineAsset.renderScale; } } else { cameraData.renderScale = 1.0f; } cameraData.renderScale = (Mathf.Abs(1.0f - cameraData.renderScale) < kRenderScaleThreshold) ? 1.0f : cameraData.renderScale; cameraData.requiresDepthTexture = pipelineAsset.supportsCameraDepthTexture || cameraData.isSceneViewCamera; cameraData.requiresSoftParticles = pipelineAsset.supportsSoftParticles; cameraData.requiresOpaqueTexture = pipelineAsset.supportsCameraOpaqueTexture; cameraData.opaqueTextureDownsampling = pipelineAsset.opaqueDownsampling; bool anyShadowsEnabled = pipelineAsset.supportsDirectionalShadows || pipelineAsset.supportsLocalShadows; cameraData.maxShadowDistance = (anyShadowsEnabled) ? pipelineAsset.shadowDistance : 0.0f; //这是一个额外添加的脚本 LightweightAdditionalCameraData additionalCameraData = camera.gameObject.GetComponent<LightweightAdditionalCameraData>(); if (additionalCameraData != null) { cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f; cameraData.requiresDepthTexture &= additionalCameraData.requiresDepthTexture; cameraData.requiresOpaqueTexture &= additionalCameraData.requiresColorTexture; } else if (!cameraData.isSceneViewCamera && camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview) { cameraData.requiresDepthTexture = false; cameraData.requiresOpaqueTexture = false; } cameraData.requiresDepthTexture |= cameraData.postProcessEnabled; } /// <summary> /// 初始化渲染数据 /// </summary> /// <param name="cameraData"></param> /// <param name="visibleLights"></param> /// <param name="maxSupportedLocalLightsPerPass"></param> /// <param name="maxSupportedVertexLights"></param> /// <param name="renderingData"></param> void InitializeRenderingData(ref CameraData cameraData, List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out RenderingData renderingData) { //用于生成阴影的非平行光源 m_LocalLightIndices.Clear(); //有阴影投射平行光源 bool hasDirectionalShadowCastingLight = false; //有阴影投射非平行光源 bool hasLocalShadowCastingLight = false; //初始化阴影的相关变量 if (cameraData.maxShadowDistance > 0.0f) { for (int i = 0; i < visibleLights.Count; ++i) { Light light = visibleLights[i].light; bool castShadows = light != null && light.shadows != LightShadows.None; if (visibleLights[i].lightType == LightType.Directional) { hasDirectionalShadowCastingLight |= castShadows; } else { hasLocalShadowCastingLight |= castShadows; m_LocalLightIndices.Add(i); } } } renderingData.cameraData = cameraData; InitializeLightData(visibleLights, maxSupportedLocalLightsPerPass, maxSupportedVertexLights, out renderingData.lightData); InitializeShadowData(hasDirectionalShadowCastingLight, hasLocalShadowCastingLight, out renderingData.shadowData); renderingData.supportsDynamicBatching = pipelineAsset.supportsDynamicBatching; } /// <summary> /// 初始化阴影数据 /// </summary> /// <param name="hasDirectionalShadowCastingLight"></param> /// <param name="hasLocalShadowCastingLight"></param> /// <param name="shadowData"></param> void InitializeShadowData(bool hasDirectionalShadowCastingLight, bool hasLocalShadowCastingLight, out ShadowData shadowData) { // Until we can have keyword stripping forcing single cascade hard shadows on gles2 bool supportsScreenSpaceShadows = SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2; //渲染平行光阴影 shadowData.renderDirectionalShadows = pipelineAsset.supportsDirectionalShadows && hasDirectionalShadowCastingLight; // we resolve shadows in screenspace when cascades are enabled to save ALU as computing cascade index + shadowCoord on fragment is expensive shadowData.requiresScreenSpaceShadowResolve = shadowData.renderDirectionalShadows && supportsScreenSpaceShadows && pipelineAsset.cascadeCount > 1; //阴影级联 shadowData.directionalLightCascadeCount = (shadowData.requiresScreenSpaceShadowResolve) ? pipelineAsset.cascadeCount : 1; shadowData.directionalShadowAtlasWidth = pipelineAsset.directionalShadowAtlasResolution; shadowData.directionalShadowAtlasHeight = pipelineAsset.directionalShadowAtlasResolution; //阴影级联统一为Vector3表达 switch (shadowData.directionalLightCascadeCount) { case 1: shadowData.directionalLightCascades = new Vector3(1.0f, 0.0f, 0.0f); break; case 2: shadowData.directionalLightCascades = new Vector3(pipelineAsset.cascade2Split, 1.0f, 0.0f); break; default: shadowData.directionalLightCascades = pipelineAsset.cascade4Split; break; } //渲染非平行光阴影 shadowData.renderLocalShadows = pipelineAsset.supportsLocalShadows && hasLocalShadowCastingLight; shadowData.localShadowAtlasWidth = shadowData.localShadowAtlasHeight = pipelineAsset.localShadowAtlasResolution; shadowData.supportsSoftShadows = pipelineAsset.supportsSoftShadows; shadowData.bufferBitCount = 16; shadowData.renderedDirectionalShadowQuality = LightShadows.None; shadowData.renderedLocalShadowQuality = LightShadows.None; } /// <summary> /// 初始化光源数据 /// </summary> /// <param name="visibleLights"></param> /// <param name="maxSupportedLocalLightsPerPass"></param> /// <param name="maxSupportedVertexLights"></param> /// <param name="lightData"></param> void InitializeLightData(List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out LightData lightData) { //控制最大可见光源数量<=pipelineAsset.maxPixelLights int visibleLightsCount = Math.Min(visibleLights.Count, pipelineAsset.maxPixelLights); lightData.mainLightIndex = GetMainLight(visibleLights); // If we have a main light we don't shade it in the per-object light loop. We also remove it from the per-object cull list int mainLightPresent = (lightData.mainLightIndex >= 0) ? 1 : 0; //计算附加光数量,maxSupportedLocalLightsPerPasss是4或16 int additionalPixelLightsCount = Math.Min(visibleLightsCount - mainLightPresent, maxSupportedLocalLightsPerPass); int vertexLightCount = (pipelineAsset.supportsVertexLight) ? Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass) - additionalPixelLightsCount : 0; //计算逐顶点光数量,maxSupportedVertexLights是4 vertexLightCount = Math.Min(vertexLightCount, maxSupportedVertexLights); //附加光数量 lightData.pixelAdditionalLightsCount = additionalPixelLightsCount; //不支持顶点光时vertexLightCount是0,最后是additionalPixelLightsCount; //支持时最后是 Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass) lightData.totalAdditionalLightsCount = additionalPixelLightsCount + vertexLightCount; lightData.visibleLights = visibleLights; lightData.visibleLocalLightIndices = m_LocalLightIndices; } /// <summary> /// 查找第一个Direction光源,返回数组中编号,若无返回-1 /// </summary> /// <param name="visibleLights"></param> /// <returns></returns> // Main Light is always a directional light int GetMainLight(List<VisibleLight> visibleLights) { int totalVisibleLights = visibleLights.Count; if (totalVisibleLights == 0 || pipelineAsset.maxPixelLights == 0) return -1; for (int i = 0; i < totalVisibleLights; ++i) { VisibleLight currLight = visibleLights[i]; // Particle system lights have the light property as null. We sort lights so all particles lights // come last. Therefore, if first light is particle light then all lights are particle lights. // In this case we either have no main light or already found it. if (currLight.light == null) break; // In case no shadow light is present we will return the brightest directional light if (currLight.lightType == LightType.Directional) return i; } return -1; } /// <summary> /// 设置每帧的Shader常量_GlossyEnvironmentColor、_SubtractiveShadowColor /// </summary> void SetupPerFrameShaderConstants() { // When glossy reflections are OFF in the shader we set a constant color to use as indirect specular SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe; Color linearGlossyEnvColor = new Color(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity; Color glossyEnvColor = CoreUtils.ConvertLinearToActiveColorSpace(linearGlossyEnvColor); Shader.SetGlobalVector(PerFrameBuffer._GlossyEnvironmentColor, glossyEnvColor); // Used when subtractive mode is selected Shader.SetGlobalVector(PerFrameBuffer._SubtractiveShadowColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.subtractiveShadowColor)); } /// <summary> /// 设置每个相机的Shader常量_ScaledScreenParams /// </summary> /// <param name="cameraData"></param> void SetupPerCameraShaderConstants(CameraData cameraData) { float cameraWidth = (float)cameraData.camera.pixelWidth * cameraData.renderScale; float cameraHeight = (float)cameraData.camera.pixelWidth * cameraData.renderScale; Shader.SetGlobalVector(PerCameraBuffer._ScaledScreenParams, new Vector4(cameraWidth, cameraHeight, 1.0f + 1.0f / cameraWidth, 1.0f + 1.0f / cameraHeight)); } bool IsStereoEnabled(Camera camera) { #if !UNITY_SWITCH bool isSceneViewCamera = camera.cameraType == CameraType.SceneView; return XRSettings.isDeviceActive && !isSceneViewCamera && (camera.stereoTargetEye == StereoTargetEyeMask.Both); #else return false; #endif } } }
参考资料
[1] Unite 2017 | Unity可编程渲染管线剖析.阅读更多
- DirectX 11游戏编程学习笔记之6: 第5章The Rendering Pipeline(渲染管线)
- GPU工作原理,可编程渲染管线,图形流水线和GPU架构
- 用可编程渲染管线实现phone光照模型
- 可编程渲染管线与着色器语言
- unity之固定渲染管线
- 【小松教你手游开发】【面试必读(编程基础)】OpenGL ES 2.0渲染管线
- Unity&Shader基础篇-可编程GPU图形绘制管线
- 固定渲染管线与可编程渲染管线
- 【Unity】图形渲染优化、渲染管线优化、图形性能优化
- 将Shader嵌入Ogre(固定渲染管线到可编程渲染管线)
- 可编程图形渲染管线
- 固定渲染管线与可编程渲染管线
- 【《Real-Time Rendering 3rd》 提炼总结】(三) 第三章 GPU渲染管线与可编程着色器
- 固定渲染管线与可编程渲染管线的区别
- 【 4000 UnityShader】学习笔记 可编程渲染管线结构及语义
- [Unity Shader编程]渲染队列、ZWrite和ZTest
- Unity Render Pipeline 渲染管线(漫画)
- Unity的Gamma渲染管线和Linear管线的区别
- 将Shader嵌入Ogre(固定渲染管线到可编程渲染管线) .
- 【Unity学习笔记】——基础篇:渲染管线