让画面动起来
Unity Shader内置的时间变量
纹理动画
序列帧动画
//个人感觉粒子编辑器完全能够胜任简单的粒子效果
要播放帧动画,从本质来说, 我们需要计算出每个时刻需要播放的关键帧在纹理中的位置 。
Shader "Unlit/序列帧动画"
{
Properties{
_Color("Main Clolr",Color) = (1,1,1,1)
_MainTex("MainTex",2D) = "white"{}
_HorizantalAmount("HorizantalAmount",Float) = 4//在水平方向包含的关键帧图像的个数
_VerticalAmount("VerticalAmount",Float) = 4//在竖直方向包含的关键帧图像的个数
_Speed("Speed",Range(1,100)) = 30
}
SubShader{
Tags {
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
}
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//参数定义
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _HorizantalAmount;
float _VerticalAmount;
float _Speed;
struct VertexInput { //输入结构
float4 vertex : POSITION;
float4 uv0:TEXCOORD0;
};
struct VertexOutput { //输出结构
float4 pos : SV_POSITION;
float2 uv:TEXCOORD0;
};
VertexOutput vert(VertexInput v) { //顶点Shader
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv0, _MainTex);
return o;
}
float4 frag(VertexOutput i) : COLOR { //像素Shader
float time = floor(_Time.y * _Speed); //内置时间变量 _Time(t/20,t,2t,3t)
float row = floor(time / _HorizantalAmount);
float column = time - row * _VerticalAmount;
//根据播放的速度计算对应的序列帧行和列
//纹理中包含许多关键帧图像,将采样坐标映射到每一个关键帧的坐标范围内
//纹理中的采样方向与关键帧的播放顺序在Y方向是反的,因此Y坐标做减法
half2 uv = float2(i.uv.x / _HorizantalAmount,i.uv.y / _VerticalAmount);
uv.x += column / _HorizantalAmount;
uv.y -= row / _VerticalAmount;
fixed4 c = tex2D(_MainTex,uv);
c.rgb *= _Color;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
滚动背景
滚动背景一般使用多个层以不同的速度进行滚动,滚动的关键在于使滚动方向上的纹理坐标的增量与时间变量相乘,这样随着时间的变化,滚动方向上的采样坐标不断变换,画面也能连续变化。
Shader "Unlit/背景"
{
Properties{
_MainTex("Base Layer",2D) = "white"{}
_DetialTex("Second Layer",2D) = "white"{}
_ScrollX("Base Layer Scroll Speed",Float) = 1.0
_Scroll2X("Second Layer Scroll Speed",Float) = 1.0
_Multiplier("Layer Multiplier",Float) = 1
}
SubShader{
Tags {
"RenderType" = "Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma vertex vert//哪个函数包含了顶点着色器的代码
#pragma fragment frag//哪个函数包含了片元着色器的代码
#include "Lighting.cginc"
//参数定义
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _DetialTex;
float4 _DetialTex_ST;
float _ScrollX;
float _Scroll2X;
float _Multiplier;
struct VertexInput { //输入结构
float4 vertex : POSITION;
float4 uv0:TEXCOORD0;
};
struct VertexOutput { //输出结构
float4 pos : SV_POSITION;
float4 uv:TEXCOORD0;
};
VertexOutput vert(VertexInput v) { //顶点Shader
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv0, _MainTex) + frac(float2(_ScrollX, 0) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.uv0, _DetialTex) + frac(float2(_Scroll2X, 0) * _Time.y);
//frac取小数函数,使取样坐标在[0,1]范围内,背景连续重复滚动
return o;
}
float4 frag(VertexOutput i) : COLOR { //像素Shader
fixed4 baseLayer = tex2D(_MainTex,i.uv.xy);
fixed4 secondLayer = tex2D(_DetialTex,i.uv.zw);
fixed4 c = lerp(baseLayer,secondLayer,secondLayer.a);
c.rgb *= _Multiplier; //_Multiplier用来控制整体亮度
return c;
}
ENDCG
}
}
FallBack "VertexLit"
}
顶点动画
流动的河流
原理通常就是使用正弦函数等来模拟水流的 波动效果
Shader "Unlit/河流"
{
Properties{
_Color("Main Color",Color) = (1,1,1,1)
_MainTex("MainTex",2D) = "white"{}
_Magnitude("Magnitude",Float) = 1
_Frequency("Frequency",Float) = 1
_InvWaveLength("InvWaveLength",Float) = 10
_Speed("Speed",Float) = 0.5
}
SubShader{
Tags {"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"DisableBatching" = "True"}
//为透明效果设置对应标签,这里“DisableBatching”的标签是关闭批处理,
//包含模型空间顶点动画的Shader是需要特殊处理的Shader,
//而批处理会合并所有相关的模型,这些模型各自的模型空间会丢失
//而顶点动画需要在模型空间对顶点进行偏移
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
ZWrite Off
Blend SrcAlpha OneMinusDstAlpha
Cull Off
CGPROGRAM
#pragma vertex vert//哪个函数包含了顶点着色器的代码
#pragma fragment frag//哪个函数包含了片元着色器的代码
#include "Lighting.cginc"
#include "UnityCG.cginc"
//参数定义
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;
struct VertexInput { //输入结构
float4 vertex : POSITION;
float2 uv0:TEXCOORD0;
};
struct VertexOutput { //输出结构
float4 pos : SV_POSITION;
float2 uv:TEXCOORD0;
};
VertexOutput vert(VertexInput v) { //顶点Shader
VertexOutput o = (VertexOutput)0;
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;
//在顶点进行空间变换前,对x分量进行正弦操作
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv0, _MainTex);
o.uv += float2(0.0, _Time.y * _Speed);
//进行纹理动画
return o;
}
float4 frag(VertexOutput i) : COLOR { //像素Shader
fixed4 c = tex2D(_MainTex,i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
广告牌
广告牌会根据视角方向来旋转一个被纹理着色的多边形,使多边形看起来始终朝着摄像机。
广告牌技术的本质是构建旋转矩阵。计算过程中先根据初始计算得到目标的表面法线(例如视角方向)和指向上的方向,这两者的方向往往不是垂直的,根据这两者的方向做叉乘得到垂直于两者的方向,再根据这个得到的方向,假定之前的两个方向某一个不变,计算另一个垂直的方向,这样得到三个正交的方向
广告牌技术中需要指定一个锚点,这个锚点在旋转过程中是固定不变的,以此来确定多边形在空间中的位置。
Shader "Unlit/广告牌"
{
Properties{
_Color("Color",Color) = (1,1,1,1)
_MainTex("MainTex",2D) = "white"{}
_VerticalBillboarding("VerticalBillboarding",Range(0,1)) = 1
}
SubShader{
Tags {
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RendererType" = "Transparent"
"DisableBatching" = "True"}
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
//这里关闭了深度写 ,开启并设置了混合模式,并关闭了剔除功能。这是为了让广告牌的每
个面都能显示
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _VerticalBillboarding;
struct VertexInput { //输入结构
float4 vertex : POSITION;
float4 uv0:TEXCOORD0;
};
struct VertexOutput { //输出结构
float4 pos : SV_POSITION;
float2 uv:TEXCOORD0;
};
VertexOutput vert(VertexInput v) { //顶点Shader
VertexOutput o = (VertexOutput)0;
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);
//当_VerticalBillboarding的值为1时,法线方向固定为视角方向,
//当_VerticalBillboarding的值为0时,法线在Y方向上没有分量,那么向上的方向固定为(0,1,0),这样才能保证与法线方向垂直
float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
//这里对向上方向是否与法向方向相平行,防止得到错误的叉乘结果
float3 rightDir = normalize(cross(normalDir, upDir));
upDir = normalize(cross(normalDir, rightDir));
float3 offset = v.vertex.xyz - center;
float3 localPos = center + rightDir * offset.x + upDir * offset.y + normalDir * offset.z;
o.pos = UnityObjectToClipPos(float4(localPos, 1));
o.uv = TRANSFORM_TEX(v.uv0, _MainTex);
return o;
}
float4 frag(VertexOutput i) : COLOR { //像素Shader
fixed4 c = tex2D(_MainTex,i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
永远朝向相机
注意事项
在顶点变换的Shader中需要做特殊处理,即关闭批处理,这样能防止Unity自动对相关模型做合并处理,从而丢失模型空间。
在对顶点进行变换后,如果想要得到正确的阴影,需要添加一个自定义的ShadowCaster Pass,否则得到的阴影会是变换前的阴影。自定义的Pass中,需要使用内置的宏。
Shader "Unlit/河流"
{
Properties{
_Color("Main Color",Color) = (1,1,1,1)
_MainTex("MainTex",2D) = "white"{}
_Magnitude("Magnitude",Float) = 1
_Frequency("Frequency",Float) = 1
_InvWaveLength("InvWaveLength",Float) = 10
_Speed("Speed",Float) = 0.5
}
SubShader{
Tags {
"DisableBatching" = "True"}
//为透明效果设置对应标签,这里“DisableBatching”的标签是关闭批处理,
//包含模型空间顶点动画的Shader是需要特殊处理的Shader,
//而批处理会合并所有相关的模型,这些模型各自的模型空间会丢失
//而顶点动画需要在模型空间对顶点进行偏移
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
Cull Off
CGPROGRAM
#pragma vertex vert//哪个函数包含了顶点着色器的代码
#pragma fragment frag//哪个函数包含了片元着色器的代码
#include "UnityCG.cginc"
//参数定义
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _Magnitude;
float _Frequency;
float _InvWaveLength;
float _Speed;
struct VertexInput { //输入结构
float4 vertex : POSITION;
float2 uv0:TEXCOORD0;
};
struct VertexOutput { //输出结构
float4 pos : SV_POSITION;
float2 uv:TEXCOORD0;
};
VertexOutput vert(VertexInput v) { //顶点Shader
VertexOutput o = (VertexOutput)0;
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;
//在顶点进行空间变换前,对x分量进行正弦操作
o.pos = UnityObjectToClipPos(v.vertex + offset);
o.uv = TRANSFORM_TEX(v.uv0, _MainTex);
o.uv += float2(0.0, _Time.y * _Speed);
//进行纹理动画
return o;
}
float4 frag(VertexOutput i) : COLOR { //像素Shader
fixed4 c = tex2D(_MainTex,i.uv);
c.rgb *= _Color.rgb;
return c;
}
ENDCG
}
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"
}