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

postprocessing stack v2Unity 引擎中的一个功能强大的后处理效果工具。通过使用 postprocessing stack v2,你可以为你的游戏添加各种视觉上的增强效果,如颜色校正、模糊、抗锯齿,以及其他的图像效果。 在使用 postprocessing stack v2 之前,你首先需要将其导入到你的项目中。你可以在 Unity 的 Asset Store 或 Unity Package Manager 中找到并下载它。安装完成后,你可以在 Unity 的菜单栏中找到 "Window" -> "Package Manager",然后在打开的窗口中找到 "Postprocessing",点击导入按钮即可将其添加到你的项目中。 导入完成后,你可以在 Unity 的 "Hierarchy" 面板中创建一个空的游戏对象,并将 "Post Process Volume" 组件添加到该对象上。这个组件将决定哪些相机将受到后处理的影响。在"Post Process Volume"的属性面板中,你可以选择所需的配置文件。配置文件中包含了各种后处理效果的设置,如颜色校正、曝光度、对比度和抗锯齿等。 在配置文件中,你可以为不同的相机创建不同的配置文件。这样,你可以为不同的相机定制不同的后处理效果,并且你可以在运行时切换不同的配置文件。 如果你想为整个场景应用一个全局的后处理效果,你可以在场景中添加一个全局的 "Post Process Layer" 组件。这个组件将为场景中的所有相机应用相同的后处理效果。 postprocessing stack v2 还提供了一些其他的功能,如在屏幕上显示调试信息、自定义效果,并且还支持自定义脚本来创建独特的效果。 总的来说,postprocessing stack v2 是一个非常方便的工具,通过它可以轻松为你的游戏添加各种视觉上的增强效果,提升游戏的质量和吸引力,使其更加引人注目。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值