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),产生更复杂的网格状波动。
✨ 进阶技巧:让动画更自然
掌握了基础,你可以通过下面几个技巧,让动画效果瞬间提升一个档次:
-
选择正确的坐标空间:默认是在模型空间(Object Space)下修改顶点,顶点是相对于模型自身的中心点运动的。有时候,你可能希望它们相对于世界空间(World Space)运动(例如让一片草地整体随风摇摆),这就需要获取顶点在世界中的位置来进行计算。
-
使用遮罩(Mask)控制区域:你可能不希望整个物体都在动,比如一棵树只有树冠在摇,树干要保持静止。这时就需要“遮罩”。你可以使用顶点颜色(Vertex Color)、UV 或者一张黑白纹理作为遮罩。让需要运动的区域(遮罩值为白色)应用动画,静止的区域(遮罩值为黑色)则不产生偏移。
cg
// 伪代码示例:用顶点颜色(红色通道)来控制波动幅度 float mask = v.color.r; // 假设红色部分想让它动 pos.y += wave * _WaveAmplitude * mask;
-
善用属性(Properties)暴露参数:将
_Amplitude(幅度)、_Speed(速度)、_Frequency(频率)等值定义为材质属性,这样你就可以在Inspector面板中直观地调整它们,实时观察动画变化,极大地提升调试效率。 -
注意性能:顶点动画是在GPU上执行的,性能非常好。但要注意,如果你的目标是移动平台,应尽量减少顶点数过高的模型使用复杂的顶点动画,以保证流畅度。
这两个示例是打开顶点动画大门的钥匙。你可以尝试修改正弦波的公式,或者结合不同的轴向,创造出旗帜飘扬、灯光闪烁、心跳律动等各种有趣的效果。
2809

被折叠的 条评论
为什么被折叠?



