URP后处理框架
该章节对上一章节的屏幕后处理框架进行调整,以方便后续扩展其他的后处理效果。请务必将上一章节内容看懂,再来理解本章。这里还是以亮度饱和度对比度效果来作为案例。
代码部分修改
AdditionPostProcessRendererFeature
namespace UnityEngine.Rendering.Universal
{
/// <summary>
/// 可编程渲染功能
/// 必须要继承ScriptableRendererFeature抽象类,
/// 并且实现AddRenderPasses跟Create函数
/// </summary>
public class AdditionPostProcessRendererFeature : ScriptableRendererFeature
{
// 后处理Pass
AdditionPostProcessPass postPass;
// 保存Shader的对象引用
public AdditionalPostProcessData postData;
//在这里,您可以在渲染器中注入一个或多个渲染通道。
//每个摄像机设置一次渲染器时,将调用此方法。
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (postPass == null)
{
return;
}
// 设置调用后处理Pass
postPass.Setup(renderingData.cameraData.cameraTargetDescriptor, renderer.cameraColorTarget, renderer.cameraDepth, RenderTargetHandle.CameraTarget);
// 添加该Pass到渲染管线中
renderer.EnqueuePass(postPass);
}
// 对象初始化时会调用该函数
public override void Create()
{
postPass = new AdditionPostProcessPass(RenderPassEvent.AfterRenderingTransparents, postData);
}
}
}
在该类中,我们将Shader的获取交由一个新的对象AdditionalPostProcessData去保存。
AdditionalPostProcessData
-
AdditionalPostProcessData
using System; namespace UnityEngine.Rendering.Universal { /// <summary> /// 附加后处理数据 /// </summary> [Serializable] public class AdditionalPostProcessData : ScriptableObject { [Serializable] public sealed class Shaders { public Shader brightnessSaturationContrast; //在这里扩展后续其他后处理Shader引用 } public Shaders shaders; } }
-
AdditionalPostProcessDataEditor
#if UNITY_EDITOR using UnityEditor; #endif namespace UnityEngine.Rendering.Universal { public class AdditionalPostProcessDataEditor : ScriptableObject { #if UNITY_EDITOR [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812")] [MenuItem("Assets/Create/Rendering/Universal Render Pipeline/Additional Post-process Data", priority = CoreUtils.assetCreateMenuPriority3 + 1)] static void CreateAdditionalPostProcessData() { var instance = CreateInstance<AdditionalPostProcessData>(); AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(AdditionalPostProcessData).Name)); Selection.activeObject = instance; } #endif } }
我们需要将AdditionalPostProcessDataEditor类放入Editor文件夹中,然后就可以在工程面板右击创建我们的Shader引用对象。接下来就可以在AdditionalPostProcessData的Inspector面板中添加我们扩展的后处理Shader。
后续扩展需要我们在AdditionalPostProcessData类的Shaders子类中添加对应Shader的公开属性
AdditionalMaterialLibrary
namespace UnityEngine.Rendering.Universal
{
/// <summary>
/// 材质列表
/// </summary>
public class AdditionalMaterialLibrary
{
public readonly Material brightnessSaturationContrast;
// 这里扩展后处理材质属性
/// <summary>
/// 初始化时从配置文件中获取材质
/// </summary>
/// <param name="data"></param>
public AdditionalMaterialLibrary(AdditionalPostProcessData data)
{
brightnessSaturationContrast = Load(data.shaders.brightnessSaturationContrast);
// 这里扩展后处理材质的加载
}
Material Load(Shader shader)
{
if (shader == null)
{
Debug.LogErrorFormat($"丢失 shader. {GetType().DeclaringType.Name} 渲染通道将不会执行。检查渲染器资源中是否缺少引用。");
return null;
}
else if (!shader.isSupported)
{
return null;
}
return CoreUtils.CreateEngineMaterial(shader);
}
internal void Cleanup()
{
CoreUtils.Destroy(brightnessSaturationContrast);
}
}
}
该类用于我们从上一步创建的Shader引用对象中获取Shader和创建材质。后续要扩展后处理材质,需要在两步:
- 在属性部分添加对应材质的引用
- 在AdditionalMaterialLibrary方法中添加对应的材质加载方法
AdditionPostProcessPass
using UnityEngine.Experimental.Rendering;
namespace UnityEngine.Rendering.Universal
{
/// <summary>
/// 附加的后处理Pass
/// </summary>
public class AdditionPostProcessPass : ScriptableRenderPass
{
//标签名,用于续帧调试器中显示缓冲区名称
const string CommandBufferTag = "AdditionalPostProcessing Pass";
// 用于后处理的材质
Material m_BlitMaterial;
AdditionalMaterialLibrary m_Materials;
AdditionalPostProcessData m_Data;
// 主纹理信息
RenderTargetIdentifier m_Source;
// 深度信息
RenderTargetIdentifier m_Depth;
// 当前帧的渲染纹理描述
RenderTextureDescriptor m_Descriptor;
// 目标相机信息
RenderTargetHandle m_Destination;
// 临时的渲染目标
RenderTargetHandle m_TemporaryColorTexture01;
// 属性参数组件
BrightnessSaturationContrast m_BrightnessSaturationContrast;
/// 这里扩展后续的属性参数组件引用
public AdditionPostProcessPass(RenderPassEvent evt, AdditionalPostProcessData data, Material blitMaterial = null)
{
renderPassEvent = evt;
m_Data = data;
m_Materials = new AdditionalMaterialLibrary(data);
m_BlitMaterial = blitMaterial;
}
public void Setup(in RenderTextureDescriptor baseDescriptor, in RenderTargetIdentifier source, in RenderTargetIdentifier depth, in RenderTargetHandle destination)
{
m_Descriptor = baseDescriptor;
m_Source = source;
m_Depth = depth;
m_Destination = destination;
}
/// <summary>
/// URP会自动调用该执行方法
/// </summary>
/// <param name="context"></param>
/// <param name="renderingData"></param>
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
// 从Volume框架中获取所有堆栈
var stack = VolumeManager.instance.stack;
// 从堆栈中查找对应的属性参数组件
m_BrightnessSaturationContrast = stack.GetComponent<BrightnessSaturationContrast>();
/// 这里扩展后续的属性参数组件获取
// 从命令缓冲区池中获取一个带标签的渲染命令,该标签名可以在后续帧调试器中见到
var cmd = CommandBufferPool.Get(CommandBufferTag);
// 调用渲染函数
Render(cmd, ref renderingData);
// 执行命令缓冲区
context.ExecuteCommandBuffer(cmd);
// 释放命令缓存
CommandBufferPool.Release(cmd);
}
// 渲染
void Render(CommandBuffer cmd, ref RenderingData renderingData)
{
ref var cameraData = ref renderingData.cameraData;
bool m_IsStereo = renderingData.cameraData.isStereoEnabled;
bool isSceneViewCamera = cameraData.isSceneViewCamera;
// VolumeComponent是否开启,且非Scene视图摄像机
// 亮度、对比度、饱和度
if (m_BrightnessSaturationContrast.IsActive() && !isSceneViewCamera)
{
SetBrightnessSaturationContrast(cmd, m_Materials.brightnessSaturationContrast);
}
/// 这里扩展后续的后处理方法的开关校验
}
RenderTextureDescriptor GetStereoCompatibleDescriptor(int width, int height, int depthBufferBits = 0)
{
var desc = m_Descriptor;
desc.depthBufferBits = depthBufferBits;
desc.msaaSamples = 1;
desc.width = width;
desc.height = height;
return desc;
}
#region 处理材质渲染
// 亮度、饱和度、对比度渲染
void SetBrightnessSaturationContrast(CommandBuffer cmd, Material uberMaterial)
{
// 写入参数
uberMaterial.SetFloat("_Brightness", m_BrightnessSaturationContrast.brightness.value);
uberMaterial.SetFloat("_Saturation", m_BrightnessSaturationContrast.saturation.value);
uberMaterial.SetFloat("_Contrast", m_BrightnessSaturationContrast.contrast.value);
// 通过目标相机的渲染信息创建临时缓冲区
//RenderTextureDescriptor opaqueDesc = m_Descriptor;
//opaqueDesc.depthBufferBits = 0;
//cmd.GetTemporaryRT(m_TemporaryColorTexture01.id, opaqueDesc);
//or
int tw = m_Descriptor.width;
int th = m_Descriptor.height;
var desc = GetStereoCompatibleDescriptor(tw, th);
cmd.GetTemporaryRT(m_TemporaryColorTexture01.id, desc, FilterMode.Bilinear);
// 通过材质,将计算结果存入临时缓冲区
cmd.Blit(m_Source, m_TemporaryColorTexture01.Identifier(), uberMaterial);
// 再从临时缓冲区存入主纹理
cmd.Blit(m_TemporaryColorTexture01.Identifier(), m_Source);
// 释放临时RT
cmd.ReleaseTemporaryRT(m_TemporaryColorTexture01.id);
}
/// 这里扩展后处理对材质填充方法
#endregion
}
}
后续添加后处理效果需要在代码中四个部分进行扩展:
- 首先要在属性部分添加对对应属性参数组件类的引用属性
- 在Execute方法中添加从Volume框架中获取对应属性参数类的方法。
- 在Render方法中添加对后处理效果是否开启的校验
- 在后续
#region
代码块中添加我们对材质填充调用方法
属性参数组件
using System;
// 通用渲染管线程序集
namespace UnityEngine.Rendering.Universal
{
// 实例化类 添加到Volume组件菜单中
[Serializable, VolumeComponentMenu("Addition-Post-processing/BrightnessSaturationContrast")]
// 集成VolumeComponent组件和IPostProcessComponent接口,用以继承Volume框架
public class BrightnessSaturationContrast : VolumeComponent, IPostProcessComponent
{
[Tooltip("开关")]
public BoolParameter _Switch = new BoolParameter(false);
[Tooltip("亮度")]
public ClampedFloatParameter brightness = new ClampedFloatParameter(1f, 0, 3);
[Tooltip("饱和度")]
public ClampedFloatParameter saturation = new ClampedFloatParameter(1f, 0, 3);
[Tooltip("对比度")]
public ClampedFloatParameter contrast = new ClampedFloatParameter(1f, 0, 3);
// 实现接口
public bool IsActive() => _Switch.value;
public bool IsTileCompatible()
{
return false;
}
}
}
这一部分没有太大修改,后续扩展其他效果需要仿照写对应的属性参数类。
需要注意的是,原本对效果开关的校验是通过组件的active属性来判断的,但在实际运用中,我发现就算关闭了组件在面板上的开关,获取到的active属性也依旧是开启状态。所以为了准确开关,我在面板中添加了上面代码中的开关选项。
public bool IsActive()
{
return active;
}
Shader
Shader "URP/Brightness Saturation And Contrast"
{
Properties
{
// 基础纹理
_MainTex ("Base (RGB)", 2D) = "white" { }
// 亮度
_Brightness ("Brightness", Float) = 1
// 饱和度
_Saturation ("Saturation", Float) = 1
// 对比度
_Contrast ("Contrast", Float) = 1
}
SubShader
{
Tags { "RenderPipeline" = "UniversalPipeline" }
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
half _Brightness;
half _Saturation;
half _Contrast;
CBUFFER_END
ENDHLSL
Pass
{
// 开启深度测试 关闭剔除 关闭深度写入
ZTest Always Cull Off ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// 声明纹理
TEXTURE2D(_MainTex);
// 声明采样器
SAMPLER(sampler_MainTex);
struct a2v
{
float4 vertex: POSITION;
float4 texcoord: TEXCOORD0;
};
struct v2f
{
float4 pos: SV_POSITION;
half2 uv: TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = TransformObjectToHClip(v.vertex.xyz);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
half4 frag(v2f i): SV_Target
{
// 纹理采样
half4 renderTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
// 调整亮度 = 原颜色 * 亮度值
half3 finalColor = renderTex.rgb * _Brightness;
// 调整饱和度
// 亮度值(饱和度为0的颜色) = 每个颜色分量 * 特定系数
half luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
half3 luminanceColor = half3(luminance, luminance, luminance);
// 插值亮度值和原图
finalColor = lerp(luminanceColor, finalColor, _Saturation);
// 调整对比度
// 对比度为0的颜色
half3 avgColor = half3(0.5, 0.5, 0.5);
finalColor = lerp(avgColor, finalColor, _Contrast);
return half4(finalColor, renderTex.a);
}
ENDHLSL
}
}
Fallback Off
}
这部分我就真没什么好说的了,要什么效果写什么Shader,记得拖到AdditionalPostProcessData对象中就行。
实操部分
-
在工程面板中右键创建我们的Shader引用对象(不知道在哪个位置,看上节的截图),然后拖动赋值对应Shader
-
在URP管线配置对象中添加我们的扩展功能组件,然后再组件中赋值上一步创建Shader引用对象
-
在场景中田间全局Volume组件,然后再组件中新建一个Profile对象,接下来添加对应效果的属性参数组件,最后开启后处理效果的开关并调整参数