前言
之前做游戏一直想弄个可以实时触发相机滤镜的效果,自处找了教程和资料,想要做到自定义效果的话最好办法是在unity 内部实现,这个办法比较硬核,其实不适合我这样的小白,所以我在实现的过程中非常痛苦,我用的unity URP 模式其实自带后处理隐藏菜单的,这功能可以通过添加Volume组件启用,但是要在底层代码中自己写个脚本,类似在底层接一根线出来。
一、自定义render feature
先从底层接线,自定义render feature,怎么定义呢,先找到你的渲染管线,打开菜单栏edit—project setting - Graphics 里面查看你的初始管线,一般是高级管线(high quality),你如果去找setting文件夹里会发现有低中高三个管线,新版 URP模式的渲染管线有一个很奇怪的bug,就是你在edit里直接换管线不会有任何变化,(不知道这是不是unity的bug)有个在游戏公司的学姐告诉我他们一般会把highQ的删了重新设置一个管线,参数之类的自己可以看官方介绍视频去调整,这里不说了。
找到highQuality管线之后双击就能看到它在setting文件夹的位置,inspector栏中再点击它的“儿子”,(这个高清管线的核心管线就是这个 forwardRender,双击它)
好的,接下来就是关键的一步了,这里我放了个油管大神的教学视频,你同时还需要它的重要道具一个叫blit的脚本:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
// this was used on https://gamedevbill.com, but originally taken from https://cyangamedev.wordpress.com/2020/06/22/urp-post-processing/
// Saved in Blit.cs
public class Blit : ScriptableRendererFeature
{
public class BlitPass : ScriptableRenderPass
{
public enum RenderTarget
{
Color,
RenderTexture,
}
public Material blitMaterial = null;
public int blitShaderPassIndex = 0 ;
public FilterMode filterMode { get; set; }
private RenderTargetIdentifier source { get; set; }
private RenderTargetHandle destination { get; set; }
RenderTargetHandle m_TemporaryColorTexture;
string m_ProfilerTag;
public BlitPass(RenderPassEvent renderPassEvent, Material blitMaterial, int blitShaderPassIndex, string tag)
{
this.renderPassEvent = renderPassEvent;
this.blitMaterial = blitMaterial;
this.blitShaderPassIndex = blitShaderPassIndex;
m_ProfilerTag = tag;
m_TemporaryColorTexture.Init("_TemporaryColorTexture");
}
public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
{
this.source = source;
this.destination = destination;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
opaqueDesc.depthBufferBits = 0;
// Can't read and write to same color target, use a TemporaryRT
if (destination == RenderTargetHandle.CameraTarget)
{
cmd.GetTemporaryRT(m_TemporaryColorTexture.id, opaqueDesc, filterMode);
Blit(cmd, source, m_TemporaryColorTexture.Identifier(), blitMaterial, blitShaderPassIndex);
Blit(cmd, m_TemporaryColorTexture.Identifier(), source);
}
else
{
Blit(cmd, source, destination.Identifier(), blitMaterial, blitShaderPassIndex);
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void FrameCleanup(CommandBuffer cmd)
{
if (destination == RenderTargetHandle.CameraTarget)
cmd.ReleaseTemporaryRT(m_TemporaryColorTexture.id);
}
}
[System.Serializable]
public class BlitSettings
{
public RenderPassEvent Event = RenderPassEvent.AfterRenderingOpaques;
public Material blitMaterial = null;
public int blitMaterialPassIndex = 0;
public Target destination = Target.Color;
public string textureId = "_BlitPassTexture";
}
public enum Target
{
Color,
Texture
}
public BlitSettings settings = new BlitSettings();
RenderTargetHandle m_RenderTextureHandle;
BlitPass blitPass;
private volumepass Volumepass;
public override void Create()
{
var passIndex = settings.blitMaterial != null ? settings.blitMaterial.passCount - 1 : 1;
settings.blitMaterialPassIndex = Mathf.Clamp(settings.blitMaterialPassIndex, -1, passIndex);
blitPass = new BlitPass(settings.Event, settings.blitMaterial, settings.blitMaterialPassIndex, name);
m_RenderTextureHandle.Init(settings.textureId);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
var src = renderer.cameraColorTarget;
var dest = (settings.destination == Target.Color) ? RenderTargetHandle.CameraTarget : m_RenderTextureHandle;
if (settings.blitMaterial == null)
{
Debug.LogWarningFormat("Missing Blit Material. {0} blit pass will not execute. Check for missing reference in the assigned renderer.", GetType().Name);
return;
}
if (!renderingData.cameraData.postProcessEnabled) return;
var stack = VolumeManager.instance.stack;
Volumepass = stack.GetComponent<volumepass>();
if (Volumepass == null) { return; }
if (!Volumepass.IsActive()) return;
blitPass.Setup(src, dest);
renderer.EnqueuePass(blitPass);
}
}
https://www.youtube.com/watch?v=4apbNiPC3yQ&t=9swww.youtube.com/watch?v=4apbNiPC3yQ&t=9s
脚本具体的细节我就不说了,其实代码之类的我也不是很懂,劝小白真的不要像我一样随意去碰unity高阶外挂式脚本。。。。如果真的求知欲很重的话,建议小白先努力学习一下c#,我这里也是参考了这位大佬的文章:
把脚本仍进unity,然后继续上面步骤,点击add render feature,blit出来了,点击:
接下来出现你的自定义参数模块了,为了易于辨认,然后我改了newblit的名字,你们随意,blit material一栏就是你们可以设置自己的屏幕滤镜(shader)材质(没错!!!就是用shader的办法自己做屏幕滤镜!!!)我这里自己做了个材质,用了特殊shader,shader怎么写具体可以参考油管大佬GameDevBill的视频。
一些滤镜你们去尝试吧,但是现在还有一个问题,如果想让这个滤镜偶尔出来一下并且和玩家交互要怎样实现,你不可能一直在setting里面的forward管线组件里去开关,所以在获得这个功能之后需要做一个和外界连接的触发开关。
接下来这里具体说一下接下来怎么做触发,在我原本的设想中,是让player触碰某个物件然后做一个触发效果,出现屏幕滤镜,现在底层的线拉出来了,怎么和前台操作界面关联呢。
二、volume pass组件
接下来要在hierarchy新建一个volume,然后添加一个叫VolumPass的脚本就可以实现了,脚本如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEngine.Rendering.Universal
{
[System.Serializable,VolumeComponentMenu("volumepass")]
public class volumepass : VolumeComponent, IPostProcessComponent
{
[Tooltip("是否开启效果")]
public BoolParameter enableEffect = new BoolParameter(true, true);
public FloatParameter GrabWidth = new FloatParameter(2, true);
public FloatParameter GrabHeight = new FloatParameter(2, true);
public bool IsActive() => enableEffect == true;
public bool IsTileCompatible() => false;
}
}
在volume组件内置模块点击 add override,出现这个脚本选项
volume pass的具体作用就是给render feature 接一个开关,实现了以后长这样,你可以自主选择是否打勾开启效果:
还记得我想做的触发效果吗,还没完,最后一步了!!!走到这里真的心累,不过还好最难的已经过去了。。。。
还记得刚刚hierarchy里的volume吗,右键添加一个cube,cube设置不可见,目的是为了做trigger触发机关,所以别忘了添加boxcollider,(其实直接在volume里面添加collider组件也可以,我这里是为了方便识别。。。)
所以这里还要再添加一个叫 posttrigger的脚本组件,这个脚本应该是最基本的c#内容了,别说你看不懂了)主要功能就是当player碰到这个cube就会触发这个滤镜:
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering;
using UnityEngine;
public class posttrigger : MonoBehaviour
{
private Volume volume;
private volumepass volumpas;
// Start is called before the first frame update
private void Start()
{
volume = GetComponent<Volume>();
volume.profile.TryGet(out volumpas);
}
// Update is called once per frame
private void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
{
volumpas.enableEffect.Override(false);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
volumpas.enableEffect.Override(true);
}
}
}