Unity Shader学习之顶点着色器

一、顶点着色器的介绍

        顶点着色器 (Vertex Shader) 是 Unity Shader 流水线中的第一个可编程阶段,主要负责处理顶点数据,如位置、法线、UV 坐标等。它为每个顶点执行一次,可用于实现顶点变换、动画、变形等效果。

Shader "Custom/VertexShaderExample" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // 顶点输入结构
            struct appdata {
                float4 vertex : POSITION;   // 顶点位置
                float3 normal : NORMAL;     // 法线方向
                float4 tangent : TANGENT;   // 切线方向
                float2 uv : TEXCOORD0;      // 第一组UV坐标
            };

            // 顶点输出结构(片元输入)
            struct v2f {
                float4 pos : SV_POSITION;   // 裁剪空间位置
                float3 worldPos : TEXCOORD0; // 世界空间位置
                float3 normal : TEXCOORD1;  // 世界空间法线
                float2 uv : TEXCOORD2;      // UV坐标
            };

            // 顶点着色器
            v2f vert (appdata v) {
                v2f o;
                // 将顶点从模型空间转换到裁剪空间
                o.pos = UnityObjectToClipPos(v.vertex);
                // 计算世界空间位置
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                // 计算世界空间法线
                o.normal = UnityObjectToWorldNormal(v.normal);
                // 传递UV坐标
                o.uv = v.uv;
                return o;
            }

            // 片元着色器
            fixed4 frag (v2f i) : SV_Target {
                // 简单的漫反射光照计算
                float3 lightDir = _WorldSpaceLightPos0.xyz;
                float ndotl = saturate(dot(i.normal, lightDir));
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                col.rgb *= ndotl; // 应用光照
                return col;
            }
            ENDCG
        }
    }
}
1. 顶点着色器输入数据

顶点着色器的输入通常通过appdata结构体定义,常用的语义有:

  • POSITION:顶点位置(模型空间)
  • NORMAL:法线方向(模型空间)
  • TANGENT:切线方向(模型空间)
  • TEXCOORD0-7:纹理坐标
  • COLOR:顶点颜色
struct appdata {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
    float2 uv : TEXCOORD0;
    fixed4 color : COLOR;
};
2. 顶点着色器输出数据

顶点着色器的输出通常是一个传递到片元着色器的结构体,常用语义有:

  • SV_POSITION:裁剪空间位置(必需)
  • TEXCOORD0-7:自定义数据(如 UV、法线、世界位置)
  • COLOR:顶点颜色
struct v2f {
    float4 pos : SV_POSITION;
    float3 worldPos : TEXCOORD0;
    float3 normal : TEXCOORD1;
    float2 uv : TEXCOORD2;
    fixed4 color : COLOR;
};

二、图形编程的语义

   1. 输入语义

   POSITION:模型空间顶点位置(float3/float4)

   SV_POSITION:裁剪空间顶点位置(顶点着色器输出必选)

  2. 法线与切线相关语义

         NORMAL:模型空间法线方向(float3)

         TANGENT:模型空间切线方向(float4,w 分量用于确定副切线方向)

  3. 纹理坐标相关语义

        TEXCOORD0TEXCOORD1...TEXCOORD7:纹理坐标(float2/float3/float4)

  4. 颜色相关语义

        COLOR:顶点颜色(fixed4/float4)

  5. 语义定义汇总

        

三、顶点着色器应用

        顶点着色器是图形渲染管线中的关键阶段,主要负责处理顶点数据。其核心功能可以分为以下几类:

        1. 坐标空间变换

        这是顶点着色器最基本的功能,将顶点从模型空间转换到裁剪空间。

v2f vert (appdata v) {
    v2f o;
    // 模型空间 → 裁剪空间
    o.pos = UnityObjectToClipPos(v.vertex);
    // 计算世界空间位置用于光照
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    // 传递UV坐标
    o.uv = v.uv;
    return o;
}
        2. 顶点动画

        通过修改顶点位置实现各种动画效果。

        案例:波浪动画

v2f vert (appdata v) {
    v2f o;
    
    // 计算波浪偏移
    float wave = _Amplitude * sin(v.vertex.x * _Frequency + _Time.y * _Speed) * 
                 sin(v.vertex.z * _Frequency * 0.5 + _Time.y * _Speed * 0.7);
    
    // 应用偏移
    v.vertex.y += wave;
    
    // 转换到裁剪空间
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
}
        3. 法线修改

        修改法线方向以影响光照计算,常用于创建特殊视觉效果。

        案例:膨胀效果(修改法线方向)

