0%

CG基础语法

前言

紧接前面的 Shader 基础框架 和 CG的基本数据类型。这里要讲解基本的CG编写语法,包括函数的声明,定义。

变量的声明

在 CGPROGRAM 中我们想要使用 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
Properties{
_Color("Color",Color)=(1,1,1,1)
_Vector("Vector",Vector)=(1,2,3,4)
_Int("Int",Int) = 20
_Float("Float",Float) = 2.5
_Rangle("Rangle",Range(10,22)) = 11.5

_2D("Texture",2D) = "red"{}
_Cube("Cube",Cube) = "white"{}
_3D("3D",3D) ="blue"{}
}

// subShader
SubShader{
pass{
CGPROGRAM
fixed4 _Color;
float4 _Vector;
float _Int; // 也可以是 int _Int;
float _Float;
float _Rangle;

sampler2D _2D;
samplerCUBE _Cube;
sampler3D _3D;
ENDCG
}
}

可见大部分的值类型属性都可以使用 float 来声明。

函数的声明和定义

函数的定义与 C语言 定义完全相同。而这里要说的是比较特殊的 Vertex 函数 和 Fragment 函数的声明。

一般比较完整的 shader,我们需用声明 Vertex/Fragment 来作为渲染 顶点和片元 的入口函数。

例如:

1
2
3
4
5
6
7
8
9
10
#pragma vertex myvert  // 声明 myvert 为 shader 顶点函数入口
#pragma fragment frag // 声明 frag 为 shader 片元函数入口

// 顶点函数
void myvert(){}
// 片元函数
fixed4 frag(){
return fixed4(0.7,0.5,1,1);
}

语义的使用

往往顶点/片元函数要搭配语义来使用。
语义一般在函数的参数与返回中使用。

如:

1
2
3
4
5
#pragma vertex myvert
// 传递一个 float4 类型参数
float4 myvert(float4 v){

}
  • 而作为顶点 shader 入口,我们希望传入模型的顶点作为参数。这里就可以使用参数的语义 POSITION 。

    1
    2
    3
    4
    // 由系统传入(或者说是从系统中获取到顶点坐标)顶点坐标向量
    float4 myvert(float4 v : POSITION){

    }
  • 而我们的返回的值(剪裁空间坐标系的顶点向量)要告诉给系统,才能被渲染出来,因此可以给函数增加语义 SV_POSITION 。

1
2
3
4
5

float4 myvert(float v:POSITION):SV_POSITION{
// 另一种运算函数 mul(UNITY_MATRIX_MVP,v) ,其中 UNITY_MATRIX_MVP 是Unity 提供的 模型坐标转换为剪裁平面坐标 的转换矩阵
return UnityObjectToClipPos(v); // 将模型坐标转换为剪裁平面坐标
}

使用结构体

Shader CG编程基础数据类型 写了结构体的定义方法,一般来说,尽量不要在结构体中定义函数。

由上面的例子可以看出,有时候往往我们需要传入多个值,甚至需要返回多个值。这里就可以使用结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 结构体 application to Vertex
* 模型空间到顶点空间
* 用来作为参数类型
*/
struct a2v{
// 从系统获取顶点坐标
float4 vertex:POSITION;
};
/**
* 作为返回值
*/
struct v2f{
float4 position:SV_POSITION;
}

// 顶点函数
v2f vert(a2v v){
v2f f;
f.position = UnityObjectToClipPos(v.vertex);
return f;
}
  • 可见,使用结构体后,函数的定义会变得十分清晰。
  • 在结构体中,必须所有的属性都要加上语义
  • 要注意 有些语义只能在特定的函数中使用。 例如 POSITION 只能在顶点函数中使用。

顶点函数和片元函数间的参数传递

有时候我们可能需要从顶点函数中获取到数据传递到片元函数中,这就涉及到顶点函数与片元函数的参数传递。

例子:

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
struct a2v{
// 从系统获取顶点坐标
float4 vertex:POSITION;

// 法线,方位可使用三维(法线只有方向)
// 从系统获取法线方向
float3 normal : NORMAL;

//纹理坐标(坐标取值0-1)
// 获取第1套纹理的坐标
float4 texcoord: TEXCOORD0;
};

struct v2f{
float4 position:SV_POSITION;
// 用于传递顶点数据
// 讲其数据放到 COLOR0中
float3 temp : COLOR0;
};

// 顶点函数
v2f vert(a2v v){

v2f f;
f.position = UnityObjectToClipPos(v.vertex);
// 将顶点的法线变量 复制给 v2f 中临时变量
f.temp = v.normal;
return f;
}
// 片元函数,将结构体a2v 中的法线变量,作为参数传递。
fixed4 frag(v2f f):SV_Target{
return fixed4(f.temp,1);
}
  • 这样就完成了两函数的参数传递。
  • 基本思路是使用一个 COLOR0 语义作为中间桥梁,COLOR0 是一个顶点颜色的存储空间。

结语

需要提一下的,shader 中必须要有一个入口,就像 C 语言的 main 函数,往往 vertex 与 fragment 必须两个同时具备。

着色程序分为顶点程序和片断程序,两者对应着图形流水线上的不同阶段,所以这两个程序都各有一个入口函数。