##Unity shader学前准备之CG语言(6)

       本节主要内容:

  1. Cg语言中函数的写法,以及函数是否可以重载
  2. 订单\片段着色程序入口函数(类似C/C++中的main()函数)
  3. Cg标准函数库

###函数

       函数可以被看作一个由用户定义的操作。Cg 语言中的函数声明形式与 C\C++中相同,由返回类型(return type)、函数名、形参列表(parameter list, 位于括号中,并用逗号分隔的参数表)和函数体组成。函数体包含在花括号中。如果没有返回值,则函数的返回类型是 void。

       注意:参数传递机制非常复杂,请参阅上一章,此外,有一个比较特殊的函数形参类型,不论在 C\C++中还是在 Cg 语言中,都是一个令人头疼的话题,它就是数组形参。

####数组形参

       在 C\C++中,当一个数组作为函数的形参时,实际上传入的只是指向首元素 的指针,并且数组边界被忽略。而在 Cg 语言中不存在指针机制(图形硬件不支持),数组作为函数形参,传递 的是数组的完整拷贝。

The most important difference from C is that arrays are first-class types. That means array assignments actually copy the entire array, and arrays that are passed as parameters are passed by value, rather than by reference

       这段英文中描述道,Cg 语言中数组是“first-class types”,中文翻译为“第一 类数据类型”,所谓“第一类(first-class)”的含义是,强调该类型数据是“不可分 解的、最高级别的、不被重述的”,即“第一类数据类型”和“基础数据类型”的概 念是近同的。

       数组类型变量作为函数形参,可以是一维的也可以是多维的,并且不必声明 数组长度,即 Unsized Array。


float myFunc( float vals[]) {
    float sum = 0.0;
    for(int i = 0; i< vals.length; i++) {
        sum += vals[i]; 
    }
    return sum; 
}

       myFunc 是一个函数,输入一个数组变量,计算数组中所有数据之和,然后 返回 float 类型数据。请注意:数组形参不必指定长度。但如果指定了函数中形参数组的长度,那么在调用该函数时实参数组的长度和形参数组的长度必须保持一致,如果没有保持一致,编译时会出现错误提示信息:error C1102: incompatible type for parameter…


float myFunc( float vals[3]) {
    float sum = 0.0;
    for(int i = 0; i< vals.length; i++) {
        sum += vals[i]; 
    }
    return sum; 
}


void main(...) {
    float a[2] = {0.0, 1.0};
    float b[3] = {0.0, 1.0, 2.0};
    myFunc(a); //错误调用,会导致编译错误 
    myFunc(b); //正确调用
}

       对于函数的形参数组最好不要指定长度,这样就可以片配任意长度的参数数组。如果函数的形参数组是多维数组,其声明方式和上面是一样的,可以不指定长度;如果指定形参数组长度,则实参数组长度必须保持一致。

###函数重载

       Cg 语言支持函数重载(Functon Overlaoding),其方式和 C++基本一致,通过形参列表的个数和类型来进行函数区分。例如:


bool function(float a, float b)     {return ( a == b);} 
bool function(boo a, bool b)        {return ( a == b);}

       Cg 语言标准函数库中绝大部分函数都被重载过。

###入口函数

       所谓入口函数,即一个程序执行的入口,例如 C\C++程序中的 main()函数。

       通常高级语言程序中只有一个入口函数,不过由于着色程序分为顶点程序和片断程序,两者对应着图形流水线上的不同阶段,所以这两个程序都各有一个入口函数。

       顶点程序和片段程序有且只有一个入口函数,当程序进行编译时,需要指定入口函数名称除非入口函数名为 main。如何区分哪个函数是入口函数呢?事实上,顶点程序和片段程序的入口函数形式,已经完全由 它们在渲染管线中所处的阶段所决定。

       之前的文章以及提到过,在片段程序中往往涉及到纹理颜色的处理,其输入参数中常有纹理形参的声明。所以通过观察程序的输入输出语义绑定,就可以区分入口函数对应到顶点程序还是片段程序。而内部函数则忽略任何应用到形参上的语义,通常也没有人会在内部函数使用语义词。

