Unity Shader中的内置变量(时间篇)
名称 |
类型 |
描述 |
_Time |
float4 |
t是自该场景加载开始所经过的时间,4个分量分别是(t/20, t, 2t, 3t) |
_SinTime |
float4 |
t是时间的正弦值,4个分量的值分别是(t/8, t/4, t/2, t) |
_CosTime |
float4 |
t是时间的余弦值,4个分量的值分别是(t/8, t/4, t/2, t) |
unity_DeltaTime |
float4 |
dt是时间增量,4个分量分别是(dt, 1/dt, smoothDt, 1/smoothDt) |
纹理动画
纹理动画在游戏中的应用非常广泛.尤其在各种资源都比较局限的移动平台上,我们往往会使用纹理动画来代替复杂的粒子系统等模拟各种动画效果
序列帧动画
最常见的纹理动画之一就是序列帧动画.序列帧动画的原理非常简单,它像放电影一样,依次播放一系列关键帧图像.
它的优点在于灵活性极强,不需要任何物理计算就能得到非常细腻的动画效果.
想要实现序列帧动画,我们先要提供一张包含了关键帧图像的图像.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| Shader "Unlit/11.2" { Properties { _Color("Color Tint", Color) = (1, 1, 1, 1) _MainTex ("Image Sequence", 2D) = "white" {}
_HorizontalAmount ("Horizontal Amount", Float) = 4.0 _VerticalAmount ("Vertical Amount",Float) = 4.0
_Speed("Speed",Range(1, 100)) = 30 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="True" } 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 = i.uv + half2(column, -row); uv.x /= _HorizontalAmount; uv.y /= _VerticalAmount;
fixed4 c = tex2D(_MainTex, uv); c.rgb *= _Color; return c; } ENDCG } } }
|
滚动的背景
很多2D游戏都使用了不同的滚动背景来模拟游戏角色在场景中的穿梭.这些背景往往包含了多个层来模拟一种视差效果.而这些背景的实现往往就是利用了纹理动画.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| Shader "Unity Shaders Book/Chapter 11/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" }
|
广告牌
另一种常见的顶点动画就是广告牌技术(Billboarding).
广告牌技术会根据视角方向来旋转一个被纹理着色的多边形(通常就是简单的四边形,这个多边形就是广告牌),使得多边形看起来好像总面对着摄像机.
广告牌技术的本质就是构建旋转矩阵,而我们知道一个矩阵需要三个基向量.
广告牌技术使用的基向量通常就是表面法线(normal),指向上的方向以及指向右的方向.
除此之外,我们还需要一个锚点(anchor location),这个锚点在旋转的过程中是固定不变的,以此来确定多边形在空间中的位置.
广告牌技术的难点在于,如何根据需求来构建3个相互正交的基向量.
计算过程通常是,我们首先会通过初始计算得到目标的表面法线(例如视角方向)和指向上的方向,而两者往往是不垂直的.但是,两者其中之一是固定的,例如模拟草丛时,我们希望广告牌的指向上的方向永远是(0, 1, 0),而法线方向应该随视角变化;而当模拟粒子效果时,我们希望广告牌的法线方向是固定的,即总指向视角方向,指向上的方向则可以发生变化.
我们假设法线方向是固定的,首先,我们根据初始的表面法线和指向上的方向来计算目标方向和指向右的方向(通过叉积操作): right = up X normal
对其归一化后,再由法线方向和指向右的方向计算出正交的指向上的方向: up’ = normal X right
如此,我们就可以得到用于旋转的三个正交基了.上图给出了计算过程的演示.
如果指向上的方向是固定的,计算过程也是类似.
实践: 广告牌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| Shader "Unity Shaders Book/Chapter 11/Billboard" { 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" }
|
注意事项
批处理
之前说的顶点动画必须关闭批处理,然而关闭批处理就会增多Draw Call,降低性能.
因此我们应该尽量避免使用模型空间下一些绝对位置和方向进行计算.
在广告牌的例子中,为了避免显式使用模型空间的中心点作为锚点,可以利用顶点颜色来存储每个顶点到锚点的距离值(smg),这种做法在商业游戏里很常见.
阴影
到时候再看吧.
P239