最近bilibili看了黑神话悟空的UE5演示视频,感觉是真牛逼,地址:黑神花悟空UE5实机演示视频
遥想我也算是国内第一批用ue4的开发者了,15年开始用ue4.7源码版,做了一年多就又用回u3d了,哈哈,主要因为ue4不知道为何总是很慢,从c++模板工程开始编译启动慢、umap大了保存慢、c++compile慢、shader compile慢、打包编译就更慢了,还进行exit with code 0。
不知道ue4或者ue5现在改版得怎么样了,应该已经解决上面的问题了吧。
言归正传,我看完黑神话悟空的视频后,觉得整体画质和战斗感是真不错,我感觉我是做不到这么漂亮的画质了。不过其中两个功能点我可能用的上,所以用u3d实现一下:
1.战斗过程中的屏幕波爆特效
2.跑动中雪地的交互特效
这篇博客主要实现第一个屏幕波爆特效,先看看截图:
截的图比较模糊,视频2:50左右,打到小怪物后一个屏幕的波纹爆动,这就很有战斗感觉了。
我们可以把屏幕画面想象成一张图片,在图片的中心点为圆心,半径r为半径的圆包含的像素数组,对这个像素数组进行uv上的操作,具体要什么样的操作我们得思考一下,如下:
假设圆心黑点就是打击中心,圆的区域就是我们要操作的像素数组,画面呈现效果就是类似圆心的像素向外“扩散”,如下图:
脑海中想象以圆心为起点,四面八方发散的像素移动一段距离,会不会形成波爆效果?我们得写代码试一试,那么像素移动怎么实现呢?其实也就是像素所在的uv远离圆心。
先写一个imageeffectshader尝试一下。
Shader "ScreenEffect/WaveEffectShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
[Enum(Off,0,On,1)]_OffOn("Effect Off On",int) = 0
_UVCenter("UV Texcoord Center",vector) = (0.5,0.5,0,0)
_UVRadius("UV Circle Radius",Range(0,1)) = 0.1
_UVStep("UV Step",Range(0,1)) = 0.1
_WavePower("Wave Power",Range(0,0.2)) = 0.01
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
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;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
int _OffOn;
float4 _UVCenter;
float _UVRadius;
float _UVStep;
float _WavePower;
fixed4 frag (v2f i) : SV_Target
{
if(_OffOn == 1)
{
float2 uv = i.uv-_UVCenter.xy; //_UVCenter指向当前片段uv的向量
float dist2 = uv.x*uv.x+uv.y*uv.y; //向量uv长度的平方
float radius2 = _UVRadius*_UVRadius; //圆半径的平方
if(dist2<radius2)
{
float step = dist2/radius2;
if(step<_UVStep)
{
//判断当前片段uv在圆内,且step比例小于我们设定的uvstep
//则i.uv加权”一个朝外的向量“,形成像素朝外移动的效果
i.uv += uv*_WavePower;
}
}
}
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
效果如下:
这里说一下原理,就是确定一个屏幕uv,设定一个圆形范围,如果片段在圆形范围内,则根据片段到圆心的长度进行步长的uv加权,加权的向量则是圆心朝向片段的“朝外向量”,而uv的加权则导致像素的变化,形成一种向外爆发的效果。
不过因为屏幕是有横纵比例的,但是uv却是1:1,所有我们还得修改到标准的圆形。
Shader "ScreenEffect/WaveEffectShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
[Enum(Off,0,On,1)]_OffOn("Effect Off On",int) = 0
_UVCenter("UV Texcoord Center",vector) = (0.5,0.5,0,0)
_UVRadius("UV Circle Radius",Range(0,1)) = 0.1
_UVStep("UV Step",Range(0,1)) = 0.1
_WavePower("Wave Power",Range(0,0.2)) = 0.01
_ScreenAspect("Screen Width/Height",float) = 1
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
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;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
int _OffOn;
float4 _UVCenter;
float _UVRadius;
float _UVStep;
float _WavePower;
float _ScreenAspect; //外部传入屏幕比例 宽度/高度
fixed4 frag (v2f i) : SV_Target
{
if(_OffOn == 1)
{
float2 uv = i.uv-_UVCenter.xy; //_UVCenter指向当前片段uv的向量
//也可以用_ScreenParams,当然直接外部一次计算aspect更好
float dist2 = uv.x*_ScreenAspect*uv.x*_ScreenAspect+uv.y*uv.y; //向量uv长度的平方
float radius2 = _UVRadius*_UVRadius; //圆半径的平方
if(dist2<radius2)
{
float step = dist2/radius2;
if(step<_UVStep)
{
//判断当前片段uv在圆内,且step比例小于我们设定的uvstep
//则i.uv加权”一个朝外的向量“,形成像素朝外移动的效果
i.uv += uv*_WavePower;
}
}
}
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
创建一个ScreenAspect(width/height)的比例(16:9),然后在uv.x处理中ScreenAspect,则可以将uv.x校正到uv.y*(9/16)的范围内,形成正圆,如下:
接下来用c#代码进行效果操作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class WaveEffectCtrl : MonoBehaviour
{
public Material waveEffectMat;
[Range(0, 1f)]
public float waveTime = 1f;
public AnimationCurve waveCurve;
void Start()
{
float sp = (float)Screen.width / (float)Screen.height;
waveEffectMat.SetFloat("_ScreenAspect", sp);
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination, waveEffectMat);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector2 spos = Input.mousePosition;
Vector4 uvpos = new Vector4(spos.x / Screen.width, spos.y / Screen.height, 0, 0);
waveEffectMat.SetVector("_UVCenter", uvpos);
waveEffectMat.SetInt("_OffOn", 1);
float step = 0f;
DOTween.To(() => step, x => step = x, 1f, waveTime).OnUpdate(() =>
{
waveEffectMat.SetFloat("_UVStep", step);
}).OnComplete(() =>
{
waveEffectMat.SetInt("_OffOn", 0);
}).SetEase(waveCurve);
}
}
}
每次点击屏幕,则形成一个波爆效果,顺便用dotween控制shader的参数,如下:
感觉怎么差点意思?我又看了看黑神话悟空的视频,感觉就是波爆圆内,像素好像更扭曲一些,而我的就一个uv朝向加权,效果还不是特别好,继续改进一下,比如添加一个sin进行扰动。
Shader "ScreenEffect/WaveEffectShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
[Enum(Off,0,On,1)]_OffOn("Effect Off On",int) = 0
_UVCenter("UV Texcoord Center",vector) = (0.5,0.5,0,0)
_UVRadius("UV Circle Radius",Range(0,1)) = 0.1
_UVStep("UV Step",Range(0,1)) = 0.1
_WavePower("Wave Power",Range(0,0.5)) = 0.01
_WaveRange("Wave Range",Range(1,1000)) = 5
_WaveSpeed("Wave Speed",Range(1,100)) = 10
_ScreenAspect("Screen Width/Height",float) = 1
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
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;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
int _OffOn;
float4 _UVCenter;
float _UVRadius;
float _UVStep;
float _WavePower;
float _WaveRange;
float _WaveSpeed;
float _ScreenAspect; //外部传入屏幕比例 宽度/高度
fixed4 frag (v2f i) : SV_Target
{
if(_OffOn == 1)
{
float2 uv = i.uv-_UVCenter.xy; //_UVCenter指向当前片段uv的向量
//也可以用_ScreenParams,当然直接外部一次计算aspect更好
float dist2 = uv.x*_ScreenAspect*uv.x*_ScreenAspect+uv.y*uv.y; //向量uv长度的平方
float radius2 = _UVRadius*_UVRadius; //圆半径的平方
if(dist2<radius2)
{
float step = dist2/radius2;
if(step<_UVStep)
{
//判断当前片段uv在圆内,且step比例小于我们设定的uvstep
//则i.uv加权”一个朝外的向量“,形成像素朝外移动的效果
//加权的乘积中使用sin和time控制一个波动的加权
i.uv += uv*_WavePower*sin(dist2*_WaveRange+_Time.y*_WaveSpeed);
}
}
}
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
这里我在“朝外”向量加权的基础上又叠加了一个sin的“波动”乘积,使用time来控制sin的偏移,效果如下:
还是没有黑神话悟空的波爆有感觉,我感觉黑神话悟空中屏幕波爆,uv像素的偏移更加随机,我猜测可能用的noise贴图进行的像素uv加权。同时我这边animationcurve和speed、range、power参数也得好好调一调。
好,今天就到这里,准备洗了睡了,下一篇继续实现黑神话悟空的武器挥动波动特效,他武器挥动的战斗感做的也挺不错的。