您现在的位置是:首页 >技术杂谈 >URP自定义屏幕后处理网站首页技术杂谈

URP自定义屏幕后处理

阿赵3D 2024-07-15 06:01:02
简介urp 后处理

回到目录

大家好,我是阿赵。这次来说一下URP渲染管线里面怎样使用后处理效果,还有怎样去自定义后处理效果。

一、使用URP自带的后处理效果

要使用URP自带的后处理效果,方法很简单,和Unity内置渲染管线的PostProcessing后处理很类似的,在一个物体上面添加一个Volume脚本。
在这里插入图片描述

这个脚本的整体用法和PostProcessing Volume非常的像,首先它可以选择是Global还是Local,如果选择Global,那么所有摄像机都可以生效。如果勾选了Local,还会出现距离的选项,和添加碰撞体的选项,这时候可以根据范围来控制哪些摄像机接受到后处理的效果。
然后是Priority选项,如果场景里面同时存在多个Volume,那么以Priority值大的那个Volume作为优先显示。
最后是Profile,可以创建一个后处理文件来编辑效果。
在创建和指定了后处理文件之后,就可以添加效果了,点击Add Override,然后选择Post-processing,可以看到很多熟悉的内置后处理效果。
这里我们添加一个Bloom,并且随便调一下参数
在这里插入图片描述
在这里插入图片描述

然后去摄像机的属性面板里面,把Post Processing选项勾上
在这里插入图片描述

这时候可以看到,Bloom后处理生效了,场景有了发光的效果。
自带的后处理效果使用很简单,不是我们这次讨论的重点。

二、自定义后处理效果

URP代码里面已经自带了很多后处理效果,但有时候我们还是想做一些特殊的效果。比如我这里想做一个把画面转成线稿的风格化后处理效果。
在这里插入图片描述
在这里插入图片描述

接下来我就以这个效果为例子,说明一下在URP里面怎么自定义后处理效果。

1、写一个Shader

首先,我要写一个可以把图片变成线条风格化效果的Shader。
注意一下,这里面我暴露了4个参数
1._MainTex
_MainTex是主贴图,这个命名不要改,因为等一下做后处理的时候,_MainTex是固定作为屏幕画面而输入的。
2.lineStrength
lineStrength是线条的强度,值越大,线条就越粗越明显
3._lineColor
_lineColor是线条的颜色,可以通过这个值来改变线条的颜色
4.baseColor
baseColor是背景的颜色,背景不一定是白色,可以根据需要改成别的颜色
通过调整各种参数,我们可以得到这样的效果
在这里插入图片描述

这个例子里面,这个shader我们需要留意的就是这个几个参数而已,后面会需要对他们进行控制。至于这个Shader的实现原理,不是这篇文章需要探讨的内容,如果大家有兴趣可以留意,我可以介绍一下。反正Shader也很简单,各位估计自己也能看得懂。
完整Shader:

Shader "azhao/LineStyle"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_lineStrength("lineStrength", Float) = 1.0
		_lineColor("lineColor", Color) = (0,0,0,0)
		_baseColor("baseColor", Color) = (1,1,1,0)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
			CBUFFER_START(UnityPerMaterial)
			float4 _baseColor;
			float4 _MainTex_ST;
			float _lineStrength;
			float4 _lineColor;
			CBUFFER_END
			TEXTURE2D(_MainTex);
			SAMPLER(sampler_MainTex);

            v2f vert (appdata v)
            {
                v2f o;
				VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
				o.pos = vertexInput.positionCS;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                // sample the texture
				half4 col = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, i.uv);
				float grayscale  = col.r * 0.2126729f + col.g * 0.7151522f + col.b * 0.0721750f;
				float ddVal = (saturate(ddx(grayscale) + ddy(grayscale))*_lineStrength);
				half3 finalCol = _baseColor.rgb * (1.0 - ddVal) + _lineColor.rgb * ddVal;
                return half4(finalCol,1);
            }
            ENDHLSL
        }
    }
}

2、新增VolumeComponent

这一步,我们需要把这个效果作为选项,显示在后处理的Volume的添加列表里面。
新建一个C#脚本,然后输入这些内容:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[System.Serializable, VolumeComponentMenu("azhao/LineStyle")]
public class LineStyle : VolumeComponent
{
    public BoolParameter isShow = new BoolParameter(false, true);
    [Tooltip("Strength of the line.")]
    public MinFloatParameter lineStrength = new MinFloatParameter(1, 0,true);
    [Tooltip("The color of the line.")]
    public ColorParameter lineColor = new ColorParameter(Color.black, true);
    [Tooltip("The color of the background.")]
    public ColorParameter baseColor = new ColorParameter(Color.white, true);
}

这时候,就能看到选项了
在这里插入图片描述

分析一下需要注意的地方:

1.指定效果的名字

[System.Serializable, VolumeComponentMenu("azhao/LineStyle")]

