侧边栏壁纸
博主头像
火腾

行动起来,活在当下

  • 累计撰写 8 篇文章
  • 累计创建 9 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

静态背景增加动效 我的URP UV动画Shader创作心路​​

温馨提示:
本文章权益归属火腾(www.firedance.cn),转载请注明来源于火腾(www.firedance.cn)。

嗨,大家好!我是你们的技术美术(或者爱捣鼓Shader的程序员)朋友。今天想和大家分享一次特别有趣的“造轮子”经历——为Unity的URP管线编写一个用于UI的UV动画Shader。这不仅仅是一段代码,更像是一次为静态界面注入灵魂的冒险。

​​缘起:当UI不再满足于“静态美”​​

不知道你有没有见过那些让人眼前一亮的游戏UI?比如:

  • 登录界面背景中,流光缓缓掠过的云层。

  • 技能图标上,能量如呼吸般明灭涌动。

  • 经验值进度条里,光芒向着终点缓缓流淌。

这些效果,如果只用序列帧或者Animator去控制图片,不仅费时费力,更会徒增Draw Call。作为一名“懒惰”的开发者,我决定寻求更优雅的解决方案:​​用Shader直接让纹理动起来!​​ 这样,一个材质球就能搞定所有动画,性能高效,艺术同学调节起来也无比方便。

于是,UIUVAnimationShader的创作之旅开始了。

​​第一步:站稳脚跟——继承UI的“优良传统”​​

在Shader的世界里,想搞创新,第一步往往是“抄作业”。不对,是“站在巨人的肩膀上”。我首先深入研究了URP和UGUI的标准Shader。它们已经完美处理了UI渲染的方方面面:

  • ​透明度混合​​:让UI可以层层叠叠而不互相遮挡。

  • ​模板测试​​:这是Mask遮罩功能的核心,必须保留!

  • ​颜色叠加​​:那个_Color属性,让我们能轻松地给UI换色。

我的目标是“增强”,而非“重造”。所以,我毫不犹豫地把这些关键的TagsStencilBlend状态原封不动地“搬”了过来。这确保了我们的动画Shader首先是一个​​合格的UI Shader​​,能和整个UI系统和谐共处。

​​第二步:注入灵魂——操控纹理坐标的魔法​​

现在,轮到重头戏了。如何让一张静态图片动起来?答案就在于操控它的​​纹理坐标​​,也就是我们常说的​​UV​​。

你可以把UV想象成一张世界地图的经纬线,而Shader就是根据这些经纬线去地图(纹理)上查找颜色的。原本,每个UI顶点的UV是固定的,所以采样到的颜色也是固定的。

那么,如果我让这些UV坐标​​随时间变化​​呢?比如,让代表“经度”的U坐标不停地增加,那么采样点就会不停地向右移动,纹理看起来就像在向左流动了!这就是最基础的UV动画。

基于这个简单的想法,我定义了两个最核心的参数:

  • _UVSpeedX:控制U方向(水平)的移动速度。

  • _UVSpeedY:控制V方向(垂直)的移动速度。

在顶点着色器中,我写下了最激动人心的一行代码:

float time = _TimeParameters.x; // 获取游戏的运行时间
animatedUV.x += _UVSpeedX * time; // 让U坐标随时间流逝
animatedUV.y += _UVSpeedY * time; // 让V坐标随时间流逝

​就这?​​ 对,就这!一个最简单的滚动动画就完成了。当我在材质球上把_UVSpeedX设为0.5,立刻就看到纹理开始缓缓流动,那种成就感,简直像魔术师完成了第一个魔术!

​​第三步:精益求精——让魔法更强大​​

只有平移未免太单调了。为了让Shader更强大、更实用,我决定加入更多控制维度。

  1. ​缩放​​:加入了_UVScaleX_UVScaleY。比如,把缩放设为2,纹理就会被放大,可以模拟能量聚集或镜头拉近的效果。​​注意:​​ 缩放的原点在UV的(0,0),也就是左下角。

  2. ​偏移​​:加入了_UVOffsetX_UVOffsetY。这就像在动画开始前,先把纹理的某个特定区域“摆放”到我们的视野中。比如一张很大的雪景图,我可以先偏移到有雪花的部分,再让雪花动起来。

在代码中,我精心安排了它们的执行顺序:​​先缩放,再偏移,最后加上时间动画​​。这个顺序非常重要,不同的顺序会产生完全不同的视觉效果。

