《崩坏3》4.8版本上线了希儿的新S级装甲。里面的这个大招特效让我印象特别深刻。 (图取自b站视频,up主:橙汁玻璃瓶) 红色的背景加上黑色的人物剪影,这样的场面给人留下了华丽的印象并且带来了强烈的视觉冲击。其配色效果要远比黑底白影、白底黑影、蓝黑甚至黑底红影都更加出色。
这么棒的效果,没什么理由不去分析了,打开unity打开VS就开干!
Command Buffer简介
传送门:Unity官方文档 CommandBuffer就是要执行的图形命令列表。 这应该也是个缓存区,用来保存各种各样的渲染命令。 渲染命令可以选在图中任何一个绿点上执行, 摄像机上、光照上、屏幕后处理上这些的。
方法一:纯CommandBuffer
这个方法很简单,简单到完全不用shader,C#代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
public class EasyMode : MonoBehaviour
{
public Material material;
public Renderer _renderer;
private CommandBuffer CB;
private void OnEnable()
{
CB = new CommandBuffer();
CB.ClearRenderTarget(false, true, Color.red);
CB.DrawRenderer(_renderer,material);
Camera.main.AddCommandBuffer(CameraEvent.AfterImageEffects, CB);
}
private void OnDisable()
{
Camera.main.RemoveCommandBuffer(CameraEvent.AfterImageEffects,CB);
CB.Clear();
CB.Release();
}
}
效果如图: 主要就是这几句话起作用
CB.ClearRenderTarget(false, true, Color.red);
CB.DrawRenderer(_renderer,material);
Camera.main.AddCommandBuffer(CameraEvent.AfterImageEffects, CB);
第一句话是清除指令,会清楚掉渲染目标的深度缓存(第一个参数)和颜色缓存(第二个参数),第三个参数决定用什么颜色去覆盖渲染目标。 第二句话就是用指定材质绘制渲染器。第三句话就是将该渲染指令添加到摄像机上。
实际应用时你就在动画帧上分别设置开关脚本的事件就可以啦。
方法二:CommandBuffer + 屏幕后处理
虽然方法一很简单易懂,但是还差了下面这个效果: 没有溶解过渡啊!所以我决定还是写个方法二来解决。
思路是这样的:将要渲染的对象印在一张RenderTexture(以下简称RT)上,再用一张噪音图对该RT做“剔除”,形成消融效果。 最终效果长这样: C#代码(挂在摄像机上):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
public class PostProcessing01 : MonoBehaviour
{
public Shader shader;
public Renderer[] renderers;
public Material material;
public Material characterMaterial;
private CommandBuffer CB;
void Start()
{
}
[Range(0.0f, 1.0f)]
public float _Threshold = 0.0f;
private void OnEnable()
{
CB = new CommandBuffer();
foreach (Renderer renderer in renderers)
{ CB.DrawRenderer(renderer, characterMaterial); }
}
private void OnDisable()
{
CB.Clear();
CB.Release();
}
[ImageEffectOpaque]
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
RenderTexture Tex1 = RenderTexture.GetTemporary(source.width, source.height);
material.SetFloat("_Threshold", _Threshold);
Graphics.Blit(source,Tex1, material, 0);
material.SetTexture("_ScreenTex", source);
CB.SetRenderTarget(Tex1);
Graphics.ExecuteCommandBuffer(CB);
Graphics.Blit( Tex1, destination,material,1);
RenderTexture.ReleaseTemporary(Tex1);
}
}
shader代码(角色材质的): (其实就是返回个颜色)
Shader "Unlit/ObjectShader"
{
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert( appdata_base v ) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(0,0,0,1);
}
ENDCG
}
}
}
Shader代码(屏幕的):
Shader "Unlit/ScreenDissolve"
{
Properties
{
_MainTex ("主纹理", 2D) = "white" {}
_NoiseTex ("噪音纹理",2D) = "white" {}
_Threshold ("阙值",Range(0.0,1.0)) = 1.0
_Color ("屏幕主颜色",Color) = (1,1,1,1)
_ScreenTex("屏幕图像",2D) = "white"{}
_Color2("边缘颜色",Color) = (1,1,1,1)
}
SubShader
{
ZWrite Off Cull Off
Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
sampler2D _ScreenTex;
float4 _ScreenTex_TexelSize;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
fixed _Threshold;
fixed4 _Color2;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col1 = tex2D(_MainTex, i.uv);
fixed4 col2 = tex2D(_ScreenTex,i.uv);
fixed4 noise = tex2D(_NoiseTex,i.uv);
if(_Threshold - noise.r >= 0.02)
{
return col1;
}
else if(_Threshold - noise.r < 0)
{
return col2;
}
else
{
return _Color2;
}
}
ENDCG
}
}
}
补充: 1、任何具有ImageEffectOpaque的图像效果都将在不透明物体渲染之后但在透明物体之前渲染。这使得大量使用深度缓冲器(比如SSAO)的效果仅影响不透明像素。该属性可用于通过后期处理减少场景中的视觉瑕疵。(参考来源:Unity官方文档)
2、在OnRenderImage函数中给RT赋值的应该是RenderTexture.GetTemporary(source.width, source.height); 但我不小心写成了GetTemporary(Screen.width, Screen.height)。期间发生了很奇怪很恼火的问题,我经过两个晚上的排查后一直以为这句话是万恶之源。但是后来才发现,Screen的宽高与source的宽高其实一模一样,真正造成我问题的其实是问题3。
3、做消融效果我最早用的是clip函数,理想很丰满,但是clip可能是把原屏幕的片元也一起剔除了,导致我的消融出现了很诡异的效果。所以我决定还是用if语句返回不同的结果实现消融。
|