通过VolumeComponentMenu,可以指定我们选择效果时的名字,比如这里我把这个效果命名为LineStyle,并放在azhao的目录里面

2.继承VolumeComponent

必须继承VolumeComponent,这个选项才能显示在Volume的添加选项里面

3.定义参数

这里我用到了这么几种参数类型:
BoolParameter 、MinFloatParameter 、ColorParameter 。
从字面意思就可以看出来
BoolParameter 是布尔型的参数
MinFloatParameter 是浮点型的参数,但它可以指定一个最小值,输入的值不能小于最小值
ColorParameter 是颜色类型的参数
我们能用的参数类型非常的多,本来想全部列出来的,但实在是太多了,可以看源码的VolumeParameter.cs,里面有各种参数类型的说明。

4.给参数加Tips

通过Tooltip标签可以给参数做tips显示
在这里插入图片描述

5.额外说明

可以看得出,这里的几个参数,都是对应着LineStyle这个Shader的暴露参数的,除了一个布尔类型的isShow 。这个问题这里先忽略,这个isShow 并不是必要的,而是我为了解决另外一个问题而加的。后面会有说明。

3、新增ScriptableRenderPass

在上面那一步在Volume里面添加了自己的效果之后,发现并没有生效。这是因为,自定义的后处理,其实是作为一种Feature来使用的,就和前一篇文章介绍的描边实现方式类似。
不过之前的Features是把指定的模型渲染的时候增加一个Pass,但后处理针对的不是每个模型,而是整个屏幕的画面。
这里分成2步,一个是指定一个处理画面的Pass,另外一个是指定一个自定义Feature用于添加到Features。
先说第一步,增加一个ScriptableRenderPass
创建一个C#脚本,然后输入下面的内容

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class LineStylePass : ScriptableRenderPass
{
    static readonly string renderTag = "LineStyle Effects";
    static readonly int MainTexId = Shader.PropertyToID("_MainTex");
    static readonly int TempTargetId = Shader.PropertyToID("_TempTargetColorTint");

    private LineStyle lineStyleVolume;
    private Material mat;
    RenderTargetIdentifier currentTarget;

    public LineStylePass(RenderPassEvent passEvent,Shader lineStyleShader)
    {
        renderPassEvent = passEvent;
        if(lineStyleShader == null)
        {
            Debug.LogError("Shader不存在");
            return;
        }
        mat = CoreUtils.CreateEngineMaterial(lineStyleShader);
    }

    public void Setup(in RenderTargetIdentifier currentTarget)
    {
        this.currentTarget = currentTarget;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if(mat == null)
        {
            return;
        }
        if(!renderingData.cameraData.postProcessEnabled)
        {
            return;
        }
        VolumeStack stack = VolumeManager.instance.stack;
        lineStyleVolume = stack.GetComponent<LineStyle>();
        if(lineStyleVolume == null)
        {
            return;
        }
        if (lineStyleVolume.isShow.value == false)
        {
            return;
        }
        CommandBuffer cmd = CommandBufferPool.Get(renderTag);
        Render(cmd, ref renderingData);
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    private void Render(CommandBuffer cmd,ref RenderingData renderingData)
    {

        ref CameraData cameraData = ref renderingData.cameraData;
        Camera camera = cameraData.camera;
        RenderTargetIdentifier source = currentTarget;
        int destination = TempTargetId;

        mat.SetFloat("_lineStrength", lineStyleVolume.lineStrength.value);
        mat.SetColor("_lineColor", lineStyleVolume.lineColor.value);
        mat.SetColor("_baseColor", lineStyleVolume.baseColor.value);

        cmd.SetGlobalTexture(MainTexId, source);
        cmd.GetTemporaryRT(destination, cameraData.camera.scaledPixelWidth, cameraData.camera.scaledPixelHeight, 0, FilterMode.Trilinear, RenderTextureFormat.Default);
        cmd.Blit(source, destination);
        cmd.Blit(destination, source, mat, 0);
    }
}

从上面的代码可以看出,这个Pass分成了以下的步骤:
1、指定输入输出的贴图

static readonly string renderTag = "LineStyle Effects";
static readonly int MainTexId = Shader.PropertyToID("_MainTex");
static readonly int TempTargetId = Shader.PropertyToID("_TempTargetColorTint");

刚才在写shader的时候,说过主贴图_MainTex的名字不要改,就是因为这一步需要指定一个输入的主贴图。当然,也可以改,和这里对应就可以了

2、构造函数

 public LineStylePass(RenderPassEvent passEvent,Shader lineStyleShader)
    {
        renderPassEvent = passEvent;
        if(lineStyleShader == null)
        {
            Debug.LogError("Shader不存在");
            return;
        }
        mat = CoreUtils.CreateEngineMaterial(lineStyleShader);
    }

这个会在外部输入,根据自己的需要编写。比如这里我需要知道的是,RenderPass的事件类型,还有需要用哪个shader进行渲染。所以构造函数就包含了这两个参数。如果这两个参数直接写死在代码里面,那么也就不需要传入这两个参数了。然后这里我顺便创建了用于渲染的材质。

3、初始化

 public void Setup(in RenderTargetIdentifier currentTarget)
    {
        this.currentTarget = currentTarget;
}

这里也是根据自己的需要来定,由于我这里只需要一个渲染输出的目标,所以外部只传入了这个参数。

4、执行
这是一个重写父类的方法,在收到渲染事件的时候,会自己执行。
写在这一部分的方法,首先是做一些过滤,在特定的条件下不需要执行这个Pass的渲染。
当条件全部都满足的时候,就获取CommandBuffer ,并调用渲染方法,最后释放CommandBuffer 。

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if(mat == null)
        {
            return;
        }
        if(!renderingData.cameraData.postProcessEnabled)
        {
            return;
        }
        VolumeStack stack = VolumeManager.instance.stack;
        lineStyleVolume = stack.GetComponent<LineStyle>();
        if(lineStyleVolume == null)
        {
            return;
        }
        if (lineStyleVolume.isShow.value == false)
        {
            return;
        }
        CommandBuffer cmd = CommandBufferPool.Get(renderTag);
        Render(cmd, ref renderingData);
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

这里有个很特殊的地方,就是我之前在VolumeComponent里面添加了一个不在shader里面用到的布尔参数isShow。为什么要添加这个参数呢?原因是,当我们真的添加了Features之后,实际上不管我们是否在场景里面添加Volume,这个后处理的Execute方法都会被执行的。区别在于,如果场景里面没有被激活的对应后处理的Volume,那么这里获取到的VolumeStack 对应的参数都是默认值。所以我在VolumeComponent里面加了一个isShow的变量,默认是false的,当场景里面没有Volume的时候,我就可以通过默认值,让Execute停在这一步,不要再走下面的渲染。
这可以说是一个取巧的办法吧,说不定还有其他的方法可以控制,希望各位可以告诉我。

5、渲染

private void Render(CommandBuffer cmd,ref RenderingData renderingData)
    {

        ref CameraData cameraData = ref renderingData.cameraData;
        Camera camera = cameraData.camera;
        RenderTargetIdentifier source = currentTarget;
        int destination = TempTargetId;

        mat.SetFloat("_lineStrength", lineStyleVolume.lineStrength.value);
        mat.SetColor("_lineColor", lineStyleVolume.lineColor.value);
        mat.SetColor("_baseColor", lineStyleVolume.baseColor.value);

        cmd.SetGlobalTexture(MainTexId, source);
        cmd.GetTemporaryRT(destination, cameraData.camera.scaledPixelWidth, cameraData.camera.scaledPixelHeight, 0, FilterMode.Trilinear, RenderTextureFormat.Default);
        cmd.Blit(source, destination);
        cmd.Blit(destination, source, mat, 0);
    }
}