​​第四步:融入体系——URP的优雅之道​​

代码写好了,但要让它成为一个“专业”的URP Shader,还得遵循规范。我做了几件事:

  • ​使用HLSL和Core.hlsl库​​:告别旧的CG和UnityCG.cginc,拥抱URP的新语法。

  • ​使用TEXTURE2DSAMPLER宏​​:这是URP中采样纹理的正确姿势。

  • ​使用CBUFFER​:将属性打包,利于SRP Batcher合批,提升性能。

  • ​简化片元着色器​​:我移除了手动的_ClipRect裁剪判断。因为在URP的UI系统中,RectMask2D组件通过Stencil(模板测试)来实现裁剪是更高效、更标准的方式。我们应该相信并利用好引擎提供的这套机制,不画蛇添足。

​​最终成果与感想​​

就这样,经过构思、编码、测试和打磨,UIUVAnimationShader诞生了!

​它的美妙之处在于:​

  • ​对艺术家友好​​:美术或UI同学只需要在材质面板上拖拖滑块,就能实时看到各种动画效果,无需任何代码。

  • ​性能优异​​:动画计算在顶点阶段完成,开销极小。

  • ​功能专注而灵活​​:虽然原理简单,但通过速度、缩放、偏移的组合,可以创造出滚动、扫描、呼吸、漩涡等丰富效果。

现在,你可以把它赋予一张背景图,让它成为流动的星河;也可以赋予一个按钮边框,让它充满科技的动感。这就是Shader的魅力——用简洁的数学,赋予数字世界以动态的生命。

希望这篇“心路历程”能让你对Shader开发有更感性的认识。如果你也心动了,不妨拿代码去试试,创造出属于你的动态UI吧!


​附:shader代码

Shader "URP/UI/UIUVAnimation"
{
    Properties
    {
        [PerRendererData] _MainTex ("Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        
        // UV动画控制参数
        _UVSpeedX ("UV Speed X", Float) = 0.0
        _UVSpeedY ("UV Speed Y", Float) = 0.0
        
        // 可选:UV缩放控制
        _UVScaleX ("UV Scale X", Float) = 1.0
        _UVScaleY ("UV Scale Y", Float) = 1.0
        
        // 可选:UV偏移控制
        _UVOffsetX ("UV Offset X", Float) = 0.0
        _UVOffsetY ("UV Offset Y", Float) = 0.0
        
        // UI必要属性
        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255
        _ColorMask ("Color Mask", Float) = 15
    }

    SubShader
    {
        Tags
        { 
            "Queue"="Transparent" 
            "IgnoreProjector"="True" 
            "RenderType"="Transparent" 
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
            "RenderPipeline"="UniversalPipeline"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
                float4 color : COLOR;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 color : COLOR;
                float4 worldPosition : TEXCOORD1;
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            
            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST;
                float4 _Color;
                float4 _ClipRect;
                float _UVSpeedX;
                float _UVSpeedY;
                float _UVScaleX;
                float _UVScaleY;
                float _UVOffsetX;
                float _UVOffsetY;
            CBUFFER_END

            Varyings vert(Attributes input)
            {
                Varyings output;
                
                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
                output.positionHCS = vertexInput.positionCS;
                output.worldPosition = input.positionOS;
                
                // 计算动画UV
                float2 animatedUV = input.uv;
                
                // 应用UV缩放
                animatedUV.x *= _UVScaleX;
                animatedUV.y *= _UVScaleY;
                
                // 应用UV偏移
                animatedUV.x += _UVOffsetX;
                animatedUV.y += _UVOffsetY;
                
                // 应用时间动画 - 使用正确的时间变量
                float time = _TimeParameters.x; // 使用URP的时间变量
                animatedUV.x += _UVSpeedX * time;
                animatedUV.y += _UVSpeedY * time;
                
                output.uv = TRANSFORM_TEX(animatedUV, _MainTex);
                output.color = input.color * _Color;
                
                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                col *= input.color;
                
                // 简化的裁剪支持 - 移除UnityGet2DClipping调用
                // 如果需要UI裁剪,可以使用RectTransform的Mask组件
                
                return col;
            }
            ENDHLSL
        }
    }
    
    Fallback "Hidden/Universal Render Pipeline/FallbackError"
}

0

评论区