上一篇博客 说了在 CGPROGRAM 中写代码、顶点处理函数、片元处理函数、以及在两个函数之间传递简单的数据、从 ShaderLab 属性到CG数据类型之间的联系等。这一篇博将稍详细一点说一下 Shader 的基本知识,以及在顶点和片元函数之间传递更多的数据。

Shader的基本理解

简单来说,Shader 决定了一个模型最终呈现在屏幕上的样子。一个模型由很多顶点构成,而每一个顶点,都会经过 Shader 中的顶点处理函数,这个过程,就是从应用将数据传递到顶点处理函数,顶点函数需要将顶点从模型空间转换到屏幕空间,或者说是裁剪空间,也可以简单理解为从 3 维空间转换到屏幕上的 2 维空间。在这个过程中,还可以做一些其他对顶点的操作。

顶点数据经过顶点处理函数处理后,接下来就返回,然后传给片元处理函数,到了这一步,面对的,就是像素,也就是每一个像素的颜色值。在这里,可以根据自己的需求,对每一个像素做处理,例如做高斯模糊,RGB通道分离,等等,各种各样的效果。

向 Shader 传递更多的数据

在之前的博客文章中,我们只是将顶点的坐标传给了Shader,但是我们还需要其他的数据,例如法线,例如切线,纹理坐标等等。接下来,我们将使用结构体来存储要传递的数据,看下面的代码

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
Shader "iMoeGirl/03-Shader" {
Properties {
_MainColor("颜色类型", Color) = (1,1,1,1)
}

SubShader {
Pass {

CGPROGRAM
float4 _MainColor;

#pragma vertex vert
#pragma fragment frag

// 这里定义一个结构体,封装需要从应用传到顶点函数的数据
struct a2v {
float4 vertex : POSITION; // 顶点坐标
float3 normal : NORMAL; // 顶点法线
float4 textcoord : TEXCOORD0; // 第一套纹理坐标(可以有多套)
};

// 这里定义另一个结构体,封装从顶点函数传到片元函数的数据
struct v2f {
float4 position : SV_POSITION;
float3 temp : COLOR0;
};

// 顶点处理函数,传入的是a2v结构体,返回的是要传到片元函数的v2f结构体
v2f vert(a2v v){
v2f result;
result.position = UnityObjectToClipPos(v.vertex);
result.temp = v.normal;
return result;
}

// 片元处理函数,传入的参数是从顶点函数返回的v2f结构体
fixed4 frag(v2f f) : SV_TARGET {
return fixed4(f.temp, 1);
}

ENDCG
}
}

Fallback "VertexLit"
}

a2v(application to vertex),就是从应用(Application)传到顶点函数的结构体,结构体的名字可以自定义。结构体本身不需要使用语义说明,但是结构体里的每一个变量,都需要使用Shader的语义来描述,这样系统才能知道使用什么数据去填充对应的变量。

v2f(vertex to fragment) 就是从顶点函数传到片元函数的结构体,顶点函数完成顶点的处理后,将片元函数需要的数据填充到结构体中返回,系统在调用片元函数时,会自动将顶点函数返回的v2f结构体作为参数传到片元函数内,这样片元函数就可以使用我们定义的数据。

上面顶点函数和片元函数中的代码只是为了演示使用结构体传递数据,没有什么意义。

常用的语义

  • POSITION 顶点坐标(模型空间)
  • NORMAL 法线
  • TANGENT 切线
  • TEXCOORD0 纹理坐标(纹理坐标可以有多组)
  • COLOR 颜色
  • SV_TARGET 颜色
  • SV_POSITION 裁剪空间顶点坐标

下一篇将介绍和光照相关的东西。