Unity_Shader中级篇_11_Unity Shader入门精要

第十一章 让画面动起来
没有动画的画面往往让人觉得很无趣。在本章中,我们将学习如何向Unity Shader中引入时间变量,以实现各种动画效果。在11.1节中,我们首先会介绍Unity Shader内置的时间变量,在随后的章节中我们会使用这些时间变量来实现动画。11.2节会介绍两种常见的纹理动画,即序列帧动画和背景循环滚动动画。在11.3节,我们会学习使用顶点动画来实现流动的河流、广告牌等动画效果,并在最后给出一些在实现顶点动画时的注意事项。

11.1 Unity Shadr 中的内置变量(时间篇)
动画效果往往都是把时间添加到一些变量的计算中,以便在时间变化时画面也随之变化。Unity Shadr 提供了一系列关于时间的内置变量来允许我们方便地在Shader中访问运行时间,实现各种动画效果。
这里写图片描述

11.2 纹理动画
纹理动画在游戏中的应用非常广泛。尤其在各种资源都比较局限的移动平台上,我们往往会使用纹理动画来代替复杂的粒子系统等模拟各种动画效果。
序列帧动画
优点在于灵活性很强,我们不需要进行任何物理计算就可以得到非常细腻的动画效果。而他的缺点也很明显,由于序列帧中每张关键帧图像都不一样,因此,要制作一张出色的序列帧纹理所需要的美术工程量也比较大。例如下图所示:
这里写图片描述
包含了8×8张关键帧图像,他们的大小相同,而且播放顺序为从左到右、从上到下。
(1)创建场景(Scene_11_2_1)。
(2)新建一个材质(ImageSequenceAnimationMat)。
(3)新建一个Unity Shader (Chapter11-ImageSequenceAnimation)。把新的Shadr赋给第二步中创建的材质。
(4)在场景中创建一个四边形(Quad),并把第二步中的材质拖拽给它。

Shader "Unity Shaders Book/Chapter 11/Image Sequence Animation" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        //_MainTex包含了所有关键帧图像的纹理。
        _MainTex ("Image Sequence", 2D) = "white" {}
        //_HorizontalAmount和_VerticalAmount分别代表了该图像在水平方向和竖直方向包含的关键帧图像的个数。
        _HorizontalAmount ("Horizontal Amount", Float) = 4
        _VerticalAmount ("Vertical Amount", Float) = 4
        //_Speed用于控制序列帧动画的播放速度。
        _Speed ("Speed", Range(1, 100)) = 30
    }
    SubShader {
        //由于序列帧图像通常是透明纹理,我们需要设置Pass的相关状态,以宣染透明效果:
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

        Pass {
            Tags { "LightMode"="ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

        //由于序列帧图像通常包含了透明通道,因此可以被当成是一个半透明对象。在这里我们使用半透明的
        //“标配”来设置它的SubShader标签,即把Queue和RenderType设置成Transparent,把
        //IgnoreProjector设置为True。在Pass中,我们使用Blend命令来开启并设置混合模式,同时关闭了深度写入。
            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结构体里
            v2f vert (a2v v) {  
                v2f o;  
                o.pos = mul(UNITY_MATRIX_MVP, 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 = 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;
            }
//要播放帧动画,从本质来说,我们需要设计出每个时刻需要播放点的关键帧在纹理中的位置。而由于序列帧纹理都是
//按行按列排列的,因此这个位置可以认为是该关键帧所在的行列索引数。因此,在上面的代码的前三行中
//我们计算了行列数,其中使用了unity的内置时间变量_Time。由11.1节可以知道,_Time.y就是自该场景加载后所经过的时间。
//我们首先把_Time.y和速度属性_Speed相乘来得到模拟的时间,并使用CG的floor函数对结果值取整来得到整数时间time。然后,
//我们使用time除以_HorizontalAmount的结果值的商来作为当前对应的行索引,除法结果的
//余数则是列索引。接下来,我们需要使用行列索引值来构建真正的采样坐标。由于序列帧图像包含了
//许多关键帧图像,这意味着采样坐标需要映射到每个关键帧图像的坐标范围。然后,我们可以首先把
//原纹理坐标i.uv按行数和列数进行等分,得到每个子图像的纹理坐标范围内。我们可以首先
//把原纹理坐标i.uv按行数和列数进行等分,得到每个子图像的纹理坐标范围。然后,我们西药使用当前
//的行列数对上面的结果进行偏移,得到当前子图像的纹理坐标。需要注意的是,对竖直方向的
//坐标偏移需要使用减法,这是因为在Unity中纹理坐标竖直方向的顺序(从下到上逐渐增大)
//和序列帧纹理中的顺序(播放顺序是从上到下)是相反的。这对应了上面代码中注释掉的代码部分。
//我们可以把上述过程中的除法整合到一起,就得到注释下方的代码。这样,我们就得到了
//真正的纹理采样坐标。
            ENDCG
        }  
    }
    FallBack "Transparent/VertexLit"
}

这里写图片描述
这里写图片描述
PS:由于是透明纹理,因此需要勾选该纹理的 Alpha Is Transparency 属性

滚动的背景
常用以2D游戏,用多个层来模拟一种视差效果。本节使用的纹理资源均来自OpenGameArt网站(https://opengameart.org/)。
(1)新建场景(Scene_11_2_2)。投影模式为正交投影。
(2)新建材质(ScrollingBackgroundMat)。
(3)新建Unity Shader(Chapter11-ScrollingBackground),把新的Shader赋给第二步中创建的材质。
(4)创建一个四边形(Quad),将第二步中的材质拖拽给它。

Shader "Unity Shaders Book/Chapter 11/Scrolling Background" {
    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
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}

        Pass { 
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            sampler2D _DetailTex;
            float4 _MainTex_ST;
            float4 _DetailTex_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 = mul(UNITY_MATRIX_MVP, v.vertex);

                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;
            }
            //我们首先进行了最基本的顶点变换,把顶点从模型空间变换到裁剪空间中。然后,我们计算了
            //两层背景纹理的纹理坐标。为此,我们首先利用TRANSFORM_TEX来得到初始的纹理坐标。然后,
            //我们利用内置的_Time.y变量在水平方向上对纹理坐标进行偏移,以此达到滚动的效果。我们把
            //两张纹理的纹理坐标存储在同一个变量o.uv中,以减少占用的差值寄存器空间。
            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;
            }
            //我们首先分别利用i.uv.xy和i.uv.zw对两张背景纹理进行采样。然后,使用第二层纹理的透明通道来混合两张
            //纹理,这使用了CG和lerp函数。最后,我们使用_Multiplier参数好输出颜色进行
            //相乘,以调整背景亮度。
            ENDCG
        }
    }
    FallBack "VertexLit"
}

