Unity URP14.0 自定义后处理框架

目录

  • 碎碎念
  • 一些基础
  • CustomPostProcessing.cs
  • CustomPostProcessingFeature.cs
  • CustomPostProcessingPass.cs
  • 例子:BSC
    • 后处理shader(BSC)
    • 后处理cs脚本(BSC)
  • 例子:ColorBlit
    • PostProcessing.hlsl
    • ColorBlit2.shader
    • ColorBlit.cs文件
  • 其他一些参考

碎碎念

额,最近在看一些关于URP的东西,挺难入门的这个东西,因为本身版本就迭代得非常快,一些代码通常你才刚接触到就已经弃用了,就很尴尬。但是新的api教程又少(微笑.jpg)。

这次得自定义后处理框架也是知乎的大神放出来的,我这边放出的代码就是简单当成我个人的学习记录,还是有一些不懂的地方,放出链接:Unity URP14.0 自定义后处理系统。

  • 这个教程应该适合刚入门的新手,但是又得对URP和Render Feature有点了解的人。
  • 给出的代码大部分都有注释。

一些基础

这个自定义框架总体分成五个部分,其实应该说是四个部分才对,但是作者把他拆成了四个部分。
分别是:

  • 后处理基类CustomPostProcessing.cs;
  • 我们自定义的Render Feature——CustomPostProcessingFeature.cs;
  • 还有Render Pass——CustomPostProcessingPass.cs。
  • 我们的后处理效果shader。
  • 最后就是我们自定义的,继承于CustomPostProcessing.cs的具体后处理类。这个类就是我们自由发挥了,通常需要跟我们的shader联系起来。

如果大家之前自己写过简单的render feature的话,通常从unity面板上创建的Render Feature都会自动创建一个CustomRenderPass,也就是类中类,但是这位大神把它们拆开了。

先来了解一下Render Feature的简单框架吧(里面有些api在urp14里是弃用的):

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;public class MyFirstRenderFeatureTest : ScriptableRendererFeature
{   // static Material blitMaterial = new Material(blitShader);/// <summary>/// 这个类是Render Feature中的主要组成部分,也就是一个render pass/// </summary> <summary>/// /// </summary>class CustomRenderPass : ScriptableRenderPass{//找到场景中的shader中的texture,并获取该贴图的idstatic string rt_name = "_ExampleRT";static int rt_ID = Shader.PropertyToID(rt_name);static string blitShader_Name = "URP/BlitShader";static Shader blitShader = Shader.Find(blitShader_Name);static Material blitMat = new Material(blitShader);/// <summary>/// 帮助Excete() 提前准备它需要的RenderTexture或者其他变量/// </summary>/// <param name="cmd"></param>/// <param name="renderingData"></param>public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData){// 存储render texture一些格式标准的数据结构RenderTextureDescriptor descriptor = new RenderTextureDescriptor(1920, 1080,RenderTextureFormat.Default, 0);// 然后创建一个临时的render texture的缓存/空间cmd.GetTemporaryRT(rt_ID, descriptor);// 想画其他东西到rt上的话就需要下面这句ConfigureTarget(rt_ID);ConfigureClear(ClearFlag.Color, Color.black);}/// <summary>/// 实现这个render pass做什么事情/// </summary>/// <param name="context"></param>/// <param name="renderingData"></param>public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData){// 到命令缓存池中get一个CommandBuffer cmd = CommandBufferPool.Get("tmpCmd");cmd.Blit(renderingData.cameraData.renderer.cameraColorTarget, rt_ID, blitMat);       //添加一个命令:将像素数据从A复制到Bcontext.ExecuteCommandBuffer(cmd);      //因为是自己创建的cmd,所以需要手动地将renderingData提交到context里去cmd.Clear();cmd.Release();}/// <summary>/// 释放在OnCameraSetup() 里声明的变量,尤其是Temporary Render Texture/// </summary>/// <param name="cmd"></param>public override void OnCameraCleanup(CommandBuffer cmd){cmd.ReleaseTemporaryRT(rt_ID);}}/// <summary>/// 声明一个render pass的变量/// </summary>CustomRenderPass m_ScriptablePass;/// <summary>/// 这个方法是render feature中用来给上面声明的render pass赋值,并决定这个render pass什么使用会被调用(不一定每帧都被执行)/// </summary>public override void Create(){m_ScriptablePass = new CustomRenderPass();// renderPassEvent定义什么时候去执行m_ScriptablePass 这个render passm_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;}/// <summary>/// 将create函数里实例化的render pass加入到渲染管线中(每帧都执行)/// </summary>/// <param name="renderer"></param>/// <param name="renderingData"></param>public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData){renderer.EnqueuePass(m_ScriptablePass);}
}

可以看到这个脚本的最外层是MyFirstRenderFeatureTest类,继承于ScriptableRendererFeature类。

  • MyFirstRenderFeatureTest类包含一个CustomRenderPass类和该pass的变量。
    • CustomRenderPass类则继承于ScriptableRenderPass类,他有OnCameraSetup、Execute、OnCameraCleanup三个抽象方法,这在pass类中必须实现,具体解释看代码。
  • 包含Create方法,这个方法是render feature中用来给声明的render pass赋值,并决定这个render pass什么使用会被调用(不一定每帧都被执行)。
  • 包含AddRenderPasses方法,这个方法将create函数里实例化的render pass加入到渲染管线中(每帧都执行)。

CustomPostProcessing.cs

是所有我们自定义后处理的基类,也就说如果你要创建自己的后处理cs脚本,就需要继承这个脚本。

因为默认的unity后处理是不支持拓展的,但是我们这里这个CustomPostProcessing.cs就为了能实现拓展做了一些操作,也就是继承VolumeComponent类和IPostProcessComponent类,这两个类能将我们自己写的后处理添加到unity的那个全局后处理上的基础。额,当然,这只是第一步,还没那么快呢。
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;//后处理效果的注入点,这里先分四个
public enum CustomPostProcessingInjectionPoint{AfterOpaque,AfterSkybox,BeforePostProcess,AfterPostProcess
}//这个类其实是自定义后处理的基类,应该改名为 MyVolumePostProcessing 比较合适
//自定义后处理的基类 (由于渲染时会生成临时RT,所以还需要继承IDisposable)
public abstract class CustomPostProcessing : VolumeComponent, IPostProcessComponent, IDisposable
{//注入点public virtual CustomPostProcessingInjectionPoint InjectionPoint => CustomPostProcessingInjectionPoint.AfterPostProcess;//在注入点的顺序public virtual int OrderInInjectionPoint => 0;    #region IPostProcessComponent//用来返回当前后处理是否activepublic abstract bool IsActive();//不知道用来干嘛的,但Bloom.cs里get值false,抄下来就行了public virtual bool IsTileCompatible() => false;//配置当前后处理public abstract void Setup();// 当相机初始化时执行(自己取的函数名,跟renderfeature里的OnCameraSetup没什么关系其实)public virtual void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData){}#endregion    //执行渲染public abstract void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination);#region IDisposablepublic void Dispose(){Dispose(true);GC.SuppressFinalize(this);}public virtual void Dispose(bool disposing){}#endregion    
}

在这个类里我们需要定义一些属性和方法,这是为了我们后面的后处理做准备。

上面还定义了一个枚举类:CustomPostProcessingInjectionPoint。这个枚举是简单的列出我们想要在渲染管线的哪个阶段插入我们的后处理,这里就先简单地列了四个阶段。

这个注入点的顺序我其实还没太明白。

CustomPostProcessingFeature.cs

这个脚本就是我们心心念念的Render Feature。主要的作用是创建并初始化render pass。

