Unity Shader入门Shader编程初级:Shader结构

【Unity Shader入门】Shader基础概念:渲染流水线
【Unity Shader入门】Shader编程基础:ShaderLab语法
【Unity Shader入门】Shader数学基础:向量(矢量)
【Unity Shader入门】Shader数学基础:矩阵
【Unity Shader入门】Shader数学基础:矩阵变换
【Unity Shader入门】Shader编程初级:Shader结构

Shader编程初级

  • 准备工具
  • Shader结构
    • 最简单的Shader
    • 输入结构体
    • 输出结构体
    • 输入输出常用语义
      • 顶点着色器输入结构体中常用语义
      • 顶点着色器输出结构体中常用语义
      • 片元着色器输出时常用语义
    • 属性控制

准备工具

Unity 2017.1.1f1:用于执行Shader显示渲染效果
Visual Studio 2019:用于Unity代码编辑工具,此处用于编写Shader脚本
ShaderlabVS:Shader语法高亮代码补全Visual Studio插件

Shader结构

最简单的Shader

Unity Shader(ShaderLab)语法有对整个Shader脚本Properties和SubShader等语义块进行详细的介绍。

1
2
#pragma vertex vert     // 顶点着色器函数名称
#pragma fragment frag   // 片元着色器函数名称

它们告诉Unity,哪个函数包含顶点着色器的代码,哪个函数包含了片元着色器的代码。

1
2
//mul(UNITY_MATRIX_MVP,v);
UnityObjectToClipPos(v);

UnityObjectToClipPos这个是把顶点从模型空间直接转化到裁剪空间,也就是进行了M-V-P三次转化,这个函数是回unity重新封装过一次,其实它最初是mul(UNITY_MATRIX_MVP,v);其中,UNITY_MATRIX_MVP是转化矩阵。

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
Shader "Example"
{
    Properties
    {
        _MainTex("Texture",2D) = "White" {}
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
        }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert     // 顶点着色器函数名称
            #pragma fragment frag   // 片元着色器函数名称

            float4 vert(float4 v:POSITION):SV_POSITION
            {
                // mul(UNITY_MATRIX_MVP,v);
                return UnityObjectToClipPos(v);
            }
           
            fixed4 frag():SV_TARGET
            {
                return fixed4(1,1,1,1);
            }
            ENDCG
        }
    }
    Fallback Off
}

输入结构体

声明了一个新的结构体a2v,它包含立顶点着色器需要的模型数据。它包含了顶点着色器需要的模型数据。a表示应用(application),v表示顶点着色器(vertex shadr),a2v的意思就是把数据从应用阶段传递到顶点着色器中。

1
2
3
4
5
6
Struct a2v
{
    float4 vertex:POSITION;            // 用模型顶点填充vertex变量
    Float3 normal:NORMAL;              // 用模型的法线填充normal变量
    Float4 texcoord:TEXCOORD0;         // 用模型的第一套UV填充texcoord
};

模型数据从哪里来:在Unity中,他们是由使用该材质的Mesh Render组件提供的。在每帧调用Draw Call的时候,Mesh Render组件会把他负责渲染的模型数据发送给Unity Shader。我们知道,一个模型通常包含了一组三角面片,每个三角面片由3个顶点构成,而每个顶点又包含了一些数据,例如顶点位置、法线、切线、纹理坐标、顶点颜色等。通过上面的方法,我们就可以在顶点着色器中访问顶点的这些数据模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CGPROGRAM
#pragma vertex vert     // 顶点着色器函数名称
#pragma fragment frag   // 片元着色器函数名称

Struct a2v
{
    float4 vertex:POSITION;            // 用模型顶点填充vertex变量
    Float3 normal:NORMAL;              // 用模型的法线填充normal变量
    Float4 texcoord:TEXCOORD0;         // 用模型的第一套UV填充texcoord
};         

float4 vert(a2v v):SV_POSITION
{
    return UnityObjectToClipPos(v.vertex);
}
           
fixed4 frag():SV_TARGET
{
    return fixed4(1,1,1,1);
}
ENDCG

在实践中,我们往往希望从顶点着色器输出一些数据,例如把模型的法线、纹理坐标等传递给片元着色器。这就涉及顶点着色器和片元着色器之间的通信。

输出结构体

顶点着色器和片元着色器之间如何通信:我们声明了一个新的结构体v2f。v2f用于在顶点着色器和片元着色器之间传递消息。同样,v2f中也需要指定每个变量的语义。

