Unity PostProcessing Stack v2整體結構梳理
整體渲染邏輯在PostProcessLayer.cs中
把幾個後期渲染順序暴露給開發者,後期渲染順序:不透明物體之後->棧前->棧中->棧後->最後的pass(最後的pass跳過輸出HDR)
不透明物體之後在RenderOpaqueOnly()方法中進行
其餘的順序都在Render()方法中進行
棧中方法為RenderBuiltins()
最後的Pass方法為RenderFinalPass()
可以通過設定屬性的方式進行分層,PostProcessLayer.cs中有一個排序後的字典,是通過渲染順序排序的
public Dictionary<PostProcessEvent, List<SerializedBundleRef>> sortedBundles { get; private set; }
// Push all sorted lists in a dictionary for easier access sortedBundles = new Dictionary<PostProcessEvent, List<SerializedBundleRef>>(new PostProcessEventComparer()) { { PostProcessEvent.BeforeTransparent, m_BeforeTransparentBundles }, { PostProcessEvent.BeforeStack,m_BeforeStackBundles }, { PostProcessEvent.AfterStack,m_AfterStackBundles } };
比如在不透明之後,在這個字典裡找不透明後的部分進行RenderList()
// Renders before-transparent effects. // Make sure you check `HasOpaqueOnlyEffects()` before calling this method as it won't // automatically blit source into destination if no opaque effects are active. public void RenderOpaqueOnly(PostProcessRenderContext context) { if (RuntimeUtilities.scriptableRenderPipelineActive) SetupContext(context); TextureLerper.instance.BeginFrame(context); // Update & override layer settings first (volume blending), will only be done once per // frame, either here or in Render() if there isn't any opaque-only effect to render. UpdateSettingsIfNeeded(context); RenderList(sortedBundles[PostProcessEvent.BeforeTransparent], context, "OpaqueOnly"); }
列舉引數
//3層,半透明前,棧前,棧後 public enum PostProcessEvent { BeforeTransparent = 0, BeforeStack = 1, AfterStack = 2, }
這個屬性在PostProcessAttribute.cs中設定
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public sealed class PostProcessAttribute : Attribute { public readonly Type renderer; public readonly PostProcessEvent eventType; public readonly string menuItem; public readonly bool allowInSceneView; internal readonly bool builtinEffect; public PostProcessAttribute(Type renderer, PostProcessEvent eventType, string menuItem, bool allowInSceneView = true) { this.renderer = renderer; this.eventType = eventType; this.menuItem = menuItem; this.allowInSceneView = allowInSceneView; builtinEffect = false; } internal PostProcessAttribute(Type renderer, string menuItem, bool allowInSceneView = true) { this.renderer = renderer; this.menuItem = menuItem; this.allowInSceneView = allowInSceneView; builtinEffect = true; } }
每個後處理類都可以設定這個屬性
[Serializable] [PostProcess(typeof(BloomRenderer), "Unity/Bloom")] public sealed class Bloom : PostProcessEffectSettings {
[Serializable] [PostProcess(typeof(BloomRenderer),PostProcessEvent.BeforeTransparent ,"Unity/Bloom")] public sealed class Bloom : PostProcessEffectSettings {
整個stack順序由一個PostProcessRenderContext型別的引數串下來,關於PostProcessRenderContext這個型別,他是後處理渲染的上下文裡面包含相機資訊,CommandBuffer,RenderTexture資訊(格式,寬高等等),PostProcessResources,PropertySheetFactory等等,
每個渲染處理的操作都會加到 CommandBuffer 這個命令緩衝中
每個後處理效果內部都維護一個材質球
這個材質球在PropertySheet中儲存,PropertySheet裡面還有引數資訊,每個後處理的Render()中都會先獲取這個sheet,向這個材質球傳入引數,
PropertySheet是通過PropertySheetFactory這個工廠獲得,他內部維護一個字典作為查詢方法
readonly Dictionary<Shader, PropertySheet> m_Sheets;
將shader作為鍵,這就說明一個shader只能有一個材質球
再看看他內部的get方法
public PropertySheet Get(string shaderName) { var shader = Shader.Find(shaderName); if (shader == null) throw new ArgumentException(string.Format("Invalid shader ({0})", shaderName)); return Get(shader); } public PropertySheet Get(Shader shader) { PropertySheet sheet; if (shader == null) throw new ArgumentException(string.Format("Invalid shader ({0})", shader)); if (m_Sheets.TryGetValue(shader, out sheet)) return sheet; var shaderName = shader.name; var material = new Material(shader) { name = string.Format("PostProcess - {0}", shaderName.Substring(shaderName.LastIndexOf('/') + 1)), hideFlags = HideFlags.DontSave }; sheet = new PropertySheet(material); m_Sheets.Add(shader, sheet); return sheet; }
可以通過shader或者shader名來獲取sheet,如果字典裡沒有(第一次建立),就新建一個sheet和一個材質球,在bloom.cs的Render()中是這樣獲取sheet的,
var sheet = context.propertySheets.Get(context.resources.shaders.bloom);
這個Get方法其實是通過名字反射到這個shader的位置的,每個shader的名字都為Shader “PostProcessing/….”,在PropertySheetFactory.cs中
public PropertySheet Get(Shader shader) { PropertySheet sheet; if (shader == null) throw new ArgumentException(string.Format("Invalid shader ({0})", shader)); if (m_Sheets.TryGetValue(shader, out sheet)) return sheet; var shaderName = shader.name; var material = new Material(shader) { name = string.Format("PostProcess - {0}", shaderName.Substring(shaderName.LastIndexOf('/') + 1)), hideFlags = HideFlags.DontSave }; sheet = new PropertySheet(material); m_Sheets.Add(shader, sheet); return sheet; }
接下來就把引數傳入sheet,其實就是賦值給材質球
// Apply auto exposure adjustment in the prefiltering pass sheet.properties.SetTexture(ShaderIDs.AutoExposureTex, context.autoExposureTexture); // Negative anamorphic ratio values distort vertically - positive is horizontal float ratio = Mathf.Clamp(settings.anamorphicRatio, -1, 1); float rw = ratio < 0 ? -ratio : 0f; float rh = ratio > 0 ?ratio : 0f; // Do bloom on a half-res buffer, full-res doesn't bring much and kills performances on // fillrate limited platforms int tw = Mathf.FloorToInt(context.screenWidth / (2f - rw)); int th = Mathf.FloorToInt(context.screenHeight / (2f - rh)); // Determine the iteration count int s = Mathf.Max(tw, th); float logs = Mathf.Log(s, 2f) + Mathf.Min(settings.diffusion.value, 10f) - 10f; int logs_i = Mathf.FloorToInt(logs); int iterations = Mathf.Clamp(logs_i, 1, k_MaxPyramidSize); float sampleScale = 0.5f + logs - logs_i; sheet.properties.SetFloat(ShaderIDs.SampleScale, sampleScale); // Prefiltering parameters float lthresh = Mathf.GammaToLinearSpace(settings.threshold.value); float knee = lthresh * settings.softKnee.value + 1e-5f; var threshold = new Vector4(lthresh, lthresh - knee, knee * 2f, 0.25f / knee); sheet.properties.SetVector(ShaderIDs.Threshold, threshold); float lclamp = Mathf.GammaToLinearSpace(settings.clamp.value); sheet.properties.SetVector(ShaderIDs.Params, new Vector4(lclamp, 0f, 0f, 0f));
回到PostProcessLayer,每個渲染順序中都會呼叫相應順序後處理的Render()方法,在這些順序中Unity自帶的這些後處理,AO、SSR、fog是在不透明之後的這層,其餘的都是在built in 在棧中處理RenderBuiltins()
每層渲染順序執行的最後都要把最後結果渲染到螢幕上一次用的是這個方法
BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, bool clear = false)
這個方法內部是一個DrawMesh的實現
public static void BlitFullscreenTriangle(this CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, PropertySheet propertySheet, int pass, RenderBufferLoadAction loadAction) { cmd.SetGlobalTexture(ShaderIDs.MainTex, source); bool clear = (loadAction == RenderBufferLoadAction.Clear); cmd.SetRenderTargetWithLoadStoreAction(destination, clear ? RenderBufferLoadAction.DontCare : loadAction, RenderBufferStoreAction.Store); if (clear) cmd.ClearRenderTarget(true, true, Color.clear); cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties); }
在Frame Debug裡面可以看到這次DrawMesh的結果
一個簡單的整體結構圖如下: