Unity Shader - 搬砖日志 - URP 扩展后效 - 实现 BoxMosaicPP (矩形马赛克后效)

141 篇文章 36 订阅


环境信息

Unity : 2019.4.30f1
URP : 7.7.1


吐个槽,UniversalRP 没有官方提供实现自定义效果的 URP Integrated 的 PP 接口,总之有点不靠谱啊

不过后来我瞄了一下 URP 部分提供的源码,发现不提供URP的PP扩展实例有几个原因:

  • 因为URP PP 的性能优化考虑,部分效果都其实一个pass处理了
  • 因为性能优化,一个pass处理多个volume 效果,那么就会有特殊处理的情况,所以扩展什么都不好扩展,硬是要扩展,我觉得也是可以的,只不过要通读URP所有PP相关代码后,调整内置代码的方式来扩展,但是如果一旦升级URP版本就很有可能很麻烦,特别是URP PP相关代码如果有改动或是升级兼容问题的话

VolumeComponent

该类是负责管理 VolumeMonoBehavior 组件中的 Add Overrides 中的 Volume 列表中的选项基类,但是需要 Attribute 来标记,这样 unity 黑箱会遍历添加到列表项中


VolumeManager

该类是负责管理 VolumeMonoBehavior 组件中的 Add Overrides 中的 Volume 列表

开类所在路径:<Unity Project 路径>/Library/PackageCache/com.unity.render-pipeline.core@x.x.x/Runtime/Volume/VolumeManager.cs 下,其中 x.x.x 就是 URP 的版本号

查看器源代码,可以发现,该类是单例模式,并且在构造函数就加载了 baseComponentTypes ,这 baseComponentTypes 就是扩展与 VolumeComponent 的所有类型

在这里插入图片描述

你可以发现,使用了一个 API:

            // Grab all the component types we can find
            baseComponentTypes = CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>()
                .Where(t => !t.IsAbstract);

其中 CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>() 是 cpp inject 到 csharp 层的接口,这里就不多说明了,作用就是获取当前程序集当中的所有继承自 VolumeComponent 的类,注意还使用了 Linq : Where(t => !t.IsAbstract);abstract

然后在创建 VolumeComponent 的 Stack CreateStack ,如下:
在这里插入图片描述

就是调用 VolumeStack 中的 Reload 函数,如下,就是遍历类型,逐个 CreateInstance
在这里插入图片描述


ScriptableRendererFeature

是 URP Renderer 中添加额外 Renderer Feature 功能的基类
实现该类后,可以在:URP Renderer 中的 Add RendererFeature 中看到此选项
在这里插入图片描述


ScriptableRenderPass

在 URP 中实现的 PostProcess 中,你可以参考 URP 中的源码是如何实现的,源码文件所在位置:<Unity Project 路径>/Library/PackageCache/com.unity.render-pipeline.universal@x.x.x/Runtime/Passes 下,其中 x.x.x 就是 URP 的版本号
在这里插入图片描述

这么多的 Pass,你可以临摹其中的写法


总结

要扩展 URP 的 PP,那么可以总结下面几步即可(留意之前说的我的环境是,unity 2019.4.30f1, URP 7.7.1 的版本,如果以后在其他版本实现扩展,发现 API 过时、不存在、或是没效果,那么要确保是否环境一致):

  • VolumeComponent 扩展,为了在 VolumeMonoBehavior 组件中的 Add Overrides 可以有对应选项,且可以设置 VolumeComponent 相关的属性,在渲染时,可以传递给 ScriptableRendererFeature, ScriptableRenderPass
  • ScriptableRendererFeature 扩展,是对应 URP Pipeline Renderer 的一个渲染 Feature 的扩展,我们可以自定义一些类,继承自 ScriptableRendererFeature,然后在该 Feature 中调用后续我括扩展的 ScriptableRenderPass
    • 实现 抽象基类的 Create 方法,在此方法中 new XXXRenderPassXXXRenderPass 就是你自己扩展的自定义 pass,然后设置 Pass 的 renderPassEvent
    • 实现 抽象基类的 AddRenderPasses 方法,在此方法中,给 上面你创建出来的 XXXRenderPass 设置渲染前需要的数据,如:指定 Material.shader、设置Material.SetXXX、最后一定要调用 renderer.EnqueuePass(pass);,这样该 Pass的 Execute 方法才会被执行到
  • ScriptableRenderPass 扩展,这里就是我们的自定义的 PP 执行的绘制 API 调用的核心封装的地方
    • 实现 抽象基类的 Execute 方法,在此方法中
    • 需要判断是否 VolumeComponent 是否启用、等,各种不符合执行 Pass 的条件都过滤掉
    • 过滤了条件后,接着就是将 RenderingData.cameraData.cameraTargetDescriptor 作为 类似 Built-in RP 中 void OnRenderImage(RenderTexture src, RenderTexture dest) 中的 RenderTexture src
    • 然后使用PP后效对应的 material 调用 Blit 来绘制到另一个 TempRT(这个RT你自己的 pass 维护,可复用的)
    • 最后将 TempRT 再复制回 RenderingData.cameraData.cameraTargetDescriptor,这样就可以给下一个 Pass 后续接着处理了

DEMO

下面是的所有源代码,注意 我在代码中写下的 注释

矩形马赛克的后效,其实我之前在 Built-in RP 中有实现过一般,可以参考文章:Unity Shader PostProcessing - 5 - PixelSyle 像素化风格


VolumeComponent - BoxMosaicVolumeComponent

其中的 MinIntParamter 类型的参数,其实可以有很多中,具体有多少中,你可以搜索一下,可以参考下面的 查看有多少种现成的 VolueComponent 的参数类型

// jave.lin 2021/10/21
// Box Mosaic Volume Component

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// VolumeComponentMenu 的 Attribute 会自动被 unity 阴影分析到 Volume 的 VolumeComponent 的菜单列表项中
[Serializable, VolumeComponentMenu("Custom-Post-processing/BoxMosaic")]
public class BoxMosaicVolumeComponent : VolumeComponent, IPostProcessComponent
{
    public static readonly int _PixelSize = Shader.PropertyToID("_PixelSize");
    [Header("马赛克大小")]
    [Tooltip("马赛克大小")]
    public MinIntParameter pixelSize = new MinIntParameter(1, 1, false);
    public bool IsActive() => pixelSize.value > 1;
    public bool IsTileCompatible() => false;
    // 将 volume 中的属性设置到对应的 mat 中,可以封装到基类处理
    public void SetToMat(Material mat)
    {
        Debug.Log($"{nameof(BoxMosaicVolumeComponent)}.SetToMat");
        mat.SetFloat(_PixelSize, (float)pixelSize.value);
    }
}


ScriptableRenderPass - ExtendURPCustomPPRenderPass

// jave.lin 2021/10/25
// 用于扩展 URP PP 的自定义 RenderPass
// 我们定位 ScriptableRenderPass.Execute 方法的作用类似于 Built-in RP 中的
// MonoBehavior 的 void OnRenderImage(RenderTexture src, RenderTexture dest)

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class ExtendURPCustomPPRenderPass : ScriptableRenderPass
{
    // command buffer 名字
    private const string CMD_BUF_NAME = "ExtendURPCustomPPRenderPass";

    // 使用的材质
    public Material mat;
    // volume component
    public BoxMosaicVolumeComponent volumeComp;
    // src render target
    public RenderTargetIdentifier srcTarget;
    // 用于可重复使用的 render target handle, RenderTargetHandle.identity() 是对应的 RenderTargetIdentity
    private RenderTargetHandle tempTargetHandle;

    public ExtendURPCustomPPRenderPass()
    {
        Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.ctor");
        // jave.lin : 相当于 this.tempTargetHandle.id = Shader.PropertyToID("_TempRT")
        this.tempTargetHandle.Init("_TempRT");
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.Execute");
        // 没材质,不处理
        if (mat == null) return;
        // 如果是在 Scene 视图下的相机,那么不处理
        if (renderingData.cameraData.isSceneViewCamera) return;
        // VolumeManager 单例管理器中拿到 VolumeStack 对象
        var stack = VolumeManager.instance.stack;
        volumeComp = stack.GetComponent<BoxMosaicVolumeComponent>();
        if (volumeComp == null) return;
        if (!volumeComp.IsActive()) return;
        // Render
        var cmd = CommandBufferPool.Get(CMD_BUF_NAME);
        Render(cmd, ref renderingData);
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    public void Setup(RenderTargetIdentifier srcTarget, Material mat)
    {
        Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.Setup");
        this.srcTarget = srcTarget;
        this.mat = mat;
    }

    private void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        Debug.Log($"{nameof(ExtendURPCustomPPRenderPass)}.Render");
        // jave.lin : 这里有一些细节
        // 先拿到 struct CameraData 的引用
        // 减少 stack frame 上的 instance, copy, release
        ref var camData = ref renderingData.cameraData;
        // 将 volume component 的属性设置到 material 中
        volumeComp.SetToMat(mat);
        // 获取临时 rt des
        // jave.lin : copy rt des
        RenderTextureDescriptor tempRTDes = camData.cameraTargetDescriptor;
        // jave.lin : override remove depth buffer
        tempRTDes.depthBufferBits = 0;
        // jave.lin : 将 tempRTDes 的内容与 tempTargetHandle.id 也就是 Shader.PropertyToID("_TempRT") 的id 对应绑定
        cmd.GetTemporaryRT(tempTargetHandle.id, tempRTDes);
        // jave.lin : 这里将是我们 box mosaic 的核心执行的地方
        cmd.Blit(srcTarget, tempTargetHandle.Identifier(), mat);
        // jave.lin : 将后效处理后的 rt 内容复制会 src
        // 便于一下的后效接着已有的内容基础上继续处理
        // 如果两个 rt 尺寸不同,也可以使用 blit 来处理
        cmd.CopyTexture(tempTargetHandle.Identifier(), srcTarget);
        // jave.lin : release 刚刚上面申请的 rt
        cmd.ReleaseTemporaryRT(tempTargetHandle.id);
    }
}


ScriptableRendererFeature - ExtendURPCustomPPRendererFeature

// jave.lin 2021/10/25
// 用于扩展 URP PP 的自定义 RendererFeature
// 我们定位 ScriptableRenderPass.Execute 方法的作用类似于 Built-in RP 中的
// MonoBehavior 的 void OnRenderImage(RenderTexture src, RenderTexture dest)

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class ExtendURPCustomPPRendererFeature : ScriptableRendererFeature
{
    // 用于后效的 shader
    public Shader shader;
    // 用于后效的 pass
    private ExtendURPCustomPPRenderPass pass;
    // 用于后效的 mat
    private Material mat;

    public override void Create()
    {
        var srcPassObj = pass;
        // jave.lin : 如果这里不判断是否创建过,那么会被创建多次而浪费掉
        if (srcPassObj == null)
        {
            // 创建 pass
            pass = new ExtendURPCustomPPRenderPass();
            // 设置 pass 执行的 event 阶段,可以理解为:inject point
            // 这里我们设置到 urp PP 前渲染
            pass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        }
        // 在该 ScriptableRendererFeature.OnEnabled, OnValidated 被创建时调用
        Debug.Log($"{nameof(ExtendURPCustomPPRendererFeature)}.Create, srcPassObj : {srcPassObj}, nowPassObj : {pass}");
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        Debug.Log($"{nameof(ExtendURPCustomPPRendererFeature)}.AddRenderPasses");

        // 没设置 shader
        if (shader == null) return;
        // 根据 shader 构建 material
        if (mat == null)
        {
            mat = CoreUtils.CreateEngineMaterial(shader);
        }
        // 将相机渲染的目录 rt 作用 pass 后效的 src target
        var passSrcTarget = renderer.cameraColorTarget;
        // 设置 pass 内容:srcTarget 和 material
        pass.Setup(passSrcTarget, mat);
        // 添加 pass 到渲染队列中
        // 这样 pass 才会被执行 Execute
        renderer.EnqueuePass(pass);
    }

    public void OnDestroy()
    {
        var srcMat = mat;
        if (mat != null)
        {
            CoreUtils.Destroy(mat);
            mat = null;
        }
        Debug.Log($"{nameof(ExtendURPCustomPPRendererFeature)}.OnDestroy, srcMat : {mat}, nowMat : {mat}");
    }
}