这一步,和传统的内置渲染管线的后处理写法是一样的,这一步里面把lineStyleVolume里面的参数设置给需要渲染的材质球,然后通过CommandBuffer.Blit方法,指定输入和输出贴图,需要渲染的材质,来得到渲染后的结果。
如果熟悉传统内置渲染管线的后处理写法,这一步倒是没什么好说的了。
如果想看看内置效果是怎样实现的,可以去看看源码:PostProcessPass.cs

4、新增ScriptableRendererFeature

有了执行实际渲染的ScriptableRenderPass后, 接下来就是要把这个Pass用上了。
创建一个C#脚本,然后输入以下内容。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class LineStyleRendererFeature : ScriptableRendererFeature
{
    [System.Serializable]
    public class Settings
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
        public Shader shader;
    }
    public Settings settings = new Settings();
    LineStylePass pass;
    public override void Create()
    {
        this.name = "LineStylePass";
        pass = new LineStylePass(RenderPassEvent.BeforeRenderingPostProcessing, settings.shader);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        pass.Setup(renderer.cameraColorTarget);
        renderer.EnqueuePass(pass);
    }
}

需要注意的地方:
1.Settings是显示在添加的Features里面的选项内容,一定要加上[System.Serializable],不然不会显示出来
2.在Create方法里面可以指定自己显示的名字,并且把Pass创建出来
3.AddRenderPasses方法里面,对pass进行初始化,并且假如到渲染的队列里面。

5、设置Feature

在完成了上面的步骤后,可以看到添加Features的地方,出现了我们自己定义的Feature了
在这里插入图片描述
在这里插入图片描述

添加之后,指定一下需要渲染的Shader。

添加完Features,然后添加Volume指定的效果,调整一下参数,最后在摄像机里面勾选Post processing,效果就出来了。到这里,这个例子就全部做完了,可以看到屏幕里面所有画面都变成了线条风格化了。
在这里插入图片描述
在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。