unity shader之简单顶点动画的效果实现

Wan2.2-I2V-A14B

Wan2.2-I2V-A14B

图生视频
Wan2.2

Wan2.2是由通义万相开源高效文本到视频生成模型,是有​50亿参数的轻量级视频生成模型,专为快速内容创作优化。支持480P视频生成,具备优秀的时序连贯性和运动推理能力

Jietu20250608-021111-HD

Shader "Custom/SphereVertex"
{
    Properties
    {
        [HideInInspector] _EmissionColor("Emission Color", Color) = (1,1,1,1)
        [HideInInspector] _AlphaCutoff("Alpha Cutoff ", Range(0, 1)) = 0.5

        _MainTex("MainTex", 2D) = "white" {}
        _Bias("Bias", Float) = 0
        [HDR]_MainColor1("MainColor1", Color) = (0,0,0,0)
        _Scale("Scale", Float) = 1
        [HDR]_MainColor2("MainColor2", Color) = (0,0,0,0)
        _Power("Power", Float) = 5
        _MainTexSpeed("MainTexSpeed", Vector) = (1,1,0,0)

        _RampTex("RampTex", 2D) = "white" {}
        _NoiseTex("NoiseTex", 2D) = "white" {}
        _RampY("RampY", Range(0,1)) = 0.73
        _NoiseSpeed("NoiseSpeed", Vector) = (1,0,0,0)
        _NoiseStrength("NoiseStrength", Range(0,1)) = 0

        [HDR]_BorderColor1("BorderColor1", Color) = (0,0,0,0)
        [HDR]_BorderColor2("BorderColor2", Color) = (0,0,0,0)
        _BorderTex("BorderTex", 2D) = "white" {}
        _BorderColorStrength("BorderColorStrength", Range(0,1)) = 0
    }

    SubShader
    {
        Tags
        {
            "RenderPipeline"="UniversalPipeline"
            "RenderType"="Transparent"
            "Queue"="Transparent"
            "UniversalMaterialType"="Unlit"
        }

        LOD 0

        Cull Back
        ZWrite On
        ZTest LEqual
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            Name "Forward"
            Tags { "LightMode"="UniversalForwardOnly" }

            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.5

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            CBUFFER_START(UnityPerMaterial)
                float4 _BorderColor1;
                float4 _BorderColor2;
                float4 _MainColor1;
                float4 _MainColor2;
                float4 _MainTex_ST;
                float2 _NoiseSpeed;
                float2 _MainTexSpeed;
                float _NoiseStrength;
                float _BorderColorStrength;
                float _Bias;
                float _Scale;
                float _Power;
                float _RampY;
            CBUFFER_END

            TEXTURE2D(_MainTex);      SAMPLER(sampler_MainTex);
            TEXTURE2D(_NoiseTex);     SAMPLER(sampler_NoiseTex);
            TEXTURE2D(_BorderTex);    SAMPLER(sampler_BorderTex);
            TEXTURE2D(_RampTex);      SAMPLER(sampler_RampTex);

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS   : NORMAL;
                float2 uv         : TEXCOORD0;
                float2 uv2        : TEXCOORD1;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float3 positionWS : TEXCOORD0;
                float3 normalWS   : TEXCOORD1;
                float2 uv         : TEXCOORD2;
                float2 noiseUV    : TEXCOORD3;
            };

            Varyings vert(Attributes input)
            {
                Varyings o;

                float2 noiseUV = input.uv2 + _NoiseSpeed * _Time.y;
                float noiseSample = SAMPLE_TEXTURE2D_LOD(_NoiseTex, sampler_NoiseTex, noiseUV, 0).r;
                float noiseValue = saturate(noiseSample) * _NoiseStrength;

                float3 displacedPosOS = input.positionOS.xyz + input.normalOS * noiseValue;

                VertexPositionInputs vpi = GetVertexPositionInputs(displacedPosOS);

                o.positionCS = vpi.positionCS;
                o.positionWS = vpi.positionWS;
                o.normalWS   = TransformObjectToWorldNormal(input.normalOS);

                float2 baseUV = TRANSFORM_TEX(input.uv, _MainTex);
                o.uv      = baseUV + _MainTexSpeed * _Time.y;
                o.noiseUV = noiseUV;

                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                float3 viewDirWS = GetWorldSpaceNormalizeViewDir(i.positionWS);
                float3 n = normalize(i.normalWS);

                float ndotV = dot(n, viewDirWS);
                float fresnel = _Bias + _Scale * pow(saturate(1.0 - ndotV), _Power);

                float t = 0.5 * (sin(_Time.y) + 1.0);

                float3 borderLerp = lerp(_BorderColor1.rgb, _BorderColor2.rgb, t);
                float  borderMask = SAMPLE_TEXTURE2D(_BorderTex, sampler_BorderTex, i.uv).a;
                float3 borderColor = borderMask * borderLerp * _BorderColorStrength;

                float3 mainLerp = lerp(_MainColor1.rgb, _MainColor2.rgb, t * 0.5);
                float2 rampUV = float2(1.0 - fresnel, _RampY);

                float3 mainTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv).rgb;
                float3 ramp    = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, rampUV).rgb;

                float3 color = borderColor + mainLerp * mainTex * ramp;

                return half4(color, 1.0);
            }

            ENDHLSL
        }
    }

    FallBack Off
}