这里写图片描述

11.3 顶点动画
在游戏里,定点动画可以用来模拟飘动的旗帜、湍流的小溪等效果。
流动的河水
原理:使用正弦函数等来模拟水流的波动效果。
(1)新建场景(Scene_11_3_1)。
(2)新建一个材质(WaterMat)(WaterMat1)(WaterMat2)。
(3)新建一个Unity Shader (Chapter11-Water)。并赋给第二步中创建的材质。
(4)在场景中创建Water模型,把第二步中的材质拖拽给模型。

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 {
        // 需要禁用批处理因为顶点动画(DisableBatching)
        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 = mul(UNITY_MATRIX_MVP, v.vertex + offset);

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv +=  float2(0.0, _Time.y * _Speed);

                return o;
            }
            //我们首先计算顶点位移量。我们只希望对顶点的x方向进行位移,因此yzw的位移量被设置为0.
            //然后,我们利用_Frequency属性和内置的_Time.y变量来控制正弦函数的频率。为了让不同位置具有不同
            //的位移,我们对上述结果加上了模型空间下的位置分量,并乘以_InvWaveLength来控制波长。
            //最后,我们对结果值乘以_Magnitude属性来控制波动幅度,得到最终的位移。剩下的工作,
            //我们只需要把位移量添加到顶点位置上,在进行正常的顶点变换即可。
            //在上面的代码中,我们还进行了纹理动画,即使用_Time.y和_Speed来控制在水平方向上的纹理动画。

            //片元着色器的代码非常简单,我们只需要对纹理采样再添加颜色控制即可;
            fixed4 frag(v2f i) : SV_Target {
                fixed4 c = tex2D(_MainTex, i.uv);
                c.rgb *= _Color.rgb;

                return c;
            } 

            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

这里写图片描述

广告牌
另一种常见的顶点动画就是广告牌技术(Billboarding)。广告牌技术会根据视角方向来旋转一个被纹理着色的多边形(通常就是简单的四边形,这个多边形就是广告牌),使得多边形看起来好像总是面对这摄影机。多被用于渲染烟雾、云朵、闪光效果等
原理:构建旋转矩阵,已知一个变换矩阵需要3个基向量。广告牌技术使用的基向量通常就是表面法线(normal)指向上的方向(up)以及指向右的方向(fight)。除此之外,我们还需要指定一个锚点(anchor location),这个锚点在旋转过程中是固定不变的,以此来确定多边形在空间中的位置。
技术难点在于,构建三个相互正交的基向量。计算过程通常是,我们首先通过初始化计算得到目标的表面法线(例如就是视角方向)和指向上的方向,而两者往往是不垂直的。但是,两者之一是固定的,例如当模拟草丛时,我们希望广告牌的指向上的方向永远是(0,1,0),而法线方向应该随视角变化;而当模拟粒子效果时,我们希望广告牌的法线方向是固定的,即总是指向视角方向,指向上的方向则可以发生变化,我们假设法线方向是固定的,首先,我们根据初始的表面法线和指向上的方向来计算出目标方向的指向右的方向(通过叉积操作):right =up×normal
对其归一化后,再由法线方向和指向右的方向计算出正交的指向上的方向即可:up‘=normal×right
至此,我们就可以得到用于旋转的三个正交基了。

