我们先来写顶点动画的shader。在写这个shader的时候我们需要从我们之前写的纹理贴图的那个shader开始进行修改。还记得我们们的纹理贴图最重要的函数么,就是顶点着色器重的TRANSFROM_TEX(uv,Tex)与片元着色器中的tex2d(Tex,uv);第一个保证了贴图在物体上的正确呈现,第二个保证了在屏幕中正确呈现的效果所以我们的基本架构可以这样
<pre class="wp-block-syntaxhighlighter-code">Shader "Custom/VertexAnimationShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
}
SubShader
{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_St;
struct a2v {
float4 vertex:POSITION;
float2 texcoord:TEXCOORD0;
};
struct v2f {
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
};
v2f vert(a2v v){
v2f o;
o.pos=UnityViewToClipPos(v.vertex);
o.uv=v.texcoord;
return o;
}
fixed4 frag(v2f i):SV_TARGET{
return tex2D(_MainTex,i.uv);
}
ENDCG
}
}
FallBack "Diffuse"
}</pre>
以上是我们的贴图基础shader。下面就是要思考我们需要什么,我们想呈现一中什么样的运动。在这里我们想要进行想有周期性的运动,随时间的变化而规律变化。所以我们在这时候应该要想到我们初中所学的sin函数。完美符合这个条件。
我们将其放在三维空间中
这时候我们要引入一个内置参数跟两个内置函数,分别是_Time,distance,sin这三个内置函。首先是_Time这个参数他的xyzw的分量上分别是t/20,t,t*2,t*3这四个内置时间,开始时间为程序开始运行的时间,单个参数类型是float,而函数distance(a,b)返回的值是a,b两点的距离,sin(x)返回的则是在sin函数上的值。所以我们只需要将sin与_Time.y相结合就能得到我们需要的效果。一个简单的方法就是计算每个点到中心的距离再乘以某个值来控制频率,加上_Time.z,最后乘以一个书达到控制幅度的效果。我们暂时不评价这个方法好不好,先实现,然后我们再修改。
首先根据这个思路我们写一下伪代码
dis=distance(v.vertex.xyz,float3(0,0,0));
h=sin(dis*_Frequence+_Time.z)*_Magnitude
所以我仅仅需要改变其顶点运动轨迹便可
Shader "Custom/VertexAnimationShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Main Tex", 2D) = "white" {} _Frequence("Frequence",Range(0,8))=2 _Magnitude("Magnitude",Range(0,8))=0.1 } SubShader { Tags{"RenderType"="Qpaque"} Pass{ CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float4 _MainTex_ST; float _Frequence; float _Magnitude; struct a2v { float4 vertex:POSITION; float2 texcoord:TEXCOORD0; }; struct v2f { float4 pos:SV_POSITION; float2 uv:TEXCOORD0; }; v2f vert(a2v v){ v2f o; float dis=distance(v.vertex.xyz,float3(0,0,0)); float h=sin(dis*_Frequence+_Time.z)*_Magnitude; o.pos=mul(unity_ObjectToWorld,v.vertex); o.pos.y=h; o.pos=mul(unity_WorldToObject,o.pos); o.pos=UnityObjectToClipPos(o.pos); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); return o; } fixed4 frag(v2f i):SV_TARGET{ fixed4 col=tex2D(_MainTex,i.uv); return col; } ENDCG } } FallBack "Diffuse" }
我们重点来解释下46行47,48行的意义,首先我们想要再世界坐标系中让他做sin 的循环运动,如果在它自身做的话也不是不可以,想做可以试试,挺有意思的。所以我们首先将物体的顶点坐标转换至世界坐标系中,然后我们将世界坐标中的y分量换成我们的变换后的分量h,再将变换转会到他的自身坐标。最后将其整体输出到裁剪屏幕。
接着我们进行UV动画的shader制作其实思路是一样的,上述的顶点动画是通过_Time改变顶点坐标的xyz分量,同理我们只要在片元着色器中修改UV的坐标分量就行了,
float2 offset=float2(0,0);
offset.x=_Time.y*_Speed;
offset.y=_Time.y*_Speed;
fixed4 tex=tex2D(_MovingTex,i.uv+offset);
Shader "Custom/VertexAnimationShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Main Tex", 2D) = "white" {} _SubTex("Sub Tex"2D)="white"{} _Speed("Speed",Range(0,8))=2 } SubShader { Tags{"RenderType"="Qpaque"} Pass{ CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; sampler2D _SubTex; float4 _SubTex_ST; float4 _MainTex_ST; float _Speed; struct a2v { float4 vertex:POSITION; float2 texcoord:TEXCOORD0; }; struct v2f { float4 pos:SV_POSITION; float2 uv:TEXCOORD0; }; v2f vert(a2v v){ v2f o; o.pos=UnityObjectToClipPos(o.pos); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); return o; } fixed4 frag(v2f i):SV_TARGET{ float2 offset=float2 (0,0); offset.x=_Time.y*_Speed; offset.y=_Time.y*_Speed; fixed4 lightcol=tex2D(_SubTex,i.uv+offset); fixed4 col=tex2D(_MainTex,i.uv); return col+lightcol ; } ENDCG } } FallBack "Diffuse" }
..最近才知道一个噩耗,就是unity自己的HRDP跟UE4一样无法在操作面修改管线。那我为什么不去修改UE4的管线呢。。shadergraph跟UE4的材质编辑器我依然是能做就做。但总觉得U3D吃枣药丸。唯一的优势都尼玛放弃了。
草的UV扰动动画
Shader "Custom/GrassVertexAni" { Properties { _MainTex ("Texture", 2D) = "white" {} _Noise("Noise", 2D) = "black" {} _WindControl("WindControl(x:XSpeed y:YSpeed z:ZSpeed w:windMagnitude)",vector) = (1,0,1,0.5) //前面几个分量表示在各个轴向上自身摆动的速度, w表示摆动的强度 _WaveControl("WaveControl(x:XSpeed y:YSpeed z:ZSpeed w:worldSize)",vector) = (1,0,1,1) //前面几个分量表示在各个轴向上风浪的速度, w用来模拟地图的大小,值越小草摆动的越凌乱,越大摆动的越整体 } SubShader { Tags { "Queue" = "Transparent"} Pass { //Tags{"LightMode"="ForwardBase"} //ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float2 uv : TEXCOORD0; float4 pos : SV_POSITION; //float3 tempCol : TEXCOORD1;//用来测试传递noise贴图采样的结果 }; sampler2D _MainTex; sampler2D _Noise; half4 _WindControl; half4 _WaveControl; v2f vert (appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); float2 samplePos = worldPos.xz / _WaveControl.w; samplePos += _Time.x * -_WaveControl.xz; fixed waveSample = tex2Dlod(_Noise, float4(samplePos, 0, 0)).r; worldPos.x += sin(waveSample * _WindControl.x) * _WaveControl.x * _WindControl.w * v.uv.y; worldPos.z += sin(waveSample * _WindControl.z) * _WaveControl.z * _WindControl.w * v.uv.y; o.pos = mul(UNITY_MATRIX_VP, worldPos); o.uv = v.uv; //o.tempCol = waveSample; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); //return fixed4(frac(i.tempCol.x), 0, 0, 1); return col; } ENDCG } } }