这里我们定义了一个列表mCustomPostProcessings,这个列表是存我们所有的后处理类的实例的列表,我们之后要得到这些实例里指定注入点的实例,比如说我们要得到AfterOpaque, AfterSkybox, BeforePostProcess, AfterPostProcess这四个注入点的AfterOpaque注入点的实例。然后我们就能为它创建pass。

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;//自定义Render Feature,实现后会自动在RenderFeature面板上可供添加
public class CustomPostProcessingFeature : ScriptableRendererFeature
{    private CustomPostProcessingPass mAfterOpaquePass;private CustomPostProcessingPass mAfterSkyboxPass;private CustomPostProcessingPass mBeforePostProcessPass;private CustomPostProcessingPass mAfterPostProcessPass;//所有后处理基类列表private List<CustomPostProcessing> mCustomPostProcessings;    //最重要的方法,用来生成RenderPass//获取所有CustomPostProcessing实例,并且根据插入点顺序,放入到对应Render Pass中,并且指定Pass Eventpublic override void Create(){//获取VolumeStackvar stack = VolumeManager.instance.stack;//获取所有的CustomPostProcessing实例mCustomPostProcessings = VolumeManager.instance.baseComponentTypeArray.Where(t => t.IsSubclassOf(typeof(CustomPostProcessing)))  //筛选出VolumeComponent派生类类型中所有的CustomPostProcessing类型元素,不论是否在Volume中,不论是否激活.Select(t => stack.GetComponent(t) as CustomPostProcessing) //将类型元素转化为实例.ToList();  //转化为List#region 初始化不同插入点的render pass#region 初始化在不透明物体渲染之后的pass//找到在不透明物后渲染的CustomPostProcessingvar afterOpaqueCPPs = mCustomPostProcessings.Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.AfterOpaque)   // 筛选出所有CustomPostProcessing类中注入点为透明物体和天空后的实例.OrderBy(c => c.OrderInInjectionPoint)  //按顺序排序.ToList();  //转化为List// 创建CustomPostProcessingPass类mAfterOpaquePass = new CustomPostProcessingPass("Custom Post-Process after Opaque", afterOpaqueCPPs);//设置pass执行时间mAfterOpaquePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;#endregion#region 初始化在透明物体和天空渲染后的passvar afterTransAndSkyboxCPPs = mCustomPostProcessings.Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.AfterSkybox).OrderBy(c => c.OrderInInjectionPoint).ToList();mAfterSkyboxPass = new CustomPostProcessingPass("Custom Post-Process after transparent and skybox", afterTransAndSkyboxCPPs);mAfterSkyboxPass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox;#endregion#region 初始化在后处理效果渲染之前的passvar beforePostProcessCPPs = mCustomPostProcessings.Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.BeforePostProcess).OrderBy(c => c.OrderInInjectionPoint).ToList();mBeforePostProcessPass = new CustomPostProcessingPass("Custom Post-Process before PostProcess", beforePostProcessCPPs);mBeforePostProcessPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;#endregion#region 初始化在后处理效果渲染之后的passvar afterPostProcessCPPs = mCustomPostProcessings.Where(c => c.InjectionPoint == CustomPostProcessingInjectionPoint.AfterPostProcess).OrderBy(c => c.OrderInInjectionPoint).ToList();mAfterPostProcessPass = new CustomPostProcessingPass("Custom Post-Process after PostProcess", afterPostProcessCPPs);mAfterPostProcessPass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;#endregion#endregion}// 当为每个摄像机设置一个渲染器时,调用此方法// 将不同注入点的RenderPass注入到renderer中(添加Pass到渲染队列)//网上有些资料在这个函数里配置RenderPass的源RT和目标RT,具体来说使用类似RenderPass.Setup(renderer.cameraColorTargetHandle, renderer.cameraColorTargetHandle)的方式.//但是这在URP14.0中会报错,提示renderer.cameraColorTargetHandle只能在ScriptableRenderPass子类里调用。具体细节可以查看最后的参考连接。public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData){//当前渲染的游戏相机支持后处理if(renderingData.cameraData.postProcessEnabled){//为每个render pass设置RT//并且将pass列表加到renderer中if(mAfterOpaquePass.SetupCustomPostProcessing()){mAfterOpaquePass.ConfigureInput(ScriptableRenderPassInput.Color);renderer.EnqueuePass(mAfterOpaquePass);}if(mAfterSkyboxPass.SetupCustomPostProcessing()){mAfterSkyboxPass.ConfigureInput(ScriptableRenderPassInput.Color);renderer.EnqueuePass(mAfterSkyboxPass);}if(mBeforePostProcessPass.SetupCustomPostProcessing()){mBeforePostProcessPass.ConfigureInput(ScriptableRenderPassInput.Color);renderer.EnqueuePass(mBeforePostProcessPass);}if(mAfterPostProcessPass.SetupCustomPostProcessing()){mAfterPostProcessPass.ConfigureInput(ScriptableRenderPassInput.Color);renderer.EnqueuePass(mAfterPostProcessPass);}}}protected override void Dispose(bool disposing){base.Dispose(disposing);//mAfterSkyboxPass.Dispose();//mBeforePostProcessPass.Dispose();//mAfterPostProcessPass.Dispose();if(disposing && mCustomPostProcessings != null){foreach(var item in mCustomPostProcessings){item.Dispose();}}}
}

CustomPostProcessingPass.cs

