【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可編程渲染管線剖析.

[2] RenderPipeLine.

[3] 詳解可編程腳本渲染管線SRP.

[4] 輕量級渲染管線:優化實時性.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章