下面的代码展示了一个顶点程序的入口函数,名称为 C2E1v_green,这个顶 点着色程序接收二维顶点数据,然后转换为齐次坐标,并将该顶点设置为绿色,最后 使用 return 语句输出。如果电脑安装了 Cg,该程序文件位于“NVIDIA Corporation\Cg\examples\OpenGL\basic\ 01_vertex_program\C2E1v_green.cg”


struct C2E1v_Output {
    float4 position : POSITION; 
    float3 color : COLOR;
};

C2E1v_Output C2E1v_green(float2 position : POSITION) {
    C2E1v_Output OUT;

    OUT.position = float4(position,0,1); 
    OUT.color = float3(0,1,0);

    return OUT; 
}

###CG标准函数库

       和 C 的标准函数库类似,Cg 提供了一系列内建的标准函数。这些函数用于执行数学上的通用计算或通用算法(纹理映射等),例如,需要求取入射光线的反射光线方向向量可以使用标准函数库中的 reflect 函数,求取折射光线方向向量可以使用 refract 函数,做矩阵乘法运算时可以使用 mul 函数。

       有些函数直接和 GPU 指令相对应,所以执行效率非常高。绝大部分标准函 数都被重载过,用于支持不同长度的数组和向量作为输入参数。

CG标准函数库分为5个部分:

  1. 数学函数(Mathematical Functions)
  2. 几何函数(Geometric Functions)
  3. 纹理映射函数(TextureMapFunctions)
  4. 偏导数函数(Derivative Functions)
  5. 调试函数(Debugging Function)

具体库函数可以在此链接看到,不再赘述。

       数学函数用于执行数 学上常用计算,包括:三角函数、幂函数、园函数、向量和矩阵的操作函数。

       几何函数用于执行和解析几何相关的计算,例如根据入射 光向量和顶点法向量,求取反射光和折射光方向向量。Cg 语言标准函数库中有 3 个几何函数会经常被使用到,分别是:normalize 函数,对向量进行归一化;reflect 函数,计算反射光方向向量;refract 函数,计算折射光方向向量。

注意

  1. 着色程序中的向量最好进行归一化之后再使用,否则会出现难以预料的 错误;
  2. reflect 函数和 refract 函数都存在以“入射光方向向量”作为输入参数, 注意这两个函数中使用的入射光方向向量,是从外指向几何顶点的;平时我们在着色程序中或者在课本上都是将入射光方向向量作为从顶点出发。

       纹理映射函数:s 象征一元、二元、三元纹理坐标;z 代表使用“深度比较(depth comparison)” 的值;q 表示一个透视值(perspective value,其实就是透视投影后所得到的齐次坐 标的最后一位),这个值被用来除以纹理坐标(S),得到新的纹理坐标(已归一 化到 0 和 1 之间)然后用于纹理查询。

       纹理函数非常多,总的来说,按照纹理维数进行分类,即:1D 纹理函数, 2D 纹理函数,3D 纹理函数,已经立方体纹理。需要注意,TexREC 函数查询的纹理实际上也是二维纹理。

       偏导函数:ddx:返回关于屏幕坐标 x 轴的偏导数;ddy:返回关于屏幕坐标 y 轴的偏导数

       调试函数

函数 功能
void debug(float4 x) 如果在编译时设置了 DEBUG,片段着 色程序中调用该函数可以将值 x 作为 COLOR 语义的最终输出;否则该函数 什么也不做。

debug 函数实际上十分鸡肋,能起到 的作用非常有限的,随着而来的影响是“难以查找程序逻辑错误”。对 Cg 程 序无法像 C++程序一样进行运行调试,步步跟踪。(菊长PS:其实可以步步调试追踪的语言屈指可数)