v2f vert (appdata v) {
    v2f o;
    
    // 沿法线方向膨胀
    v.vertex.xyz += v.normal * _Amount * sin(_Time.y);
    
    o.pos = UnityObjectToClipPos(v.vertex);
    o.normal = UnityObjectToWorldNormal(v.normal);
    o.uv = v.uv;
    return o;
}
        4. 光照预处理

        在顶点着色器中进行部分光照计算,减轻片元着色器负担。

        案例 :顶点光照(简化版)

v2f vert (appdata v) {
    v2f o;
    
    // 计算世界空间位置和法线
    float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    
    // 计算光照方向(简化,假设为方向光)
    float3 lightDir = _WorldSpaceLightPos0.xyz;
    
    // 计算漫反射光照
    float ndotl = saturate(dot(worldNormal, lightDir));
    fixed4 diffuse = _LightColor0 * ndotl * _Color;
    
    // 传递光照结果到片元着色器
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    o.color = diffuse;
    return o;
}
        5. 几何变形

        基于各种算法修改顶点位置,创建复杂几何效果.

        案例:基于噪声的变形

// 需要包含噪声函数库
#include "Noise.cginc"

v2f vert (appdata v) {
    v2f o;
    
    // 计算噪声值
    float noise = snoise(float3(v.vertex.x * _Frequency, v.vertex.y * _Frequency, _Time.y * _Speed));
    
    // 应用变形
    v.vertex.xyz += v.normal * noise * _Amplitude;
    
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
}

   

    核心功能分类表:

 四、顶点的动态变化

顶点动态变化日常会使用到的功能如下:

五、顶点着色器性能优化

        顶点着色器是图形渲染管线中的关键环节,其性能优化对于提升游戏或应用的帧率至关重要。由于顶点着色器为每个顶点执行一次,当处理大量顶点时,低效的代码会成为性能瓶颈。以下从多个角度详细介绍顶点着色器的性能优化方法。

1. 减少计算复杂度

 避免昂贵的数学函数

  • 昂贵操作:三角函数 (sin/cos/tan)、幂运算 (pow)、开方 (sqrt)、倒数 (rcp) 等
  • 优化方法
    • 预先计算常量值(如在 CPU 端计算或使用 #define)
    • 使用近似函数(如快速近似 sin/cos)
    • 减少重复计算(使用临时变量存储中间结果)
// 优化前
float angle = v.vertex.x * _Frequency + _Time.y * _Speed;
float wave = _Amplitude * sin(angle) * sin(v.vertex.z * _Frequency * 0.5 + _Time.y * _Speed * 0.7);

// 优化后(减少三角函数调用)
float angleX = v.vertex.x * _Frequency;
float angleZ = v.vertex.z * _Frequency * 0.5;
float timeFactor = _Time.y * _Speed;
float waveX = _Amplitude * sin(angleX + timeFactor);
float waveZ = sin(angleZ + timeFactor * 0.7);
float wave = waveX * waveZ;
2. 压缩传递数据
  • 使用更小的数据类型:
    • fixed(11 位浮点数,适合颜色)代替float
    • half(16 位浮点数)代替float(精度要求不高时)
  • 合并数据到更少的寄存器:
    • 使用float4存储多个值(如位置和颜色)
    • 利用向量的多个分量(如用 w 分量存储额外数据)
// 优化前
float3 worldPos : TEXCOORD0;
float3 normal : TEXCOORD1;
float2 uv : TEXCOORD2;

// 优化后(合并到TEXCOORD0和TEXCOORD1)
float4 posAndUV : TEXCOORD0; // .xyz = worldPos, .w = uv.x
float4 normalAndUV : TEXCOORD1; // .xyz = normal, .w = uv.y
3. 空间变换优化

        避免不必要的变换

  • 只计算必要的空间坐标(如不需要世界空间位置则不计算)
  • 复用已有的变换矩阵(如利用UNITY_MATRIX_MVP
// 优化前(分步变换)
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
float4 viewPos = mul(UNITY_MATRIX_V, worldPos);
float4 clipPos = mul(UNITY_MATRIX_P, viewPos);

// 优化后(直接使用MVP矩阵)
float4 clipPos = mul(UNITY_MATRIX_MVP, v.vertex);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值