Unity PostProcessing Stack v2源码整体结构梳理

Posted wolf96

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的结果

一个简单的整体结构图如下:

欢迎访问我的个人博客:wolf96的技术博客

------   by wolf96

以上是关于Unity PostProcessing Stack v2源码整体结构梳理的主要内容,如果未能解决你的问题,请参考以下文章

Unity 之 后处理实现界面灰度效果(PostProcessing实现 | Shader实现)

unity post processing的应用

unity post processing的应用

Unity PostProcessing Stack v2源码整体结构梳理

Unity 之 Post Processing后处理不同项目配置(URP项目配置)

Unity 之 Post Processing后处理不同项目配置(URP项目配置)