这里写图片描述
上图展示了当Vertical Restraints属性为1时,即固定法线方向为观察视角时所得到的效果,可以看出,所有的广告牌都完全面朝摄像机。
这里写图片描述
上图展示了当Vertical Restraints属性为0时,即固定指向上的方向为(0,1,0)时所得到的效果,可以看出,广告牌虽然最大限度地面朝摄像机,但其指向上的方向并未发生改变。
(1)创建场景(Scene_11_3_2)。
(2)创建材质(BillboardMat)。
(3)新建一个Unity Shader(Chapter11-Billboard)。并赋给第二步中创建的材质。
(4)在场景中创建多个四边形(Quad),将第二步中的材质拖拽给第二步中创建的材质。

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(_World2Object,float4(_WorldSpaceCameraPos, 1));
                //计算三个正交矢量。首先,我们根据观察位置和锚点计算目标法线方向,并
                //根据_VerticalBillboarding属性来控制垂直方向上的约束度。

                float3 normalDir = viewer - center;             
                normalDir.y =normalDir.y * _VerticalBillboarding;
                normalDir = normalize(normalDir);
                //当_VerticalBillboarding为1时,意味着法线方向固定为视角方向;当_VerticalBillboarding
                //为0时,意味着向上方向固定为(0,1,0)。最后,我们需要计算得到的法线方向进行干预、
                //归一化操作来得到单位矢量。
                //接着,我们得到了粗略的向上方向。为了防止法线方向和向上方向平行(如果平行,那么叉积得到的
                //结果将是错误的),我们对法线方向的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 = mul(UNITY_MATRIX_MVP, 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"
}

PS:我们使用四边形(Quad)来作为广告牌,而不能使用自带的平面(Plane)。这是因为,我们的代码是建立在一个竖直摆放的多边形的基础上的,也就是说,这个多边形的顶点结构需要满足在模型空间下是竖直排列的。只有这样,我们才能使用v.vertex来计算得到正确的相对于中心的位置偏移。

注意事项
顶点动画非常灵活有效,但也是有一些注意事项的。
首先,如11.3.2中,我们在模型空间下进行顶点动画,那么批处理往往就会破坏这种动画效果。这时,我们可以通过SubShader的DisableBatching标签来强制取消对该Unity Shader的批处理。然而,取消批处理会带来一定的性能下降,增加了Draw Call,因此我们应该尽量避免使用模型空间下的一些绝对位置和方向来进行计算。在广告牌的例子中,为了避免显示使用模型空间的中心来作为锚点,我们可以利用顶点颜色来存储每个顶点到锚点的距离值,这种做法在商业游戏中很常见。
其次,如果我们想要包含了顶点动画的物体添加阴影,那么如果仍然想9.4节中那样使用内置Diffuse等包含的阴影Pass来渲染,就得不到正确的阴影效果(这里指的是无法向其他物体正确地投射阴影)。这是因为,我们讲过Unity的阴影绘制需要调用一个ShadowCaster Pass,而如果直接使用这些置ShadowCaster Pass,这个Pass中并没有进行相关的顶点动画,因此Unity会仍然按照原来的顶点位置来计算阴影,这并不是我们希望看到的。这时,我们就需要提供一个自定义的ShadowCaster Pass,在这个Pass中,我们将进行同样 的顶点变换过程。需要注意的是,在前面的实现中,如果涉及半透明物体我们都把Fallback设置成了Transparent/VertexLit,而Transparent/VertexLit没有定义Transparent/VertexL,因此就不会产生阴影。(9.4.5)
在本书资源的Scene(_11_3_3),我们给出了计算顶点动画的阴影的一个例子。在这个例子中,我们使用了11.3.1节中的大部分代码,模拟一个波动的水流。同时,我们开启了场景中平行关的阴影效果,并添加了一个平面来接受来自“水流”的阴影。我们还把这个Unity Shader的Fallback设置为了内置的VertexLit,这样Unity将根据Fallback最终找到VertexLit中的ShadowCaster Pass来渲染阴影。
这里写图片描述

Shader "Unity Shaders Book/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 {
        // Need to disable batching because of the vertex animation
        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 to render object as a shadow caster
        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"
}

这里写图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值