不用画的动画——ShaderCp11

——20.9.14

Shader中主要有及两种动画,一种就是纹理动画还有一种就是顶点动画。

动画效果一般都需要把时间加入一些变量的计算,以便画面可以随时间发生变化。下面是Shader中的如何去访问时间的方法。

一、纹理动画

序列帧动画就是我们接触的第一种纹理动画。序列帧动画原理就是依次播放一系列关键帧动画。当图片的切换速度达到一定数值后,看上去就像是连续的动画。优点:在于它的灵活性强,不用进行物理计算就会有对应的动画效果。缺点:依旧是需要逐关键帧的动画内容,工作量依旧很大。

我们看看shaderlab部分。我们需要看有什么样的变量。一般来说序列帧一般是一张图片包括了所有的关键帧。并且按播放顺序排放。并且方便读取一般是规整的(就是没有边框)。所以我们要确定有几行_HorizontalAmount几列_VerticalAmount根据图片决定。还有就是播放速度_Speed,最后就是图片_MainTex,和颜色_Color。

1
2
3
4
5
6
7
8
//frag<br>float time = floor(_Time.y * _Speed);<br>float row = floor(time / _HorizontalAmount);<br>float column = time - row * _VerticalAmount;
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;

  首先是_Time.y就是取时间变量t,然后通过取整来确定行数,然后通过取小数来确定列数。这里是在片元着色器中的两种读取方式。一种是根据行和列把坐标放大到整张图片。并且通过增加基础单位来进行可以看到对uv.y进行的是减操作。是因为unity里面是从左下为(0,0)。然后第二种方法先去确定要读取的行列。然后再去细分到一个区间内。要注意上面这两种方法本质上都是为了取这张关键帧中的一张图片。于是就要把uv坐标缩放到其中的一张。

下面就是针对不同的行列。相同的速度数值也是截然不同的速度。  

 

然后就是不同的背景进远景对应的速度不同。我们看一下首先需要两张图分别是近景和远景_MainTex _DetailTex。然后就是分别他们的速度_ScrollX _Scroll2X。最后就是有关亮度_Multiplier。

1
2
3
T frac(T v)<br>//vert
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;<br>fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);

 

   frac函数是返回变量的小数部分。其中的变量可以是float float2 float3 float4 都会分别对变量取小数。这样可以保证图片可以循环播放。因为上面每一个顶点取偏移值值是一样的。然后需要混合颜色采用的lerp然后第三个变量取的是secondlayer的a通道是因为这种图是一张黑白图取值分别就是1或者0。便可以确定近景中哪一些是镂空可以放远景的颜色。

 二、顶点动画

我们先做一个2d的顶点动画。就是河流。我们看一下我们需要河流的纹理_MainTex,_Color调整整体颜色,控制水流动的幅度_Magniture,水流的波动_Frequency,波长的倒数_InvWaveLength(即该值越大,波长越小),流动速度_Speed.

1
2
3
4
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True" }
//vert
offset.yzw = float3(0,0,0);
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;

  DisableBatching关闭该tags是为了指明是否要对该SubShader进行批处理,因为这些需要特殊处理的Shader基本包括顶点动画。会合并与之相关的模型导致相关的顶点出现问题,合并模型会导致各自模型空间丢失(留个坑?)。顶点动画本质就是改变其中的顶点着色器中顶点的位置。这个的vertex是模型空间中的加上模型空间的位置分量。

  

 

 

另一种顶点动画就是广告牌技术。会根据视角方向来旋转多边形,看上去好像面向摄像头,比如延误云朵闪光。其中的难处在于建立三个相互垂直的基向量。视角方向和向上的向量往往不垂直,所以要通过叉乘得到一个与两者相垂直的变量,然后再取该向量与视角方向叉积,更新向上的变量。

 

 

 

1
2
3
4
5
6
7
8
9
10
11
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));<br>
float3 centerOffs = v.vertex.xyz - center;
float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;
o.pos = UnityObjectToClipPos(float4(localPos, 1));

  上面就是得到三个基向量的过程。_VerticalBillboarding主要是这个向量通过调整数值来改变normal变量来模拟特殊需求。比如草地下面的根部是不懂的。还有一些广告牌只会有旋转操作但是不会脱离垂直方向。

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
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
 
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
 
Shader "Unlit/11-4Billboard"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
        _VerticalBillboarding ("VerticalBillboarding", 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 "UnityCG.cginc"
            #include "Lighting.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            float _VerticalBillboarding;
 
            v2f vert (appdata 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"
}

 

然后可以看到我们在做河流的时候河流的影子是不对的,我们需要重写ShaderCaster Pass。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct v2f {
    V2F_SHADOW_CASTER;
;
 
v2f vert(appdata_base v){
    v2f o;
    float4 offset;
    offset.yzw = float3(0,0,0);
    offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
    v.vertex += offset;
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
    return o;
}
 
fixed4 frag(v2f i) : SV_Target{
    SHADOW_CASTER_FRAGMENT(i);
}

  这里采用了UnityCG.cginc中定义的一些宏。来计算阴影所需的内容。V2F_SHADOW_CASTER用于定义一些变量。

  

 

最后就是一些小事项。取消批处理可以放置模型空间出现问题,但是会带来性能的问题,就是DrawCall增加了。所以应该避免一些在模型空间的计算,用顶点颜色存顶点到锚点的距离,避免使用模型空间中性作为锚点。

感谢你看到这里,Cheers!

 

posted @   xkyl  阅读(329)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示