在Unity中,实现简单的顶点动画,核心原理其实并不复杂:在顶点着色器(Vertex Shader)里,通过数学函数(如正弦函数sin)结合时间变量,动态地改变模型顶点的位置,从而让整个模型“活”起来。

下面,我会通过两个最经典、也最容易上手的例子——“呼吸效果”和“水波效果”,带你一步步拆解实现过程。

💡 核心思路:让顶点“动”起来

所有顶点动画的秘密都藏在这行逻辑里:最终顶点位置 = 原始顶点位置 + 偏移量
这个“偏移量”通常由两个因素驱动:

  • 时间:让动画随时间变化,Unity提供了内置的 _Time.y 变量来获取运行时间。

  • 位置:让不同的顶点有不同幅度的运动,通常使用顶点原始的 x 或 z 坐标作为计算依据。

🫁 示例一:简单的“呼吸/浮动”效果

这个效果会让整个物体有节奏地上下起伏,像在呼吸一样。它的特点是所有顶点同步运动

原理:直接使用 sin 函数计算一个基于时间的值,然后加到所有顶点的Y轴上。

Shader核心代码

cg

v2f vert (appdata v)
{
    v2f o;
    float4 pos = v.vertex;

    // 呼吸动画:所有顶点一起上下运动
    // _Time.y 是时间,_Speed 控制快慢,_Amplitude 控制起伏高度
    pos.y += sin(_Time.y * _Speed) * _Amplitude;

    o.pos = UnityObjectToClipPos(pos);
    return o;
}

你可以把这段逻辑放入完整的Shader代码中,并通过 _Amplitude 和 _Speed 两个参数在材质面板中调整动画的强度和速度。

🌊 示例二:经典的“水波/波浪”效果

这个效果更生动,物体会像水面一样泛起涟漪。它的关键是不同位置的顶点,运动状态不同

原理:将顶点的原始X(或Z)坐标作为 sin 函数输入的一部分。这样,位置不同的顶点就会有不同的相位,从而形成波浪传播的视觉效果。

Shader核心代码

cg

v2f vert (appdata v)
{
    v2f o;
    float4 pos = v.vertex;

    // 水波动画:基于顶点的X位置产生相位差
    // _WaveFrequency 控制波纹的密度,_WaveSpeed 控制波纹流动的速度
    float wave = sin(pos.x * _WaveFrequency + _Time.y * _WaveSpeed);
    pos.y += wave * _WaveAmplitude;

    o.pos = UnityObjectToClipPos(pos);
    return o;
}

为了让效果更接近真实水面,你甚至可以叠加X轴和Z轴的正弦波 sin(pos.x) * sin(pos.z),产生更复杂的网格状波动。

✨ 进阶技巧:让动画更自然

掌握了基础,你可以通过下面几个技巧,让动画效果瞬间提升一个档次:

  1. 选择正确的坐标空间:默认是在模型空间(Object Space)下修改顶点,顶点是相对于模型自身的中心点运动的。有时候,你可能希望它们相对于世界空间(World Space)运动(例如让一片草地整体随风摇摆),这就需要获取顶点在世界中的位置来进行计算。

  2. 使用遮罩(Mask)控制区域:你可能不希望整个物体都在动,比如一棵树只有树冠在摇,树干要保持静止。这时就需要“遮罩”。你可以使用顶点颜色(Vertex Color)UV 或者一张黑白纹理作为遮罩。让需要运动的区域(遮罩值为白色)应用动画,静止的区域(遮罩值为黑色)则不产生偏移。

    cg

    // 伪代码示例:用顶点颜色(红色通道)来控制波动幅度
    float mask = v.color.r; // 假设红色部分想让它动
    pos.y += wave * _WaveAmplitude * mask;
  3. 善用属性(Properties)暴露参数:将 _Amplitude(幅度)、_Speed(速度)、_Frequency(频率)等值定义为材质属性,这样你就可以在Inspector面板中直观地调整它们,实时观察动画变化,极大地提升调试效率。

  4. 注意性能:顶点动画是在GPU上执行的,性能非常好。但要注意,如果你的目标是移动平台,应尽量减少顶点数过高的模型使用复杂的顶点动画,以保证流畅度。

这两个示例是打开顶点动画大门的钥匙。你可以尝试修改正弦波的公式,或者结合不同的轴向,创造出旗帜飘扬、灯光闪烁、心跳律动等各种有趣的效果。

您可能感兴趣的与本文相关的镜像

Wan2.2-I2V-A14B

Wan2.2-I2V-A14B

图生视频
Wan2.2

Wan2.2是由通义万相开源高效文本到视频生成模型,是有​50亿参数的轻量级视频生成模型,专为快速内容创作优化。支持480P视频生成,具备优秀的时序连贯性和运动推理能力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值