最后就是我们的render pass类。它同样也需要创建一个自定义后处理的列表。为了我们后面获取到当前已激活的后处理。根据我们当前已激活的后处理组件的数量,我们就能决定我们需要添加的pass数量。
比如下图框出来的两个阶段就是我们自己后处理加上的:
在这里插入图片描述

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;public class CustomPostProcessingPass : ScriptableRenderPass
{        //所有自定义后处理基类 列表private List<CustomPostProcessing> mCustomPostProcessings;//当前active组件下标private List<int> mActiveCustomPostProcessingIndex;//每个组件对应的ProfilingSampler,就是frameDebug上显示的private string mProfilerTag;private List<ProfilingSampler> mProfilingSamplers;//声明private RTHandle mSourceRT;private RTHandle mDesRT;private RTHandle mTempRT0;private RTHandle mTempRT1;private string mTempRT0Name => "_TemporaryRenderTexture0";private string mTempRT1Name => "_TemporaryRenderTexture1";//Pass的构造方法,参数都由Feature传入public CustomPostProcessingPass(string profilerTag, List<CustomPostProcessing> customPostProcessings){mProfilerTag = profilerTag;     //这个profilerTag就是在frame dbugger中我们自己额外创建的渲染通道的名字        mCustomPostProcessings = customPostProcessings;mActiveCustomPostProcessingIndex = new List<int>(customPostProcessings.Count);//将自定义后处理对象列表转换成一个性能采样器对象列表mProfilingSamplers = customPostProcessings.Select(c => new ProfilingSampler(c.ToString())).ToList();//在URP14.0(或者在这之前)中,抛弃了原有RenderTargetHandle,而通通使用RTHandle。原来的Init也变成了RTHandles.Alloc//mTempRT0 = RTHandles.Alloc(mTempRT0Name, name:mTempRT0Name);//mTempRT1 = RTHandles.Alloc(mTempRT1Name, name:mTempRT1Name);}// 相机初始化时执行public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData){var descriptor = renderingData.cameraData.cameraTargetDescriptor;descriptor.msaaSamples = 1;descriptor.depthBufferBits = 0;//分配临时纹理  TODO:还有疑问,关于_CameraColorAttachmentA和_CameraColorAttachmentBRenderingUtils.ReAllocateIfNeeded(ref mTempRT0, descriptor, name:mTempRT0Name);RenderingUtils.ReAllocateIfNeeded(ref mTempRT1, descriptor, name:mTempRT1Name);foreach(var i in mActiveCustomPostProcessingIndex){mCustomPostProcessings[i].OnCameraSetup(cmd, ref renderingData);}}          //获取active的CPPs下标,并返回是否存在有效组件public bool SetupCustomPostProcessing(){mActiveCustomPostProcessingIndex.Clear();  //mActiveCustomPostProcessingIndex的数量和mCustomPostProcessings.Count是相等的for(int i = 0; i < mCustomPostProcessings.Count; i++){mCustomPostProcessings[i].Setup();if(mCustomPostProcessings[i].IsActive()){mActiveCustomPostProcessingIndex.Add(i);}}return (mActiveCustomPostProcessingIndex.Count != 0);}      //实现渲染逻辑public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData){//初始化CommandBufferCommandBuffer cmd = CommandBufferPool.Get(mProfilerTag);context.ExecuteCommandBuffer(cmd);cmd.Clear();//获取相机Descriptorvar descriptor = renderingData.cameraData.cameraTargetDescriptor;descriptor.msaaSamples = 1;descriptor.depthBufferBits = 0;//初始化临时RTbool rt1Used = false;        //1.声明temp0临时纹理//cmd.GetTemporaryRT(Shader.PropertyToID(mTempRT0.name), descriptor);// mTempRT0 = RTHandles.Alloc(mTempRT0.name);RenderingUtils.ReAllocateIfNeeded(ref mTempRT0, descriptor, name:mTempRT0Name);//2.设置源和目标RT为本次渲染的RT,在Execute里进行,特殊处理 后处理 后注入点mDesRT = renderingData.cameraData.renderer.cameraColorTargetHandle;mSourceRT = renderingData.cameraData.renderer.cameraColorTargetHandle;//执行每个组件的Render方法//3.如果只有一个后处理效果,则直接将这个后处理效果从mSourceRT渲染到mTempRT0(临时纹理)if(mActiveCustomPostProcessingIndex.Count == 1){   int index = mActiveCustomPostProcessingIndex[0];using(new ProfilingScope(cmd, mProfilingSamplers[index])){mCustomPostProcessings[index].Render(cmd, ref renderingData,mSourceRT, mTempRT0);}}else{//如果有多个组件,则在两个RT上来回blit。由于每次循环结束交换它们,所以最终纹理依然存在mTempRT0RenderingUtils.ReAllocateIfNeeded(ref mTempRT1, descriptor, name:mTempRT1Name);//Blitter.BlitCameraTexture(cmd, mSourceRT, mTempRT0);rt1Used = true;Blit(cmd, mSourceRT, mTempRT0);for(int i = 0; i < mActiveCustomPostProcessingIndex.Count; i++){int index = mActiveCustomPostProcessingIndex[i];var customProcessing = mCustomPostProcessings[index];using(new ProfilingScope(cmd, mProfilingSamplers[index])){customProcessing.Render(cmd, ref renderingData, mTempRT0, mTempRT1);}CoreUtils.Swap(ref mTempRT0, ref mTempRT1);}}Blitter.BlitCameraTexture(cmd,mTempRT0, mDesRT);//释放cmd.ReleaseTemporaryRT(Shader.PropertyToID(mTempRT0.name));if(rt1Used){cmd.ReleaseTemporaryRT(Shader.PropertyToID(mTempRT1.name));}context.ExecuteCommandBuffer(cmd);CommandBufferPool.Release(cmd);}//相机清除时执行public override void OnCameraCleanup(CommandBuffer cmd){mDesRT = null;mSourceRT = null;}    public void Dispose(){mTempRT0?.Release();mTempRT1?.Release();}
}

