再次安利一下这个视频,黑神话悟空
这里面战斗都太秀了,难度我凭观看感觉和黑魂血源有的一拼,我感觉我是打不过的,黑魂3的古达就把我虐死二三十次,血源更不说了,小怪我都打不赢,所以还是来实现一下其中的特效,以后可能用到我的游戏中。
这次我们就来实现一下战斗中武器挥动的光波特效,如图:
截图就很不清晰了,还是得直接看视频才行,不过大致能看得出来武器挥动的时候产生一个扇形(弧形)的光波,我们可以分解这个特效如下:
1.挥动的时候生成一个扇形(弧形)的网格
2.基于扇形(弧形)网格面写一个光波的特效shader
接下来做第一步,生成扇形网格面,如下图:
假设AB就是金箍棒,O点为挥动的轴心,金箍棒从A0B0挥动到A1B1,产生一个小弧面,从A1B1挥动到A2B2,又产生一个小弧面,一次内推,我们创建这个n个小弧形组合起来的大弧形网格。
我们根据微分思想,就假设金箍棒AB挥动中每帧的移动非常小,那么就可以认为A0B0B1A1为梯形,这里我们假设A0A1、B0B1为线段,而不是弧线。当然如果有朋友说我们必须追求效果,而不是效率,也可以酌情将A0B0B1A1就当作弧形,再次微分计算弧形的n个小弧形顶点,如下图:
这里我们就当梯形来计算。
using System.Collections.Generic;
using UnityEngine;
public class WeaponWaveTrapezoidMesh : MonoBehaviour
{
[Range(0, 10)]
public int UpdateStep = 2; //跳帧执行
public GameObject weaponWaveGo; //网格体
public Transform A;
public Transform B;
public Material mat;
private int currentStep = 0;
private MeshRenderer wpMeshRender;
private MeshFilter wpMeshFilter;
private Mesh mesh;
private Vector3 currentAPos;
private Vector3 currentBPos;
private List<Vector3> vertexPosList = new List<Vector3>(); //储存每一帧运动后的顶点,B0A0 B1A1 ... Bn-1An-1
void Start()
{
wpMeshRender = weaponWaveGo.GetOrAddComponent<MeshRenderer>();
wpMeshRender.sharedMaterial = mat;
wpMeshFilter = weaponWaveGo.GetOrAddComponent<MeshFilter>();
currentBPos = B.position;
currentAPos = A.position;
vertexPosList.Add(currentBPos);
vertexPosList.Add(currentAPos);
}
void Update()
{
if (currentStep <= 0)
{
if (CheckTransformMoved(B.position, currentBPos) || CheckTransformMoved(A.position, currentAPos))
{
currentBPos = B.position;
currentAPos = A.position;
vertexPosList.Add(currentBPos);
vertexPosList.Add(currentAPos);
UpdateMesh();
}
currentStep = UpdateStep;
}
currentStep--;
}
public void InitMesh()
{
mesh = new Mesh();
currentStep = 0;
}
private void UpdateMesh()
{
if (mesh != null)
{
mesh.Clear();
}
int halfcount = vertexPosList.Count / 2;
//包含的梯形个数
int trapezoidcount = halfcount - 1;
//排序顶点,B0B1...Bn-1A0A1...An-1
Vector3[] vertices = new Vector3[vertexPosList.Count];
for (int i = 0; i < halfcount; i++)
{
int bindex = i * 2;
int aindex = i * 2 + 1;
vertices[i] = vertexPosList[bindex];
vertices[halfcount + i] = vertexPosList[aindex];
}
mesh.vertices = vertices;
//计算拓扑三角
int[] triangles = new int[trapezoidcount * 6];
for (int i = 0; i < trapezoidcount; i++)
{
int bindex = i;
int aindex = halfcount + i;
int triindex = i * 6;
triangles[triindex] = bindex;
triangles[triindex + 1] = bindex + 1;
triangles[triindex + 2] = aindex;
triangles[triindex + 3] = bindex + 1;
triangles[triindex + 4] = aindex + 1;
triangles[triindex + 5] = aindex;
}
mesh.triangles = triangles;
wpMeshFilter.sharedMesh = mesh;
}
public void ClearMesh()
{
currentStep = 0;
vertexPosList.Clear();
wpMeshFilter.sharedMesh = null;
}
/// <summary>
/// 检测transform移动了
/// 移动了才绘制网格
/// </summary>
/// <param name="fpos"></param>
/// <param name="tpos"></param>
/// <returns></returns>
private bool CheckTransformMoved(Vector3 fpos, Vector3 tpos)
{
if (Mathf.Approximately(fpos.x, tpos.x) && Mathf.Approximately(fpos.y, tpos.y) && Mathf.Approximately(fpos.z, tpos.z))
{
return false;
}
return true;
}
}
原理就是检测圆柱体移动后,就更新网格AnBn顶点列表,顺便绘制网格,效果如下:
接下来就是要处理绘制的弧形网格的着色渲染问题,首先我们得处理uv,因为圆柱体挥动本身就是不规则的类弧形,我们计算弧形的映射uv还不如直接处理成长方形的映射uv,如下图:
脑海中想象弧形拉伸展开成一个平面矩形,这样就好得到uv了。
//计算矩形uv
Vector2[] uvs0 = new Vector2[vertexPosList.Count];
float segx = 1f / (float)trapezoidcount; //均分uv.x
for (int i = 0; i < halfcount; i++)
{
Vector2 BnUV = new Vector2(i * segx, 1f);
Vector2 AnUV = new Vector2(i * segx, 0f);
uvs0[i] = BnUV;
uvs0[halfcount + i] = AnUV;
}
mesh.uv = uvs0;
效果如下:
接下来继续,我们需要将网格的渲染做成一个类似之前的“屏幕波动”的效果,所以我们需要将背景采样出来,然后通过像素扰动做效果。
Shader "Weapon/WeaponWaveEffectShader"
{
Properties
{
_MainAlpha("Main Alpha",Range(0,1)) = 1
_OverlayColor("Overlay Color",Color) = (1,1,1,1)
_WaveRange("Wave Range",Range(0,1)) = 0.5
_WavePower("Wave Power",Range(0,200)) = 10
}
SubShader
{
Tags {
"RenderType"="Transparent" "Queue"="Geometry" }
LOD 100
//使用grabtextre来采样背景
GrabPass{
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 spos : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _GrabTexture;
float _MainAlpha; //alpha值
float4 _OverlayColor; //叠加颜色
float _WaveRange;
float _WavePower;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.spos = ComputeGrabScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = float2(i.spos.x/i.spos.w,i.spos.y/i.spos.w);
//以uv.y轴做sin(-1,1)周期加权
//以uv.y为sin的入参,产生周期扰动
uv.y += sin(uv.y*_WavePower)*_WaveRange;
fixed4 col = tex2D(_GrabTexture,uv);
col *= _OverlayColor;
col.a = _MainAlpha;
return col;
}
ENDCG
}
}
}
这里我们使用_GrabPass来采样背景,然后使用ComputeGrabScreenPos计算uv,再通过sin函数对uv做周期的像素扰动,则得到了武器光波的效果,如下:
上面shader的核心也是以前讲过的_GrabPass和ScreenEffect,所以不再详细介绍。
ps:我们也可以使用photoshop制作像素uv扰动的采样图,这样的话像素扰动也更加随机,而且美术人员制作的采样图也更有艺术感。
最后我又看了看黑神话悟空的效果,武器光波上面都有一道白光,这个我就用photoshop实现,如下:
用photoshop拉一个黑色的渐变,我觉得既然是黑神话悟空,那棍影就应该是黑色的,然后在shader中采样叠加。
Shader "Weapon/WeaponWaveEffectShader"
{
Properties
{
_MainAlpha("Main Alpha",Range(0,1)) = 1
_OverlayColor("Overlay Color",Color) = (1,1,1,1)
_WaveRange("Wave Range",Range(0,1)) = 0.5
_WavePower("Wave Power",Range(0,200)) = 10
_SharpTex("Weapon Top Sharp Texture",2D) = "white" {
}
_SharpAlpha("Weapon Top Sharp Alpha",Range(0,1)) = 1
}
SubShader
{
Tags {
"RenderType"="Transparent" "Queue"="Geometry" }
LOD 100
//使用grabtextre来采样背景
GrabPass{
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float2 uv : TEXCOORD0;
float4 vertex : POSITION;
};
struct v2f
{
float2 uv : TEXCOORD1;
float4 spos : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _GrabTexture;
float _MainAlpha; //alpha值
float4 _OverlayColor; //叠加颜色
float _WaveRange;
float _WavePower;
sampler2D _SharpTex; //刀光贴图
float4 _SharpTex_ST;
float _SharpAlpha;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.spos = ComputeGrabScreenPos(o.vertex);
o.uv = TRANSFORM_TEX(v.uv,_SharpTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = float2(i.spos.x/i.spos.w,i.spos.y/i.spos.w);
//以uv.y轴做sin(-1,1)周期加权
//以uv.y为sin的入参,产生周期扰动
uv.y += sin(uv.y*_WavePower)*_WaveRange;
fixed4 col = tex2D(_GrabTexture,uv);
//叠加刀光棍影
//通过lerp进行插值
fixed4 scol = tex2D(_SharpTex,i.uv);
col = lerp(col,scol*_SharpAlpha,scol.a*_SharpAlpha);
col *= _OverlayColor;
col.a = _MainAlpha;
return col;
}
ENDCG
}
}
}
效果如下:
我又看了下黑神话的视频,感觉想做出看起来舒服的武器光波效果,还是得更加注重细节,得美术感艺术感好才能做出好看的效果。
好,后面有时间继续实现雪地的交互效果。