动画效果往往都是把时间添加到一些变量的计算中,以便在时间发生变化时,变量也发生变化,从这些变量控制的画面也会随之发生变化,unity shader中常见的时间内置变量如下:
序列帧动画:就是从序列帧纹理中,获取当前时间播放关键帧的纹理采样坐标,从序列帧纹理中进行采样,获取当前播放的帧颜色。参考代码如下:
Shader "Custom/ImageSequence" {
Properties {
_Color("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0) // 序列帧纹理颜色变量
_MainTex("MainTex", 2D) = "white"{} // 序列帧纹理对象
_HorizontalAmount("Horizontal Amount", Float) = 8 // 序列帧纹理行关键帧个数
_VerticalAmount("Vertical Amount", Float) = 8 // 序列帧纹理列关键帧个数
_Speed("Speed", Range(0.0, 100.0)) = 30 // 序列帧播放速度
}
SubShader {
// 使用透明度混合渲染标签
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
Pass {
// 当前渲染流程中使用前向渲染路径,将光照信息存储到对应变量中
Tags { "LightModel" = "ForwardBase" }
ZWrite Off // 关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha // 打开混合功能,并且设置纹理和像素缓冲区的混合因子,alpha分别使用rgb的设置
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 i) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
// 获取纹理采样坐标,等价与:o.uv = i.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw
o.uv = TRANSFORM_TEX(i.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// 获取当前时间播放的总帧数
float total = floor(_Time.y * _Speed);
// 获取当前播放帧的行列数
float row = floor(total / _HorizontalAmount);
float column = total - row * _HorizontalAmount;
// 获取基于行列的每一关键帧的起始采样坐标
half2 uv = float2(i.uv.x / _HorizontalAmount, i.uv.y / _VerticalAmount);
// 获取水平方向当前关键帧的采样坐标[0, 1]
uv.x += column / _HorizontalAmount;
// 由于纹理坐标向上为正,而纹理帧中向下为正,所以获取垂直方向当前关键帧的采样坐标[0, 1]时,应该相减
uv.y -= row / _VerticalAmount;
// 采样当前关键帧的纹理数据信息
fixed4 c = tex2D(_MainTex, uv);
c.rgb *= _Color;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
滚动背景动画:就是从背景纹理中,获取当前时间对应的背景滚动距离采样坐标,并用这个采样坐标从纹理中进行采样,从而获取滚动的颜色。参考代码如下
Shader "Custom/ScrollBacground" {
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; // 纹理采样坐标,xy表示远背景纹理采样坐标 zw表示近纹理采样坐标
};
v2f vert (a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// 获取远纹理采样坐标[0, 1],通过纹理采样起始坐标和水平滚动的距离小数部分偏移值进行累加
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);
// 获取近纹理采样坐标[0, 1],通过纹理采样起始坐标和水平滚动的距离小数部分偏移值进行累加
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);
// 使用近背景纹理的a通道来混合远近采样像素,得到插值结果
fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);
// 调整当前时间混合后像素的亮度
c.rgb *= _Multiplier;
return c;
}
ENDCG
}
}
FallBack "Diffuse" // 默认漫反射
}
流水动画:就是从河流纹理中,获取当前时间移动距离的采样坐标,利用该采样坐标从河流纹理中进行采样,得到河流颜色,并且在顶点着色器中,利用正弦函数特性来模拟河流纹波动的效果。参考代码如下:
Shader "Custom/Water" {
Properties {
_MainTex("Main Tex", 2D) = "white" {} // 河流纹理对象
_MainColor ("Main Color", Color) = (1.0, 1.0, 1.0, 1.0) // 控制整体颜色
_Magnitude("Magnitude", Float) = 1 // 控制水流波动幅度
_Frequency("Frequency", Float) = 1 // 控制水流波动频率
_InvWaveLength("InvWaveLength", Float) = 1 // 控制水流波长的倒数
_Speed("Speed", Float) = 0.5 // 控制河流纹理移动速度
}
SubShader {
// 指定透明度混合渲染标签,由于河流流动动画需要在顶点着色器中用顶点模型空间中的偏移来实现,
// 而批处理会合并模型,并且合并后的模型对应的模型空间会消失,所以我们应该关掉批处理
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "DisableBatching" = "True" }
Pass {
Tags { "LightModel" = "ForwardBase" } // 设置当前Pass使用前向渲染路径,并将光照信息填充到对应的变量中
ZWrite Off // 透明度混合时,关掉深度写入,避免透明物体在不透明物体前面时,不透明物体离相机远而被裁剪掉,从而看不到半透明效果
Blend SrcAlpha OneMinusSrcAlpha // 使用混合因子将片元透明度和像素缓冲区中的颜色进行相乘,并默认按照叠加的方式进行混合
Cull Off // 关掉图元裁剪,使图元的前后面都显示出来
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 定义材质检视面板中关联的属性变量
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
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 i) {
// 获取河流波动偏移,yzw方向不波动,x代表的水平方向进行波动,并用sin函数实现波动偏移
float4 offset;
offset.yzw = float3(0.0, 0.0, 0.0);
offset.x = sin(_Frequency * _Time.y + i.vertex.x * _InvWaveLength + i.vertex.y * _InvWaveLength + i.vertex.z * _InvWaveLength) * _Magnitude;
// 获取顶点波动后齐次空间坐标
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, i.vertex + offset);
// 获取河流纹理当前时间滚动采样坐标
o.uv = TRANSFORM_TEX(i.texcoord, _MainTex);
o.uv += float2(0.0, _Speed * _Time.y);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// 使用河流采样坐标进行采样,并与整体颜色进行相乘
fixed4 c = tex2D(_MainTex, i.uv);
c.rgb *= _MainColor.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
广告牌动画:就是通过控制视角方向,利用旋转矩阵M(指向上方向,指向右方向,表面法线方向)来旋转被纹理着色的多边形(也称为广告牌),使多边形总是朝着摄像机方向。通常被用作渲染烟雾,云朵,闪光效果等。参考代码如下:
Shader "Custom/Billboard" {
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
_MainColor ("Main Color", Color) = (1.0, 1.0, 1.0, 1.0)
_VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1 // 固定方向,为1时表示法线方向固定,为0时表示指向上方向固定
}
SubShader {
// 使用透明度混合渲染标签,并且关掉批处理,避免模型空间丢失造成模型空间偏移计算不正确
Tags { "RenderType" = "Transparent" "IgnoreProjector" = "True" "Queue" = "Transparent" "DisableBatching" = "True" }
Pass {
Tags { "LightModel" = "ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
fixed _VerticalBillboarding;
struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v i) {
float3 anchor = float3(0.0, 0.0, 0.0); // 模型空间的中心点作为多边形(广告牌)的锚点
float3 viewer = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0)); // 获取摄像机模型空间观察方向
// 获取法线方向
float3 normal_dir = viewer - anchor; // 获取模型空间中法线的方向
normal_dir.y = normal_dir.y * _VerticalBillboarding; // 当_VerticalBillboarding为1时,法线方向为固定视角方向,否则指向上方向为视角方向
normal_dir = normalize(normal_dir);
// 获取指向右方向
float3 up_dir = abs(normal_dir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
float3 right_dir = normalize(cross(up_dir, normal_dir));
// 获取指向上方向
up_dir = normalize(cross(normal_dir, right_dir));
// 使用指向上方向,指向右方向,法线方向组成的旋转矩阵,来旋转多边形(广告牌),使多边形每一个面都朝着摄像机方向
float3 offset = i.vertex.xyz - anchor;
float3 local_pos = anchor + right_dir * offset.x + up_dir * offset.y + normal_dir * offset.z;
// 获取顶点经过旋转后得到的齐次空间坐标
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, float4(local_pos, 1));
// 获取纹理采样坐标
o.uv = TRANSFORM_TEX(i.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// 采样纹理,获取采样颜色并与整体颜色参数相乘混合
fixed4 c = tex2D(_MainTex, i.uv);
c.rgb *= _MainColor.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}