render pass主要也是分成几个部分:

  • 构造函数,构造pass,参数都由之前的render feature传入。
  • OnCameraSetup函数,相机初始化时执行的函数,我们在这个函数里初始化一些东西。这在那里面打的一个注释TODO关于_CameraColorAttachmentA和_CameraColorAttachmentB,其实是原来的那位大大说如果我们把自定义后处理的注入点设为AfterPostProcessing之后,就会多出来一个Final Blit阶段,这个阶段的输入源RT为_CameraColorAttachmentB而不是_CameraColorAttachmentA。具体怎么操作的我没太明白。
  • Execute函数,实现渲染逻辑,也就是我们要在这个render pass中做什么事情。搬一下原文的解释:
    1. 声明临时纹理
    2. 设置源渲染纹理mSourceRT目标渲染纹理mDesRT为渲染数据的相机颜色目标处理。(区分有无finalBlit)
    3. 如果只有一个后处理效果,则直接将这个后处理效果从mSourceRT渲染到mTempRT0。
    4. 如果有多个后处理效果,则逐后处理的在mTempRT0和mTempRT1之间渲染。由于每次循环结束交换它们,所以最终纹理依然存在mTempRT0。
    5. 使用Blitter.BlitCameraTexture函数将mTempRT0中的结果复制到目标渲染纹理mDesRT中。
  • OnCameraCleanup函数,相机清除时执行。
  • Dispose函数,释放资源。

在写完render feature和render pass后就能上unity面板上挂载了。如果没有Universal Render Pipeline Asset就需要自己创建了。

在这里插入图片描述
在这里插入图片描述
如果没有就创建:
在这里插入图片描述

例子:BSC

后处理shader(BSC)

