UnityShader内置的时间变量(时间篇)
纹理动画
序列帧动画
Shader "Custom/ImageSequency"
{
Properties
{
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Image Sequency", 2D) = "white" {}
_HorizontalAmount ("Horizontal Amount", Float) = 4
_VerticalAmount ("Vertical Amount", Float) = 4
_Speed ("Speed", Range(1, 100)) = 30
}
SubShader
{
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass
{
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _HorizontalAmount;
float _VerticalAmount;
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(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float time = floor(_Time.y * _Speed);
float row = floor(time / _HorizontalAmount);
float column = time - row * _HorizontalAmount;
// half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount);
// uv.x += column / _HorizontalAmount;
// uv.y -= row / _VerticalAmount;
half2 uv = i.uv + half2(column, -row);
uv.x /= _HorizontalAmount;
uv.y /= _VerticalAmount;
fixed4 c = tex2D(_MainTex, uv);
c.rgb *= _Color;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
滚动的背景
Shader "Custom/LoopBg"
{
Properties
{
_MainTex ("Base Layer (RGB)", 2D) = "white" {}
_DetailTex ("2nd Layer (RGB)", 2D) = "white" {}
_ScrollX ("Base layer Scroll Speed", Float) = 1.0
_Scroll2X ("2nd layer Scroll Speed", Float) = 1.0
_Multiplier ("Layer Multiplier", Float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _DetailTex;
float4 _MainTex_ST;
float4 _DetailTex_ST;
float _ScrollX;
float _Scroll2X;
float _Multiplier;
struct a2v
{
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
};
v2f vert (a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 firstLayer = tex2D(_MainTex, i.uv.xy);
fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw);
fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);
c.rgb *= _Multiplier;
return c;
}
ENDCG
}
}
FallBack "VertexLit"
}
_MainTex和_DetailTex分别是第一层和第二层的背景纹理,_ScrollX和_Scroll2X对应了各自的水平滚动速度。_Multiplier参数则用于控制纹理的整体亮度。
在进行顶点变换时,把顶点从模型空间变换到裁剪空间中。然后我们计算了两层背景纹理的纹理坐标,然后利用_Time.y在水平方向上对纹理坐标进行偏移,以此达到滚动的效果。
在片元着色器中,首先分别利用i.uv.xy和i.uv.zw对两张背景纹理进行采样。然后,使用第二层纹理的透明通道来混合两张纹理。最后使用_Multiplier参数和输出颜色进行相乘,以调整背景亮度。
顶点动画
流动的河流
Shader "Custom/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;
float _Frequency;
float _InvWaveLength;
float _Speed;
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);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
o.pos = UnityObjectToClipPos(v.vertex + offset);
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"
}
使用正弦函数模拟水流。
_MainTex是河流纹理,_Color用于控制整体颜色,_Magnitude用于控制水流波动的幅度,_Frequency用于控制波动频率,_InvWaveLength用于控制波长的倒数,_Speed用于控制河流纹理的移动速度。
注意需要设置SubShader标签DisableBatching,不能使用批处理,这是因为批处理会合并所有相关的模型,而这些模型各自的模型空间就会丢失。而在本例中我们需要在物体的模型空间下对顶点位置进行偏移。
在顶点着色器中,对顶点的x方向进行位移,利用_Frequency属性和内置的_Time.y变量来控制正弦函数的频率,并加上模型空间下的位置位置分量,并乘以_InvWaveLength来控制波长,最后,对结果乘以_Magnitude属性来控制波动幅度,得到最终位移,加到顶点位置上进行正常得分顶点变换即可。
广告牌
Shader "Custom/Board"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1
}
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 "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _VerticalBillboarding;
struct a2v
{
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (a2v v)
{
v2f o;
float3 center = float3(0, 0, 0);
float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1));
float3 normalDir = viewer - center;
normalDir.y =normalDir.y * _VerticalBillboarding;
normalDir = normalize(normalDir);
float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
float3 rightDir = normalize(cross(upDir, normalDir));
upDir = normalize(cross(normalDir, rightDir));
float3 centerOffs = v.vertex.xyz - center;
float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;
o.pos = UnityObjectToClipPos(float4(localPos, 1));
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 c = tex2D (_MainTex, i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
在顶点着色器中,首先选择模型空间中的原点作为广告牌的锚点,利用内置变量获取模型空间下的视角位置。
开始计算3个正交矢量,根据观察位置和锚点计算目标法线方向,并根据_VerticalBillboarding属性来控制垂直方向上的约束度。
接着得到粗略的向上方向,如果法向方向的单位矢量的y接近与1了向上,那么取(0,0,1),否则为(0,1,0)。
然后根据法线方向和粗略的向上方向得到向右的方向,归一化,最后,再根据法线方向和右方向得到向上方向。
这样,三个方向上的正交基向量就可以得到了,再根据原始位置相对于锚点的偏移量以及3个正交基向量,以得到新的顶点位置。
最后完成顶点位置变换。
需要注意的是,在模型空间中增加顶点动画,往往画会通过SubShader的DiableBatching标签来强制取消批处理,增加Dc,因此尽量避免显示使用迷哦行空间下的一些绝对位置和方向来进行计算。
在广告牌的例子中,我们可以利用顶点颜色来存储每个顶点到锚点的距离值。
如果想要为添加了顶点动画的物体添加阴影,无法使用内置的Diffuse等包含的阴影Pass来渲染,无法向其他物体正确地投射阴影,这是因为Unity的阴影绘制需要调用一个ShaderCasterPass,而如果直接使用这些内置的Pass并没有进行相关的顶点动画。
添加阴影的水流动画
Shader "Custom/VertexAnimationWithShadow"
{
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
{
// Need to disable batching because of the vertex animation
Tags {"DisableBatching"="True"}
Pass
{
Tags { "LightMode"="ForwardBase" }
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;
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);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
o.pos = UnityObjectToClipPos(v.vertex + offset);
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
}
// Pass to render object as a shadow caster
Pass
{
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;
struct v2f
{
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f o;
float4 offset;
offset.yzw = float3(0.0, 0.0, 0.0);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
v.vertex = v.vertex + offset;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
fixed4 frag(v2f i) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "VertexLit"
}
在自定义投射阴影的Pass中,我们通常会使用Unity提供的内置宏V2F_SHADOW_CASTER,TRANSFER_SHADOW_CASTER_NORMALOFFSET和SHADOW_CASTER_FRAGMENT来计算阴影投射的各种变量。