顶点移动 --- 水流效果
顶点移动就是指在shader中真的将顶点进行了偏移,所以模型的顶点数决定了最终效果,顶点数越多,模拟的效果越好,细节越多,模拟什么呢?其实就是模拟一个函数的图像,比如模拟一个2D的水的波纹,哪个函数图像和波纹像呢?说到底就是sin和cos的函数图像像水波纹,所以我们需要将一个长方形平面的边上的点,经过顶点移动后变成sin或者cos的图像即可,这样模型的形状就会被我们改变.顶点移动是改变的形状,想要让里面的贴图动起来,让采样uv动起来即可
Shader "Unity Shaders Book/Chapter 11/Water" {
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_Magnitude ("Distortion Magnitude", Float) = 1
_Frequency ("Distortion Frequency", Float) = 1
_InvWaveLength ("Distortion Inverse Wave Length", Float) = 10
_Speed ("Speed", Float) = 0.5
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _Magnitude;//sin函数的振幅
float _Frequency;//sin函数的频率
float _InvWaveLength;//sin函数的波长
float _Speed;//采样uv的速度
struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v) {
v2f o;
float4 offset;
offset.yzw = float3(0.0, 0.0, 0.0);//只需要在x轴上进行形状改变,所以其他轴偏移量都设置为0即可
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
//简单理解一下吧(详细的在下面),其实只要一个 v.vertex.z * _InvWaveLength就行,因为模型平面是在x和z坐标轴上形成的,模型顶点位置取决于xz,xz轴互相影响
o.pos = UnityObjectToClipPos(v.vertex + offset);//在x轴上模拟sin函数,并进行sin函数偏移
//,这里的_Frequency * _Time.y,为了让x轴上的顶点随时间变化左右移动起来,他后面的一串是为了让他在模型空间下模拟sin函数图像
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
//o.uv += float2(0.0, _Time.y * _Speed);//这里是负责贴图里面是否移动的,而上面是负责改变模型顶点形状并移动的
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
详细讲解一下
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
分析流程
首先经过测试发现去掉 v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength 也能实现同样的效果,代码如下
offset.x = sin(_Frequency * _Time.y + v.vertex.z * _InvWaveLength) * _Magnitude;
那就说明 v.vertex.z * _InvWaveLength起到了影响offset.x的决定性作用,为什么是z轴起到决定性作用,首先我们来看一下模型空间的坐标,点击一下你的模型,蓝色是z轴,红色是x轴,发现这个模型就是在x轴和z轴上生成的一个面片!!!!! 我们xy轴的模型用的太多,导致可能习惯性的认为横着的就是x轴,竖着的就是y轴.在这里就不是.
我们想要将其变成波浪形,只要将顶点进行上下移动即可,在这个模型上,上下移动说明要移动的是x轴,那上下移动多少才能形成图二这种波浪形呢?这个波浪形不就是sin或者cos函数图像吗?我们用sin函数来实现,首先找到sin函数公式
图一
图二
图三
首先找到最基础sin函数公式 y=sinx; 现在我们要弄明白一点,千万不要混淆了,我们要将图一中的顶点上下移动,上下移动在高中时候数学里指的是什么?y坐标,上下移动的叫y坐标,左右的叫x坐标对吧? 所以才有了公式y=sinx. 现在我们遇到了什么?上下移动的是x,左右移动的是z,所以现在的公式是什么?x=sinz;
搞懂了这一点之后,回到我们目标,要求顶点上下移动了多少?
即: offset.x = sin v.vertex.z ; //将原来的offset.x代码那行注释掉,把这个弄上去,跟我一步一步来
根据当前顶点的z轴的值求出他x该移动到的位置,这样之后,恭喜你成功再次得到了和图一模一样的效果,为什么呢?其实我们并没有错,只是力度不够,直接给他扩大2倍看看:
offset.x = 2*sin v.vertex.z;
这样我们就得到了图三这样的一个静态图,并且乘的值越大,它位移的越厉害. 我们可以用一个专门的变量来代替数字2,书里面给他定义的是"_Magnitude",它也有一个专业的名词,叫做振幅.
即:offset.x = _Magnitude*sin v.vertex.z
那怎么让这个函数能做出更多的变化呢?之前我们用的是简单的基础公式y=sinx,为了让他实现更多效果,我们需要用他的高级公式见下图
![](https://img-blog.csdnimg.cn/fa29f3582b104e6ca044f0748f3d8637.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbTBfNjIyMzM3OTA=,size_20,color_FFFFFF,t_70,g_se,x_16)
看到这个公式之后,我们根据我们的实际情况给他做一个替换(把y换成x 把x换成z,我们是根据现有的点的z的位置来求x的位置的)
即: x=Asin(wz+o)+k (有个字母不会打用o代替了,嘻嘻~)
振幅的功能刚才说过了,像什么相位啊,初相啊,偏距啊,你懂吗?反正我不懂,我们只需要知道它们各自在函数中起的作用即可,可以这么理解,数学家给我们发明了一种函数,这个函数图像长得像波浪,而振幅啊,相位啊,初相啊等等就是来调整这个波浪的形状的工具即可,你先别管他们什么意思先按照我下面来
好!我们现在根据公式对号入座,根据规范我在这些变量前加了"_"并且首字母也大写,不要忘记命名规范哦,还得记得在前面加声明,都是float类型,算了~我知道有些人没有手^^:
_W("W", Float) = 1
_O("O", Float) = 1
_K("K", Float) = 1
float _W;
float _O;
float _K;
offset.x = _Magnitude*sin (_W*v.vertex.z+_O)+_K;
图四
好,在我们的不屑努力下成功得到了一个emmmm~ "小路吧",图四效果,刚才我们没有去了解那些功能是干啥的,现在我们得去试试了,把鼠标移动到相应的名字上,按住鼠标左键不放,左右匀速移动,可以改变数值,一个一个的去试试,你就知道它是干嘛用的了,下面是我调好的参数,至此对于模型顶点偏移的工具全部制作完成,接下来就是怎么让他自动动起来
![](https://img-blog.csdnimg.cn/0174a3e90af849869b53e61b6e5b388a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbTBfNjIyMzM3OTA=,size_9,color_FFFFFF,t_70,g_se,x_16)
当我们测试完参数之后你就会发现,O这个变量,可以让我们这个小河有一种左右移动的感觉,我们在回到之前的那个很专业的解释里去看看对O这个解释,
分号前面看不懂,我只看懂了"坐标系上则为图像的左右移动"哈! 这不就对应上我们的测试了么,所以想让水有种动态流动的感觉,就得对他下手,我直接反手单独给它乘以一个_Time.y ,对方心态爆炸,教程结束
现在我们把最基础的模型给实现了,再把它原来的那个公式拿出来对比一下,为了方便对比,我们将位置调整一下
offset.x = sin( _O * _Time.y + v.vertex.z* _W ) *_Magnitude + _K;
offset.x = sin(_Frequency * _Time.y + v.vertex.z * _InvWaveLength) * _Magnitude;
按照一 一对应的关系我们得到
_O -> _Frequency
_W -> _InvWaveLength
我们突然会发现这两个公式就是一摸一样,而且我们还多了一个K的功能,K的功能的确在做这个例子的时候没啥用,所以他省略了,此时我们再拿出那个一脸蒙蔽的公式做对比
offset.x = sin(_Frequency * _Time.y + v.vertex.z * _InvWaveLength) * _Magnitude;
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
第一个公式 v.vertex.z * _InvWaveLength 我们已经推导出来了,简单的说就是根据现有的顶点的z坐标,来计算出x坐标的位置,
第二个公式 v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength+ v.vertex.z * _InvWaveLength ,同理,根据现有顶点的x,y,z坐标共同计算x坐标的偏移量
在我们这个例子中,根据之前的公式得出实际上只有v.vertex.z 才能真正影响到x坐标的偏移量,那为什么要把这两个v.vertex.x 和 v.vertex.y没用的东西加上去呢?我认为这只是一个用惯了的通用公式
我们这个模型平面是在x,z轴上建立的,所以z轴影响x轴偏移,那么现在给我们一个x,y轴上建立的平面模型怎么办?此时如果我们shader不写那两个没用的东西,我们就会得到错误的效果,反正为了shader能在更多的不同轴形成的平面上正常运行,加上就行了,反正这又不影响正确结果的运算