回到目录
大家好,我是阿赵。这次来说一下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,效果就出来了。到这里,这个例子就全部做完了,可以看到屏幕里面所有画面都变成了线条风格化了。