Unity Shader学习笔记(18)纹理动画、顶点动画、广告牌技术

发表于2018-01-02
评论0 9.1k浏览
程序纹理的内容介绍完,乘热打铁,现在就给大家介绍下纹理动画、顶点动画和广告牌技术,这三类在开发过程中也是用的比较多的,分享给大家,希望能对大家有帮助。

Unity Shader的内置变量

纹理动画

可用于代替粒子系统模拟动画效果。

帧序列动画

8x8帧序列纹理动画 

Properties {
    _Color ("Color Tint", Color) = (1, 1, 1, 1)
    _MainTex ("Image Sequence", 2D) = "white" {}            // 包含所有关键帧图像的纹理
    _HorizontalAmount ("Horizontal Amount", Float) = 4      // 水平方向包含关键帧图像个数
    _VerticalAmount ("Vertical Amount", Float) = 4          // 同上(竖直方向)
    _Speed ("Speed", Range(1, 100)) = 30                    // 播放速度
}

因为序列帧图像通常是透明纹理,所以就按半透明的方式设置。

// 序列帧图像通常包含透明通道
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
    Tags { "LightMode"="ForwardBase" }
    // 关闭深度写入,开启混合模式
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha
    ...
    fixed4 frag (v2f i) : SV_Target {
        float time = floor(_Time.y * _Speed);               // floor()取整。CG的函数
        float row = floor(time / _HorizontalAmount);        // 行索引
        float column = time % _HorizontalAmount;        // 列索引
        // 纹理坐标从上到下,所以是-row。下面两种计算方法
        //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;
        fixed4 c = tex2D(_MainTex, uv);
        c.rgb *= _Color;
        return c;
    }
}


滚动动画

可以模拟2D跑酷游戏背景的视察效果。

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             // 纹理亮度
}
v2f vert (a2v v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    // TRANSFORM_TEX 得到初始纹理坐标,加上偏移坐标。frac为获取小数部分的值。
    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);
    return o;
}
fixed4 frag (v2f i) : SV_Target {
    fixed4 firstLayer = tex2D(_MainTex, i.uv.xy);
    fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw);
    fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);
    c.rgb *= _Multiplier;
    return c;
}

顶点动画

即修改顶点信息的动画。

一张长方形纹理,通过修改顶点信息,实现扭曲效果。 

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
}
// DisableBatching:取消批处理。需要对模型空间下的顶点位置进行偏移,所以就不合并模型。
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
Pass {
    Tags { "LightMode"="ForwardBase" }
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha
    Cull Off                            // 关闭剔除模式(正反面都显示)
    ...
    v2f vert(a2v v) {
        v2f o;
        float4 offset;
        offset.yzw = float3(0.0, 0.0, 0.0);
        // 只改变X变量
        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;
    }
}

 
注意到前面使用内置阴影的Pass是没有处理顶点动画的,所以最后投影的阴影是错误的,需要自定义ShadowCasterPass,在这个Pass做相同顶点变换即可。

下面用到阴影的宏都在UnityCG.cginc中定义。

Pass {
    Tags { "LightMode" = "ShadowCaster" }
    ...
    #pragma multi_compile_shadowcaster
    ...
    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
}


广告牌技术(Billboarding)

即让纹理始终面对着镜头。本质就是构建旋转矩阵,三个基向量:表面法线、向上的方向、向右的方向。还需要指定一个锚点,即旋转中心。

计算方法(如下图): 
1. 先计算向右方向 right = up × normal。起始up是竖直向上的。 
2. 叉乘即可得到向上方向 up’ = normal × right。

Vertical Restraints值为1,所有星星不管原来什么角度都会转向到镜头。 

Vertical Restraints值为0,所有星星固定向上方向,并最大限度面向镜头。 

Properties {
    _MainTex ("Main Tex", 2D) = "white" {}
    _Color ("Color Tint", Color) = (1, 1, 1, 1)
    _VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1      // 垂直程度,0:固定向上,1:固定法线
}
// 需要模型空间下的位置来作为锚点计算,所以要关掉批处理。
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
Pass { 
    Tags { "LightMode"="ForwardBase" }
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha
    Cull Off
    CGPROGRAM
    ...
    v2f vert (a2v v) {
        v2f o;
        float3 center = float3(0, 0, 0);                // 模型空间的原点作为广告牌锚点
        float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1));   // 获取模型空间下视角
        float3 normalDir = viewer - center;
        // _VerticalBillboarding 为1为法线固定(始终向上),为0为向上方向固定。 
        normalDir.y =normalDir.y * _VerticalBillboarding;
        normalDir = normalize(normalDir);
        // 通过normalDir.y 先判断法线和向上是否平行(叉积会错),来改变向上方向
        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;
    }


关于 DisableBatching

如果要进行一些顶点动画,就要关闭批处理。性能会下降,所以尽量避免使用模型空间下的绝对位置和方向进行计算。如广告拍技术中,可以用顶点颜色来存储顶点到锚点的距离。

Unity Shader学习笔记系列教程:

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引