Shader笔记十二 动画效果

动画效果

动画效果需要引入时间变量,Unity内置的时间变量:

这些时间变量可以用来实现纹理动画和顶点动画

纹理动画

  • 序列帧动画
    序列帧动画是通过依次播放一系列关键帧图像,当播放速度达到一定数值时,看起来就像是一个连续的动画。灵活性较强,通过一张包含关键帧的图像可以得到比较细腻的动画效果,缺点在于需要大量时间制作包含关键帧的图像。
    关键帧实现的关键在于,每个时刻计算该时刻下应该播放的关键帧的位置

完整代码

	Shader "Custom/Chapter11_ImageSequenceAnimation" {
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{
		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 a2v{
			float4 vertex:POSITION;
			float4 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);   //内置时间变量 _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 "Custom/Chapter11_ScrollingBackground" {
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{
	Pass{
		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 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)*_Time.y);
			o.uv.zw=TRANSFORM_TEX(v.texcoord,_DetialTex)+frac(float2(_Scroll2X,0)*_Time.y);
			//frac取小数函数,使取样坐标在[0,1]范围内,背景连续重复滚动

			return o;
		}

		fixed4 frag(v2f i):SV_Target{
			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 "Custom/Chapter11_Water" {
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{
		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 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;
			    //在顶点进行空间变换前,对x分量进行正弦操作
				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"
}
  • 广告牌技术
    广告牌技术是另一种常见的顶点动画。广告牌会根据视角方向来旋转一个被纹理着色的多边形,使多边形看起来始终朝着摄像机。
    广告牌技术的本质是构建旋转矩阵。计算过程中先根据初始计算得到目标的表面法线(例如视角方向)和指向上的方向,这两者的方向往往不是垂直的,根据这两者的方向做叉乘得到垂直于两者的方向,再根据这个得到的方向,假定之前的两个方向某一个不变,计算另一个垂直的方向,这样得到三个正交的方向,计算过程类似于:

    在广告牌技术中需要指定一个锚点,这个锚点在旋转过程中是固定不变的,以此来确定多边形在空间中的位置。

完整代码:

	Shader "Custom/Chapter11_Billboarding" {
	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{
			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 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);
				//当_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.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"
}

注意事项:

  • 在顶点变换的Shader中需要做特殊处理,即关闭批处理,这样能防止Unity自动对相关模型做合并处理,从而丢失模型空间。

  • 在对顶点进行变换后,如果想要得到正确的阴影,需要添加一个自定义的ShadowCaster Pass,否则得到的阴影会是变换前的阴影。自定义的Pass中,需要使用内置的宏。完整代码为:

      Shader "Custom/Chapter 11_Vertex Animation With Shadow" {
      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 {"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 = mul(UNITY_MATRIX_MVP, 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 {
      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"
      }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值