1
2
3
4
5
6
7
Struct v2f
{
    //SV_POSITION语义告诉unity:pos为裁剪空间中的位置信息
    float4 pos:SV_POSITION;
    // COLOR0语义可以存储颜色信息
    fixed3 color:COLOR0;
};

在本例中,我们使用了SV_POSITION和COLOR0语义。顶点着色器的输出结构中必须包含一个变量,他的语义是SV_POSITION。否则,渲染器将无法得到裁剪空间中的顶点坐标,也就无法把顶点渲染到屏幕上。COLOR0语义中的数据在可以由用户自定义,但一般都是存储颜色,例如逐顶点的漫反射颜色或逐顶点的高光反射颜色。类似的语义还有COLOR1等。

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
#pragma vertex vert     //顶点着色器函数名称
#pragma fragment frag   //片元着色器函数名称

Struct a2v
{
    // 用模型顶点填充vertex变量
    float4 vertex:POSITION;        
    // 用模型的法线填充normal变量  
    float3 normal:NORMAL;    
    //用模型的第一套UV填充texcoord          
    float4 texcoord:TEXCOORD0;    
};         

Struct v2f
{
    //SV_POSITION语义告诉unity:pos为裁剪空间中的位置信息
    float4 pos:SV_POSITION;
    // COLOR0语义可以存储颜色信息
    fixed3 color:COLOR0;
};

v2f vert(a2v v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    // 将[-1,1]转变为[0,1]常用写法
    o.color = v.normal * 0.5 + fixed3(0.5,0.5,0.5);
    return o;
}
           
fixed4 frag(v2f i):SV_TARGET
{
    return fixed4(i.color,1);
}
ENDCG

至此,我们就完成了顶点着色器和片元着色器之间的通信。需要注意的是,顶点着色器是逐顶调用的,而片元着色器是逐片元调用的。片元着色器中的输入实际上是把顶点着色器的输出进行差值后得到的结果。

输入输出常用语义

顶点着色器输入结构体中常用语义

POSITION:模型空间中的顶点位置,通常是float4类型
NORMAL:顶点法线,通常是float3类型
TANGENT:顶点切线,通常是float4类型
TEXCOORDn:该顶点的纹理坐标,TEXCOORD0表示第一组坐标纹理,依次类推,通常是float2,float4类型
COLOR:顶点颜色,通常是fixed4或float4类型

Shader Model版本不同,TEXCOORDn中N的支持个数也不尽相同

Shader Model2:8
Shader Model3:8
Shader Model4:16
Shader Model5:16

顶点着色器输出结构体中常用语义

SV_POSITION:裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量。等同于DX9中的POSITION。
COLOR0:通常用于输出第一组顶点颜色,不是必须
COLOR1:通常用于输出第二组顶点颜色,不是必须
TEXCOORD0-TEXCOORD7:通常用于输出纹理坐标,不是必须

片元着色器输出时常用语义

SV_Target:输出值将会储存到渲染目标(render target)中。等同于DX9中COLOR语义。

属性控制

声明了一个属性_Color,它的类型是Color,初始值是(1.0,1.0,1.0,1.0),对应白色。

1
2
3
4
Properties
{
    _Color("Color",Color) = (1,1,1,1)
}

为了在CG代码中可以访问他,我们还需要在CG代码片段中提前定义一个新的变量,这个变量的名称和类型必须与Properties语义块中的属性定义相匹配。

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
Shader "Example"
{
    Properties
    {
        _MainTex("Texture",2D) = "White" {}
        _Color("Color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
        }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert     // 顶点着色器函数名称
            #pragma fragment frag   // 片元着色器函数名称
           
            //只有在CGPROGRAM内再次定义一个与属性块内名字类型相同的变量,属性块对应的变量才能起作用
            fixed4 _Color;

            V2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                // 将[-1,1]转变为[0,1]常用写法
                o.color = v.normal * 0.5 + fixed3(0.5,0.5,0.5);
                return o;
            }
           
            fixed4 frag(v2f i):SV_TARGET
            {
                fixed3 c = i.color;
                // fixed4 .xyzw .rgba   //fixed .x .y fixed2 .xw
                c *= _Color.rgb;
                return fixed4(c,1);
            }
            ENDCG
        }
    }
    Fallback Off
}