Shader "URP/12_BrightnessSaturationAndContrast"
{Properties{_MainTex ("Texture", 2D) = "white" {}_Brightness("Brightness",Float)=1.5_Saturation("Saturation", Float) = 1.5_Contrast("Contrast", Float) = 1.5}SubShader{Tags {"RenderPipeline" = "UniversalRenderPipeline""RenderType"="Opaque" }        //基本是后处理shader的必备设置,放置场景中的透明物体渲染错误//注意进行该设置后,shader将在完成透明物体的渲染后起作用,即RenderPassEvent.AfterRenderingTransparents后ZTest AlwaysCull OffZWrite OffHLSLINCLUDE#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"CBUFFER_START(UnityPerMaterial)float4 _MainTex_ST;half _Brightness;half _Saturation;half _Contrast;CBUFFER_END// 下面两句类似于 sampler2D _MainTex;TEXTURE2D(_MainTex);SAMPLER(sampler_MainTex);struct a2v{float4 positionOS:POSITION;            float2 texcoord:TEXCOORD;};struct v2f{float4 positionCS:SV_POSITION;float2 texcoord:TEXCOORD;};ENDHLSLPass{Name "BSC_Pass"Tags{"LightMode" = "UniversalForward"}HLSLPROGRAM#pragma vertex vert#pragma fragment fragv2f vert (a2v v){v2f o;//o.vertex = UnityObjectToClipPos(v.vertex);o.positionCS = TransformObjectToHClip(v.positionOS.xyz);    // 类似于上面那句o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);                return o;}half4 frag (v2f i) : SV_Target{// sample the texture//fixed4 col = tex2D(_MainTex, i.uv);half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);   //SAMPLE_TEXTURE2D类似上面那句采样的tex2D//应用亮度,亮度的调整非常简单,只需要把原颜色乘以亮度系数_Brightness即可half3 finalColor = tex.rgb * _Brightness;//应用饱和度,通过对每个颜色分量乘以一个特定的系数再相加得到一个饱和度为0的颜色值half luminance = 0.2125 * tex.r + 0.7154 * tex.g + 0.0721 * tex.b;half3 luminanceColor = half3(luminance,luminance,luminance);//用_Saturation属性和上一步得到的颜色之间进行插值finalColor = lerp(luminanceColor, finalColor, _Saturation);//应用对比度,创建一个对比度为0的颜色值(各分量为0.5)half3 avgColor = half3(0.5, 0.5, 0.5);//使用_Contrast属性和上一步得到的颜色之间进行插值finalColor = lerp(avgColor, finalColor, _Contrast);return half4(finalColor, 1.0);                }ENDHLSL}}FallBack "Packages/com.unity.render-pipelines.universal/FallbackError"
}

后处理cs脚本(BSC)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;[VolumeComponentMenu("My Post-Processing/BSC_Blit")]
public class BSC_Blit : CustomPostProcessing
{public ClampedFloatParameter brightness = new ClampedFloatParameter(1.5f, 0.0f, 10.0f);public ClampedFloatParameter saturation = new ClampedFloatParameter(1.5f, 0.0f, 10.0f);public ClampedFloatParameter contrast = new ClampedFloatParameter(1.5f, 0.0f, 10.0f);private Material material;private const string mShaderName = "URP/12_BrightnessSaturationAndContrast";public override CustomPostProcessingInjectionPoint InjectionPoint => CustomPostProcessingInjectionPoint.AfterOpaque;public override int OrderInInjectionPoint => 0;public override bool IsActive(){return (material != null);}//配置当前后处理,创建材质public override void Setup(){if(material == null){material = CoreUtils.CreateEngineMaterial(mShaderName);}}//渲染,设置材质的各种参数public override void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination){if(material == null){Debug.LogWarning("材质不存在");return;}material.SetFloat("_Brightness", brightness.value);material.SetFloat("_Saturation", saturation.value);material.SetFloat("_Contrast", contrast.value);cmd.Blit(source, destination,material, 0);}public override void Dispose(bool disposing){base.Dispose(disposing);CoreUtils.Destroy(material);}
}

例子:ColorBlit

这个是大大的列子,还加了hlsl文件。

PostProcessing.hlsl

#ifndef POSTPROCESSING_INCLUDED
#define POSTPROCESSING_INCLUDED#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);TEXTURE2D(_CameraDepthTexture);
SAMPLER(sampler_CameraDepthTexture);struct Attributes {float4 positionOS : POSITION;float2 uv : TEXCOORD0;
};struct Varyings {float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;UNITY_VERTEX_OUTPUT_STEREO
};half4 SampleSourceTexture(float2 uv) {return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
}half4 SampleSourceTexture(Varyings input) {return SampleSourceTexture(input.uv);
}Varyings Vert(Attributes input) {Varyings output = (Varyings)0;// 分配instance idUNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);output.vertex = vertexInput.positionCS;output.uv = input.uv;return output;
}#endif

ColorBlit2.shader

Shader "URP/ColorBlit2"
{Properties {// 显式声明出来_MainTex[HideInInspector]_MainTex ("Base (RGB)", 2D) = "white" {}}SubShader {Tags {"RenderType"="Opaque""RenderPipeline" = "UniversalPipeline"}LOD 200Pass {Name "ColorBlitPass"HLSLPROGRAM#include "PostProcessing.hlsl"#pragma vertex Vert#pragma fragment fragfloat _Intensity;half4 frag(Varyings input) : SV_Target {float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);return color * float4(0, _Intensity, 0, 1);}ENDHLSL}}
}

ColorBlit.cs文件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;[VolumeComponentMenu("My Post-processing/Color Blit2")]
public class ColorBlit : CustomPostProcessing
{public ClampedFloatParameter intensity = new ClampedFloatParameter(0.0f, 0.0f, 2.0f);private Material material;private const string mShaderName = "URP/ColorBlit2";    public override bool IsActive(){return (material != null && intensity.value > 0);}public override CustomPostProcessingInjectionPoint InjectionPoint => CustomPostProcessingInjectionPoint.BeforePostProcess;public override int OrderInInjectionPoint => 0;public override void Setup(){if(material == null){material = CoreUtils.CreateEngineMaterial(mShaderName);}}public override void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination){if(material == null){Debug.LogWarning("材质不存在,请检查");return;}material.SetFloat("_Intensity", intensity.value);cmd.Blit(source, destination, material, 0);}public override void Dispose(bool disposing){base.Dispose(disposing);CoreUtils.Destroy(material);}
}

其实不太明白这个hlsl文件的意义,因为我自己写的那个BSC是可以用的,如果大家优质的麻烦评论区讲解一下。

其他一些参考

  • URP RenderFeature 基础入门教学
  • 猫都能看懂的URP RenderFeature使用及自定义方法
  • 《Unity Shader 入门精要》从Bulit-in 到URP (HLSL)之后处理(Post-processing : RenderFeature + VolumeComponent)
  • Unity URP管线如何截屏,及热扰动(热扭曲)效果的实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/171739.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ab压力测试

标题相关概念 QPS&#xff0c;每秒查询 QPS&#xff1a;Queries Per Second意思是“每秒查询率”&#xff0c;是一台服务器每秒能够相应的查询次数&#xff0c;是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。 互联网中&#xff0c;作为域名系统服务器的机…

Python时间序列分析库介绍:statsmodels、tslearn、tssearch、tsfresh

时间序列分析在金融和医疗保健等领域至关重要&#xff0c;在这些领域&#xff0c;理解随时间变化的数据模式至关重要。在本文中&#xff0c;我们将介绍四个主要的Python库——statmodels、tslearn、tssearch和tsfresh——每个库都针对时间序列分析的不同方面进行了定制。这些库…

亿图导出word和PDF中清晰度保留方法

步骤一 在亿图软件中画一个元件大小搭配合理的图。注意字体大小的安排&#xff0c;尤其是角标的大小要合适&#xff0c;示范如下 选中所有元器件&#xff0c;右键使用组合功能将电路图组合为一个整体 步骤二&#xff1a; 将亿图软件中的图保存为SVG格式。示范如下 在导出到…

Mybatis-Plus(企业实际开发应用)

一、Mybatis-Plus简介 MyBatis-Plus是MyBatis框架的一个增强工具&#xff0c;可以简化持久层代码开发MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 官网&a…

人工智能基础_机器学习003_有监督机器学习_sklearn中线性方程和正规方程的计算_使用sklearn解算八元一次方程---人工智能工作笔记0042

然后我们再来看看,如何使用sklearn,来进行正规方程的运算,当然这里 首先要安装sklearn,这里如何安装sklearn就不说了,自己查一下 首先我们还是来计算前面的八元一次方程的解,但是这次我们不用np.linalg.solve这个 解线性方程的方式,也不用 直接 解正规方程的方式: 也就是上面…

java后端返回给前端不为空的属性

问题背景&#xff1a; 目前遇到的一个问题。一个对象里面定义了数组、集合、和字符串属性等&#xff0c;但是返回给前端的时候数组和集合都是空的&#xff0c;前端接收到的是“” 一个空字符。然后保存的时候又把空字符传给后端&#xff0c;出现了数据结构不匹配导致报错。 解…

k8s之Flannel网络插件安装提示forbidden无权限

一、问题描述 在安装k8s的网络插件时&#xff0c;提示如下信息&#xff0c;各种forbidden无权限 [rootzzyk8s01 scripts]# kubectl apply -f kube-flannel.yml Error from server (Forbidden): error when retrieving current configuration of: Resource: "policy/v1b…

基于Qt串口Serial Port配置纯代码实现(桌面和嵌入式平台)

## Serial Port Qt 提供了串口类,可以直接对串口访问。我们可以直接使用 Qt 的串口类编程即可,十分方便。Qt 串口类不仅在 Windows 能用,还能在 Linux 下用,虽然串口编程不是什么新鲜事儿,既然 Qt 提供了这方面的接口,我们就充分利用起来,这将会使我们的开发十分方便!…

UnoCSS快速入门

UnoCSS快速入门 UnoCSS一、UnoCSS简介二、UnoCSS解决问题三、UnoCSS实践四、好文推荐 UnoCSS 一、UnoCSS简介 UnoCSS 是一个即时、按需的原子级 CSS 引擎。它专注于提供轻量化、高性能的 CSS 解决方案。“Instant On-demand” 表示 UnoCSS 的加载和渲染速度非常快&#xff0c;…

如何进行渗透测试以提高软件安全性?

对于各种规模的企业和组织来说&#xff0c;软件安全是一个至关重要的问题。随着网络攻击越来越复杂&#xff0c;软件中的漏洞越来越多&#xff0c;确保你的软件安全比以往任何时候都更重要。提高软件安全性的一个有效方法是渗透测试&#xff08;penetration testing&#xff09…

Mac-postman存储文件目录

今天postman弹窗要求登录账号才可访问之前的API文档数据。 但是这postman的账号又是前同事的账号&#xff0c;我没有他的账号和密码啊。 登录了我自己的postman账号后&#xff0c;所有的api文档都不见了....我服了。 首先去屏幕左上角---> 前往 --->个人 然后键盘按显…

用图说话——流程图进阶

目录 一、基本流程图 二、时序流程图 一、基本流程图 经常阅读歪果仁绘制的流程图&#xff0c;感觉比较规范&#xff0c;自己在工作中也尝试用他们思维来绘图&#xff0c;这是一个小栗子&#xff1a; 二、时序流程图 在进行Detail设计过程中&#xff0c;一般的绘图软件显得…

【QT】信号和槽能自动传递参数

一、前置示例代码 main.cpp #include "widget.h"#include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv); // 应用程序对象a&#xff0c;在Qt中&#xff0c;应用程序对象&#xff0c;有且仅有一个。Widget w; // 窗口对…

电力巡检/电力抢修行业解决方案:AI+视频技术助力解决巡检监管难题

一、行业背景 随着国民经济的蓬勃发展&#xff0c;工业用电和居民用电需求迅速增加&#xff0c;电厂、变电站、输电线路高负荷运转&#xff0c;一旦某个节点发生故障&#xff0c;对生产、生活造成巨大的影响。目前电力行业生产现场人员、设备较多&#xff0c;而生产监督员有限…

PS笔记2_钢笔工具的形状和路径

本文目录 前言Step 1 形状的用法&#xff1a;画图Step 2 路径的用法&#xff1a;抠图 前言 当我们在PS中选择钢笔工具时&#xff0c;上方功能栏中可以选择钢笔的功能项&#xff0c;有三种选项&#xff1a;形状&#xff0c;路径和像素。最常用的就是“形状”和“路径”。本博文…

AcWing 1.2.1 最长上升子序列模型 + 动态规划 + 图解(详细)

&#xff08;1&#xff09;acwing 4557. 最长上升子序列 4557. 最长上升子序列 - AcWing题库 给定一个长度为 N 的整数序列 a1,a2,…,aN。请你计算该序列的最长上升子序列的长度。上升子序列是指数值严格单调递增的子序列 输入格式 第一行包含整数 N第二行包含 N个整数 a1,a…

UE4 使用材质后期 制作玻璃有雨效果

效果展示&#xff0c;其实这是一个动画效果 以上为所有逻辑 拿到TexCoord给到Panner&#xff0c;Time和Speed都是通过下面计算而来&#xff0c;后面讲&#xff0c;再拿到时间和速度值过后&#xff0c;加上扰动值&#xff0c;最后取G值&#xff0c;因为雨事从上而下的动&#xf…

MATLAB中polyvalm函数用法

目录 语法 说明 示例 特征多项式的矩阵计算 polyvalm函数的功能是矩阵多项式计算。 语法 Y polyvalm(p,X) 说明 Y polyvalm(p,X) 以矩阵方式返回多项式 p 的计算值。此计算方式等同于使用多项式 p 替换矩阵 X。 示例 特征多项式的矩阵计算 求解 4 阶帕斯卡矩阵的特征…