Shader Vertex 的初步使用。

Basic

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Shader "Custom/vertexShader" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex : vert
#pragma target 2.0
struct Input {
float2 uv_MainTex;
float4 vertColor;
};
fixed4 _Color;
void vert (inout appdata_full v,out Input o)
{
o.vertColor = v.color;
}
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = IN.vertColor.rgb * _Color.rgb;
}
ENDCG
}
FallBack "Diffuse"
}

参数详情

  1. appdata_full 位于UnityCG.cginc . 顾名思义保存了顶点的绝大部分可用于计算的参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    struct appdata_full {
    //顶点
    float4 vertex : POSITION;
    //切线
    float4 tangent : TANGENT;
    //法线
    float3 normal : NORMAL;
    //顶点坐标(字面意思,以此类推)
    float4 texcoord : TEXCOORD0;
    float4 texcoord1 : TEXCOORD1;
    float4 texcoord2 : TEXCOORD2;
    float4 texcoord3 : TEXCOORD3;
    #if defined(SHADER_API_XBOX360) //4-5 移动端暂时就别想了
    half4 texcoord4 : TEXCOORD4;
    half4 texcoord5 : TEXCOORD5;
    #endif
    fixed4 color : COLOR;
    };

    这里还包含两个appdata_full的简化版,其实都一样 什么时候要用简化版?当然是越简化越省性能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct appdata_base {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    };
    struct appdata_tan {
    float4 vertex : POSITION;
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
    };
  2. SurfaceOutput 位于Lighting.cginc . 这可以说是最常见的一个结构体了但还是稍微写下吧。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct SurfaceOutput {
    //本身的颜色
    fixed3 Albedo;
    //法线贴图
    fixed3 Normal;
    //自发光
    fixed3 Emission;
    //反光
    half Specular;
    //光泽度
    fixed Gloss;
    //透明
    fixed Alpha;
    };

    Effect

    效果展示

    效果就是文章首页放的图了。下面的算法我就不解释了,毕竟这数学也不是靠别人能说清楚的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    Shader "test/vertexAnimation" {
    Properties {
    _ColorA ("Color", Color) = (1,1,1,1)
    _ColorB ("Color", Color) = (1,1,1,1)
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
    _TintAmount("TintAmount",Range(0,1)) = 0.5
    _Speed("Wave Speed",Range(0.1,80)) = 1
    _Frequency("Wave Frequency",Range(0,5)) = 2
    _Amplitude("Wave Amplitude",Range(-1,1)) = 1
    }
    SubShader {
    Tags { "RenderType"="Opaque" }
    LOD 200
    CGPROGRAM
    #pragma surface surf Lambert vertex:vert
    #pragma target 2.0
    sampler2D _MainTex;
    fixed4 _ColorA;
    fixed4 _ColorB;
    float _TintAmount;
    float _Speed;
    float _Frequency;
    float _Amplitude;
    struct Input{
    float2 uv_MainTex;
    float4 vertColor;
    };
    void vert (inout appdata_full v,out Input o)
    {
    UNITY_INITIALIZE_OUTPUT(Input,o);
    float time = _Time * _Speed;
    float waveValueA = sin(time + v.vertex.x * _Frequency) * _Amplitude;
    v.vertex.xyz = float3(v.vertex.x , v.vertex.y + waveValueA , v.vertex.z);
    v.normal = normalize(float3(v.normal.x + waveValueA,v.normal.y,v.normal.z));
    o.vertColor = float4(waveValueA,waveValueA,waveValueA,255);
    }
    void surf (Input IN, inout SurfaceOutput o) {
    // Albedo comes from a texture tinted by color
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    float3 tintColor = lerp(_ColorA,_ColorB,IN.vertColor).rgb;
    o.Albedo = c.rgb * (tintColor * _TintAmount);
    o.Alpha = c.a;
    }
    ENDCG
    }
    FallBack "Diffuse"
    }

    WARNING

    #pragma声明函数

    在 #pragma 声明函数的时候,不要重名否则会报错。这是常识,引擎编译的时候会自动搜索所有编译期文件是否包含此函数。

    in out inout

    in out inout 是cg语言提供的输入输出关键字,用来表示图形硬件上的不同寄存器。
    引用《GPU 编程与CG 语言之阳春白雪下里巴人》的一段话来概括:

    1
    2
    3
    4
    5
    6
    7
    8
    1. in: 修辞一个形参只是用于输入,进入函数体时被初始化,且该形参值 的改变不会影响实参值,这是典型的值传递方式。
    2. out: 修辞一个形参只是用于输出的,进入函数体时并没有被初始化,这 种类型的形参一般是一个函数的运行结果;
    3. inout: 修辞一个形参既用于输入也用于输出,这是典型的引用传递。 举例如下:
    void myFunction(out float x); //形参 x,只是用于输出
    void myFunction(inout float x); //形参 x,即用于输入时初始化,也用于输出数据
    void myFunction(in float x); //形参 x,只是用于输入
    void myFunction(float x); /等价与 in float x,这种用法和 C\C++完全一致 也可以使用 return 语句来代替 out 修辞符的使用。输入\输出修辞符通常和语
    义词一起使用,表示顶点着色程序和片段着色程序的输入输出。

使用Lambert的vertex

使用Lambert的vertex自定义函数的时候编辑器默认会报错,这个时候加入下面代码。这些代码可以看作是对Direcxtx11的hack。

1
UNITY_INITIALIZE_OUTPUT(Input,o);

使用_Time

如果直接使用_Time * Float 那么默认使用_Time.x

1
float4 _Time : Time (t/20, t, t*2, t*3), use to animate things inside the shaders