Unity制作力场护盾
大家好,我是阿赵。
继续做护盾,这一期做一个力场冲击波护盾。
一、效果展示
主要的效果并不是这个球,而是护盾在被攻击的时候,会出现一个扩散的冲击波。比如上图在右边出现了冲击波
如果在左边被攻击,冲击波会出现在左边,并且慢慢扩散,渐隐消失。
二、原理
把用于装饰的球体去掉之后,实际上我们要做的事情其实很简单,就是要计算一个沿着球体的环形范围显示。
1、准备工作
这里我们需要几个东西
1.一个球形模型
用于获取碰撞点,还有显示冲击波
2.碰撞点的坐标
比如我们用鼠标点击来模拟攻击护盾,那么从屏幕的鼠标位置发射射线碰撞到上面的球体,得到一个球体上的坐标,用于计算扩散的中心点
3.扩散范围的大小
在每次点击护盾获取到碰撞点之后,这个值应该是动态改变的,从小到大,用于控制扩散的幅度
4.一张渐变图
这张渐变图其实就是上面看到的环形效果所采样的贴图了。
2、计算过程
接下来看看是怎样计算的
1.计算碰撞点的坐标和球体的顶点坐标的距离distance
2.距离distance减去一个size,得到一个扩散范围range
3.扩散范围除以一个扩散值diffuse,用于扩散范围的拉伸
4.上面得到的值,作为UV坐标的u坐标,然后v坐标是0.5。其实v坐标是0-1随便一个值都可以,因为 我们的渐变图只有x轴有变化。用这个uv值去采样渐变图,就能得到一个沿着球体的环形贴图效果
通过控制size的大小,就能让这个 环形从小到大的变化。
如果觉得这个环太整齐了,可以在第2步计算范围之后,再减去一个噪声图采样,这样,环形的边缘就会出现不规则的变化
看着好像很复杂的效果,其实做起来是很简单的。接下来写一个很简单的C#脚本,获取碰撞点,把坐标传入到材质球里面,然后在C#写一个size从小到大变化的控制,让环形从小到大的扩散,最后消失就可以了。
3、特别说明
1.多个冲击波
上面这样做之后,我们每次就只能点击出一个冲击波,如果点击第二个的时候,第一个会消失。为了可以多个冲击波同时存在,可以通过C#端的SetVectorArray方法和SetFloatArray方法使用数组去传冲击波中心点和扩散的size,然后在shader里面用循环的方式计算多个中心点和扩散size,得到多个冲击波同时出现的效果
2.三平面采样
然后还有一个需要说明的是,如果我们单纯的用UV坐标去采样噪声图,在球形的UV接缝位置,会出现断裂的情况。所以我这里使用了一个三平面采样的技术
inline float4 TriplanarSampling44(sampler2D topTexMap, float3 worldPos, float3 worldNormal, float falloff, float2 tiling, float3 normalScale, float3 index)
{
float3 projNormal = (pow(abs(worldNormal), falloff));
projNormal /= (projNormal.x + projNormal.y + projNormal.z) + 0.00001;
float3 nsign = sign(worldNormal);
half4 xNorm; half4 yNorm; half4 zNorm;
xNorm = tex2D(topTexMap, tiling * worldPos.zy * float2(nsign.x, 1.0));
yNorm = tex2D(topTexMap, tiling * worldPos.xz * float2(nsign.y, 1.0));
zNorm = tex2D(topTexMap, tiling * worldPos.xy * float2(-nsign.z, 1.0));
return xNorm * projNormal.x + yNorm * projNormal.y + zNorm * projNormal.z;
}
使用起来也很简单
直接使用UV坐标采样,一般是这样写:
float2 noiseUV = i.uv*_noiseTex_ST.xy + _noiseTex_ST.zw;
float4 noiseCol = tex2D(_noiseTex, noiseUV);
现在改成
float4 noiseCol = TriplanarSampling44(_noiseTex, i.vertex_world, i.normal_world, 1.0, _noiseTilling, 1, 0);
3.装饰球体的制作
最后,那个用于装饰用的球,其实Shader代码更简单,就是一个菲涅尔边缘光,加上一个Flow流动效果,用一张噪声图做流动而已。Flow的源码在上一篇闪电护盾给过了,所以这里就不给了,各位有兴趣可以自己试试。
三、代码
c#代码就不给了,实在太简单,就是一个射线拾取碰撞点的代码。
给一下Shader的源码吧:
Shader "azhao/ShockWave"
{
Properties
{
_diffuse("kuosan",float) = 0
_noiseTex("noiseTex",2D) = "black"{}
_gradient("gradientTex",2D) = "black"{}
_baseColor("baseColor",Color) = (1,1,1,1)
_noisePow("noisePow",float) = 1
_addSize("addSize",float) = 0
_divSize("divSize",float) = 1
_noiseTilling("noiseTilling",Vector) = (1,1,1,1)
_hitSpeed("hitSpeed",float) = 10
}
SubShader
{
Tags { "Queue"="Transparent" }
LOD 100
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 vertex_world:TEXCOORD1;
float3 normal_world:TEXCOORD2;
};
float3 _hitCenter[10];
float _hitSize[10];
float _diffuse;
sampler2D _noiseTex;
float4 _noiseTex_ST;
sampler2D _gradient;
float4 _baseColor;
float _noisePow;
float _addSize;
float _divSize;
float2 _noiseTilling;
float _hitSpeed;
inline float4 TriplanarSampling44(sampler2D topTexMap, float3 worldPos, float3 worldNormal, float falloff, float2 tiling, float3 normalScale, float3 index)
{
float3 projNormal = (pow(abs(worldNormal), falloff));
projNormal /= (projNormal.x + projNormal.y + projNormal.z) + 0.00001;
float3 nsign = sign(worldNormal);
half4 xNorm; half4 yNorm; half4 zNorm;
xNorm = tex2D(topTexMap, tiling * worldPos.zy * float2(nsign.x, 1.0));
yNorm = tex2D(topTexMap, tiling * worldPos.xz * float2(nsign.y, 1.0));
zNorm = tex2D(topTexMap, tiling * worldPos.xy * float2(-nsign.z, 1.0));
return xNorm * projNormal.x + yNorm * projNormal.y + zNorm * projNormal.z;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.vertex_world = mul(v.vertex, unity_ObjectToWorld);
o.normal_world = mul(unity_WorldToObject, v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float alpha = 0;
for (int j = 0;j < 10;j++)
{
float dis = distance(_hitCenter[j], i.vertex_world);
float hitMaskVal = (1 - (dis - _addSize)) / (max(_divSize, 0.001f));
float hitSizeVal = (dis - (_hitSize[j] * _hitSpeed)) / _diffuse;
//float2 noiseUV = i.uv*_noiseTex_ST.xy + _noiseTex_ST.zw;
//float4 noiseCol = tex2D(_noiseTex, noiseUV);
float4 noiseCol = TriplanarSampling44(_noiseTex, i.vertex_world, i.normal_world, 1.0, _noiseTilling, 1, 0);
float noisePowerVal = pow(noiseCol.r, _noisePow);
float hitRange = clamp(hitSizeVal - noisePowerVal, 0, 1);
float2 jianbianUV = float2(hitRange, 0);
float4 jianbianCol = tex2D(_gradient, jianbianUV);
alpha = alpha + clamp(hitMaskVal* jianbianCol.r, 0, 1);
}
alpha *= _baseColor.a;
return float4(_baseColor.rgb, alpha);
}
ENDCG
}
}
}