Shader - URP/Custom-PostProcess/BoxMosaicPP

// jave.lin 2020.10.25
// URP 版本的 像素化风格的另一种参数控制方式
// Built-in URP 版本的可以参考 blog : https://blog.csdn.net/linjf520/article/details/104842999
Shader "URP/Custom-PostProcess/BoxMosaicPP"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {} // 便于材质中查看该 RT 内容
        _PixelSize ("PixelSize", Range(1, 100)) = 1
    }
    HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        struct appdata
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
        };
        struct v2f
        {
            float4 vertex : SV_POSITION;
            float2 uv : TEXCOORD0;
        };
        v2f vert(appdata v)
        {
            v2f o;
            o.vertex = TransformObjectToHClip(v.vertex.xyz);
            o.uv = v.uv;
            return o;
        }
        TEXTURE2D(_MainTex);
        SAMPLER(sampler_MainTex);
        // jave.lin : 如该 shader 会出现在单帧中重复绘制调用的情况下
        // 那么建议将下面的 uniform 都提取到 CBUFFER 中,否则不用提取
        float4 _MainTex_TexelSize;
        half _PixelSize;
        half4 frag(v2f i) : SV_Target
        {
            float2 interval = _PixelSize * _MainTex_TexelSize.xy;
            float2 th = i.uv * rcp(interval);       // 按interval划分中,属于第几个像素
            float2 th_int = th - frac(th);          // 去小数,让采样的第几个纹素整数化,这就是失真UV关键
            th_int *= interval;                     // 再重新按第几个像素的uv坐标定位
            return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, th_int);
        }
    ENDHLSL
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDHLSL
        }
    }
}



效果

在这里插入图片描述

可以看到:Pixel Size == 1 时,SetPass Call 为 4,大于 1 才会 + 1 = 5


封装

其实上面的写法当中,单单查扩展的一个 URP PP 没什么问题

但是如果你想要扩展多个 URP PP 时,你会发现你需要写重复的代码一堆一堆,这时候就是需要你的 软件基本功:OOP 设置一下,封装一下就好了,虽然一致决定 OOP 的设计有很多 诟病,但是对于这么小的一个功能够用,诟病也不会因此放大


Project

TestingURPCustomPP_BoxMosaicPP 提取码: 82cr


References


查看有多少种现成的 VolueComponent 的参数类型



Searching 377 files for "(?!=\[Serializable, DebuggerDisplay\(k_DebuggerDisplay\)\]\n).*?class \w+?Parameter\b" (regex)

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.core@7.7.1\Editor\Volume\SerializedDataParameter.cs:
   12:     public sealed class SerializedDataParameter

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.core@7.7.1\Runtime\Utilities\TextureCurve.cs:
  244:     public class TextureCurveParameter : VolumeParameter<TextureCurve>

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.core@7.7.1\Runtime\Volume\VolumeParameter.cs:
   18:     public abstract class VolumeParameter
  131:     /// public sealed class MyFloatParameter : VolumeParameter&lt;float&gt;
  145:     public class VolumeParameter<T> : VolumeParameter, IEquatable<VolumeParameter<T>>
  316:     //  public sealed class MyEnumParameter : VolumeParameter<MyEnum> { }
  324:     public class BoolParameter : VolumeParameter<bool>
  339:     public class LayerMaskParameter : VolumeParameter<LayerMask>
  361:     public class IntParameter : VolumeParameter<int>
  396:     public class NoInterpIntParameter : VolumeParameter<int>
  419:     public class MinIntParameter : IntParameter
  463:     public class NoInterpMinIntParameter : VolumeParameter<int>
  507:     public class MaxIntParameter : IntParameter
  551:     public class NoInterpMaxIntParameter : VolumeParameter<int>
  595:     public class ClampedIntParameter : IntParameter
  646:     public class NoInterpClampedIntParameter : VolumeParameter<int>
  698:     public class FloatParameter : VolumeParameter<float>
  733:     public class NoInterpFloatParameter : VolumeParameter<float>
  757:     public class MinFloatParameter : FloatParameter
  803:     public class NoInterpMinFloatParameter : VolumeParameter<float>
  848:     public class MaxFloatParameter : FloatParameter
  894:     public class NoInterpMaxFloatParameter : VolumeParameter<float>
  940:     public class ClampedFloatParameter : FloatParameter
  993:     public class NoInterpClampedFloatParameter : VolumeParameter<float>
 1046:     public class FloatRangeParameter : VolumeParameter<Vector2>
 1115:     public class NoInterpFloatRangeParameter : VolumeParameter<Vector2>
 1163:     public class ColorParameter : VolumeParameter<Color>
 1232:     public class NoInterpColorParameter : VolumeParameter<Color>
 1280:     public class Vector2Parameter : VolumeParameter<Vector2>
 1308:     public class NoInterpVector2Parameter : VolumeParameter<Vector2>
 1324:     public class Vector3Parameter : VolumeParameter<Vector3>
 1353:     public class NoInterpVector3Parameter : VolumeParameter<Vector3>
 1369:     public class Vector4Parameter : VolumeParameter<Vector4>
 1399:     public class NoInterpVector4Parameter : VolumeParameter<Vector4>
 1414:     public class TextureParameter : VolumeParameter<Texture>
 1431:     public class NoInterpTextureParameter : VolumeParameter<Texture>
 1446:     public class RenderTextureParameter : VolumeParameter<RenderTexture>
 1463:     public class NoInterpRenderTextureParameter : VolumeParameter<RenderTexture>
 1478:     public class CubemapParameter : VolumeParameter<Cubemap>
 1495:     public class NoInterpCubemapParameter : VolumeParameter<Cubemap>
 1513:     public class ObjectParameter<T> : VolumeParameter<T>
 1588:     public class AnimationCurveParameter : VolumeParameter<AnimationCurve>

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Editor\SavedParameter.cs:
    6:     class SavedParameter<T>

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\DepthOfField.cs:
   60:     public sealed class DepthOfFieldModeParameter : VolumeParameter<DepthOfFieldMode> { public DepthOfFieldModeParameter(DepthOfFieldMode value, bool overrideState = false) : base(value, overrideState) { } }

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\FilmGrain.cs:
   41:     public sealed class FilmGrainLookupParameter : VolumeParameter<FilmGrainLookup> { public FilmGrainLookupParameter(FilmGrainLookup value, bool overrideState = false) : base(value, overrideState) { } }

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\MotionBlur.cs:
   39:     public sealed class MotionBlurModeParameter : VolumeParameter<MotionBlurMode> { public MotionBlurModeParameter(MotionBlurMode value, bool overrideState = false) : base(value, overrideState) { } }
   42:     public sealed class MotionBlurQualityParameter : VolumeParameter<MotionBlurQuality> { public MotionBlurQualityParameter(MotionBlurQuality value, bool overrideState = false) : base(value, overrideState) { } }

H:\WorkFiles\TestingSRPStartProj\TestingSRPStartProj\Library\PackageCache\com.unity.render-pipelines.universal@7.7.1\Runtime\Overrides\Tonemapping.cs:
   24:     public sealed class TonemappingModeParameter : VolumeParameter<TonemappingMode> { public TonemappingModeParameter(TonemappingMode value, bool overrideState = false) : base(value, overrideState) { } }

48 matches across 8 files

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值