Cg(类似HLSL)简介
来源:《GPU编程与CG语言》
结构:
一、Cg概述
二、Cg基本数据类型
三、数组类型
四、结构类型struct
五、类型转换
六、操作符
七、 输入\输出与语义绑定
八、语义词(Semantic)与语义绑定
九、 函数与数组形参
十、入口函数
十一、CG 标准函数库
一、Cg概述
Cg(C for Graphcis)语言,是NVIDIA 与Microsoft 合作研发,旨在为开发人员提供一套方便、跨平台(良好的兼容性),控制可编程图形硬件的高级语言。Cg 语言的语法结构与C 语言非常类似,使用Cg 编写的着色程序默认的文件后缀是*. Cg。
Cg 同时被OpenGL 与Direct3D 两种编程API 所支持。这一点不但对开发人员而言非常方便,而且也赋予了Cg 程序良好的跨平台性。一个正确编写的Cg应用程序可以不做任何修改的同时工作在OpenGL 和Direct3D 之上。
Cg 程序的编译不但依赖于宿主程序所使用的三维编程接口,而且依赖于图形硬件环境,因为图形硬件自身的限制,不一定支持某种Cg 语句,例如,如果你所使用的GPU 并不支持循环控制指令,那么在Cg 程序中编写的循环控制语句将无法通过编译。 被特定的图形硬件环境或AIP 所支持的Cg 语言子集,被称为Cg Profiles。需要注意的是: profile 分为顶点程序的profile 和片段程序的profile ,这是因为顶点着色器和片段着色器原本就不是工作在同一个硬件。Cg Profiles 是Cg 语言的重要组成部分,在使用Cg 语言编写着色程序时,首先要考虑的一点就是“当前的图形硬件环境支持那个Cg Profile”,这直接关系到您所编写的着色程序是否可以在当前的图形硬件上运行。
任意一种shader language 都是基于可编程图形硬件的(寄存器、指令集等),这也就意味着:不同的图形硬件对应着不同的功能子集。Profile 按照功能可以划分为顶点Profile 和片断Profile,而顶点profile 和片段profile 又基于OpenGL 和DirectX 的不同版本或扩展,划分为各种版本。从某种意义上而言,OpenGL 和DirectX 的发展历程成就了Cg 语言。
二、Cg基本数据类型
- float , 32 位浮点数据 ,一个符号位。浮点数据类型被所有的profile 支持(但是DirectX8 pixel profiles 在一些操作中降低了浮点数的精度和范围)
- half,16 位浮点数据
- int,32 位整形数据 ,有些profile 会将int 类型作为float 类型使用
- fixed,12 位定点数 ,被所有的fragment profiles 所支持
- bool 布尔数据,通常用于if 和条件操作符(?:),布尔数据类型被所有的profiles 支持
- sampler* 纹理对象的句柄 (the handle to a texture object),分为6 类:sampler,sampler1D, sampler2D, sampler3D, samplerCUBE,和samplerRECT。DirectX profiles 不支持samplerRECT 类型,除此之外这些类型被所有的pixelprofiles 和 NV40 vertex program profile 所支持
- string ,字符类型,该类型不被当前存在的profile 所支持,实际上也没有必要在Cg 程序中用到字符类型,但是你可以通过Cg runtime API 声明该类型变量,并赋值;因此,该类型变量可以保存Cg 文件的信息
除了上面的基本数据类型外, Cg还提供了内置的向量数据类型(built-in vector data types),内置的向量数据类型基于基础数据类型。 例如:float4,表示float 类型的4 元向量;bool4,表示bool类型4 元向量。注意:向量最长不能超过4 元,即在Cg 程序中可以声明float1、float2、float3、float4 类型的数组变量,但是不能声明超过4 元的向量。Cg 中向量、矩阵与数组是完全不同,向量和矩阵是内置的数据类型(矩阵基于向量),而数组则是一种数据结构,不是内置数据类型!
附:向量初始化方式一般为:
float4 array = float4(1.0, 2.0, 3.0, 4.0);
较长的向量还可以通过较短的向量进行构建:
float2 a = float2(1.0, 1.0);
float4 b = float4(a, 0.0, 0.0);
此外,Cg 还提供 矩阵数据类型 ,不过最大的维数不能超过4*4 阶。例如:
float1x1 matrix1;//等价于float matirx1; x 是字符,并不是乘号!
float2x3 matrix2;// 表示2*3 阶矩阵,包含6 个float 类型数据
float4x2 matrix3;// 表示4*2 阶矩阵,包含8 个float 类型数据
float4x4 matrix4;//表示4*4 阶矩阵,这是最大的维数
矩阵的初始化方式为:
float2x3 matrix5 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
三、数组类型
数组数据类型在Cg 程序中的作用是: 作为函数的形参,用于大量数据的转递。 作为从外部应用程序传入大量参数到Cg 的顶点程序中的形参接口,例如与皮肤形变相关的矩阵数组,或者光照参数数组等。 进行数组变量声明时,一定要指定数组长度,除非是作为函数参数而声明的形参数组。 并且在当前的profiles 中,数组的长度和所引用的数组元素的地址必须在编译时就知道。
Cg 中声明数组变量的方式和C 语言类似,如:
float a[10];//声明了一个数组,包含10 个float 类型数据
float4 b[10];//声明了一个数组,包含10 个float4 类型向量数据
对数组进行初始化的方式为:
float a[4] = {1.0, 2.0, 3.0, 4.0}; //初始化一个数组
要获取数组长度,可以调用“.length”,例如:
float a[10]; //声明一个数组
int length = a.length;//获取数组长度
声明多维数组以及初始化的方式如下所示:
float b[2][3] = {{0.0, 0.0, 0.0},{1.0, 1.0, 1.0}};
对多维数组取长度的方式为:
int length1 = b.length; // length1 值为2
int length2 = b[0].length; // length2 值为3
数组和矩阵有些类似,但是并不是相同。 例如4*4 阶数组的的声明方式为:float M[4][4];4 阶矩阵的声明方式为:float4x4 M。前者是一个数据结构,包含16个float 类型数据,后者是一个4 阶矩阵数据。float4x4 M[4],表示一个数组,包含4 个4 阶矩阵数据。
四、结构类型struct
一个结构体相当于一种数据类型,可以定义该类型的变量。 引入结构体机制,赋予了Cg 语言一丝面向对象的色彩。还记得C++中,结构体与类的区别吗?没有区别,除了默认访问属性在结构体中为public,类中为private,所以结构体与类是非常近似的。目前的Cg 语言中的结构体以展现“封装”功能为主,并不支持继承机制。
结构体的声明以关键字struct 开始,然后紧跟结构体的名字,接下来是一个大括号,并以分号结尾( 不要忘了分号 )。大括号中是结构体的定义,分为两大类:成员变量和成员函数。如:
struct myAdd
float val;
float add(float x)
return val + x;
myAdd s;
//“.“引用结构体中的成员变量和成员函数
float a = s.value;
float b = s.add(a);
一般来说, Cg 的源代码都会在文件首部定义二个结构体 ,分别用于定义输人和输出的类型,这二个结构体定义与普通的C 结构定义不同, 除了定义结构体成员的数据类型外,还定义了该成员的绑定语义类型( Binding Semantics) , 所谓绑定语义类型是为了与宿主环境进行数据交换的时候识别不同数据类型的。 目前Cg 支持的绑定语义类型包括 POSTION (位置),COLOR(颜色),NORMAL(法向量),Texcoord(纹理坐标) 等类型。
五、类型转换
Cg 中的类型转换和C 语言中的类型转换很类似。C 语言中类型转换可以是强制类型转换,也可以是隐式转换,如果是后者,则数据类型从低精度向高精度转换。在Cg 语言中也是如此。例如:
float a = 1.0;
half b = 2.0;
float c = a+b; //等价于float c = a + (float)b;
//当有类型变量和无类型常量数据进行运算时,该常量数据不做类型转换
float a = 1.0;
float b = a + 2.0; //2.0 为无类型常量数据,编译时作为float 类型
常量的类型后缀(type suffix)有3 种: f(float) h(half) x(fixed) 。如
float a = 1.0;
float b = a + 2.0h; //2.0h 为half 类型常量数据,运算是需要做类型转换
六、操作符
Cg中的操作符与C语言中的类似(操作符的功能和写法与C相同,但用法不尽相同),按照操作符的功能可以划分为:关系操作符、逻辑操作符、条件操作符。 Cg中有一类较为独特的操作符,称为Swizzle操作符, 这个操作符用于取出向量类型变量中的分量。 此外,与C语言不同的是, Cg允许在向量类型变量上使用操作符,例如>操作符可以用来比较两个向量各个分量的大小关系。
操作符优先顺序:
- 关系操作符< <= != == >= > 运算后的返回类型均为bool类型。注,Cg语言表达式允许对向量使用所有的boolean operator,如果是二元操作符,则被操作的两个向量的长度必须一致。
float3 a = float4(0.5, 0.0, 1.0);
float3 b = float4(0.6, -0.1, 0.9);
bool3 c = a<b;
//运算后向量c的结果为float3(true, false, true);
2. 逻辑操作符&& || ! 运算后的返回类型均为bool类型。注,同上,逻辑操作符也可以对向量使用,返回的变量类型是同样长度的内置bool向量。
3. 数学操作符:*乘、/除、-取反、+加、-减、%求余、++、--、*=、/=、+=、-=。 Cg语言对向量的数学操作提供了内置的支持。注意:求余操作符%。只能在int类型数据间进行,否则编译器会提示错误信息: error C1021: operands to “%” must be integral。
注:当使用这些数学操作符对一个标量和一个向量进行运算时,标量首先被复制到一个长度相同的向量中,然后进行运算。
void function()
float2 a = float2(1.0, 1.0);
float b = 2.0;
f *= b;
f *= 2.0;
}
4. Swizzle操作符
swizzle操作符后接x、y、z、w,分别表示原始向量的第一个、第二个、第三个、第四个元素;swizzle操作符后接r、g、b和a的含义与前者等同。不过为了程序的易读性,建议对于表示颜色值的向量,使用swizzle操作符后接r、g、b和a的方式。:swizzle操作符只能对结构体和向量使用,不能对数组使用。
float4(a, b, c, d).xyz //等价于 float3(a, b, c)
float4(a, b, c, d).xyy //等价于 float3(a, b, b)
float4(a, b, c, d).wzyx //等价于 float4(d, c, b, a)
float4(a, b, c, d).w //等价于 float d
float a = 1.0;
float4 b = a.xxxx;
//要从数组中取值必须使用[]符号
float a[3] = {1.0,1.0,0.0};
float b = a[0]; //正确
float c = a.x; //编译会提示错误信息
5. 条件操作符如 (a < 0) ? (b = a) : ( c = a)
Cg中的条件操作符一个独特的性能是:支持向量运算。
float3 h = float3(-1.0,1.0,1.0);
float3 i = float3(1.0,0.0,0.0);
float3 g = float3(1.0,1.0,0.0);
float3 k;
k = (h < float3(0.0,0.0,0.0))?(i):(g);
//三元向量h与float3(0.0, 0.0, 0.0)做比较运算后结果为(true, false, false),
以i的第一个数据赋值给K的第一个数据,g的第二个和第三个数据赋值给k的第二
//和第三个数据,K的值为(1.0, 1.0
, 0.0)
6. 控制流语句
Cg中的控制流语句和循环语句与C语言类似:条件语句有:if、if-else;循环语句有:while、for。break语句可以和在for语句中使用。Cg语言中的控制流语句要求其中的条件表达式返回值都是bool类型,这一点是与C语言不同之处(C语言中,条件表达式返回值可以是0、1)。“在其他的profiles中,for和while循环只有当确切的知道循环次数时才能被使用”。如果没有确切的把握,不要在低级的profiles中使用循环控制语句。同样,return只能作为最后一条语句出现。函数的递归调用(recursion)在Cg语言中是被禁止的。Switch 、case和default在Cg中作为保留关键字存在,但是它们目前不被任何profile所支持。
七、输入\输出与语义绑定
1、前言
Cg 中也创造了一系列独特的关键字,这些关键字不但用于指定输入图元的数据含义(是位置信息,还是法向量信息),本质也则对应着这些图元数据存放的硬件资源(寄存器或者纹理),称之为语义词(Semantics) ,通常也根据其用法称之为绑定语义词(binding semantics)。
除语义词外,Cg 中还提供了三个关键字, in、out、inout ,用于表示函数的输入参数的传递方式,称为输入\输出关键字,这组关键字可以和语义词合用表达硬件上不同的存储位置,即同一个语义词,使用in 关键字修辞和out 关键词修辞,表示的图形硬件上不同的寄存器。
Cg 语言还提供两个修辞符: uniform ,用于指定变量的数据初始化方式。 const 关键字的含义与C\C++中相同,表示被修辞变量为常量变量。
2、回顾
应用程序(宿主程序)将图元信息(顶点位置、法向量、纹理坐标等)传递给顶点着色程序;顶点着色程序基于图元信息进行坐标空间转换,运算得到的数据传递到片段着色程序中;片段着色程序还可以接受从应用程序中传递的纹理信息,将这些信息综合起来计算每个片段的颜色值,最后将这些颜色值输送到帧缓冲区(或颜色缓冲区)中。 这些是顶点着色程序和片段着色程序的基本功能和数据输入输出,实际上现在的着色程序已经可以接受多种数据类型 ,并灵活的进行各种算法的处理,如,可以接受光源信息(光源位置、强度等)、材质信息(反射系数、折射系数等)、运动控制信息(纹理投影矩阵、顶点运动矩阵等),可以在顶点程序中计算光线的折射方向,并传递到片段程序中进行光照计算。
基于以上流程提出相关问题: 1、从应用程序传递到GPU的数据,分为图元信息数据(在GPU处理的基本数据如顶点位置信息等)和其他的离散数据(在GPU运行流程中不会发生变化,如材质对光的反射、折射信息),这两种输入数据如何区分?2、从应用程序传递到GPU中的图元信息如何区分类型,即,顶点程序怎么知道一个数据是位置数据,而不是法向量数据?3、顶点着色程序与片段着色程序之间的数据传递如何进行?
3、 输入数据流:Varying 、 Uniform
- Uniform inputs, 表示一些与三维渲染有关的离散信息数据 ,这些数据通常由应用程序传入,并 通常不会随着图元信息的变化而变化 ,如材质对光的反射信息、运动矩阵等。Uniform 修辞一个参数,表示该参数的值由外部应用程序初始化并传入。
uniform float brightness //从“外部”传入一个float 类型数据
uniform float4x4 modleWorldProject //从“外部”传入一个4阶矩阵
//“外部”的含义
通常是用OpenGL 或者DirectX 所编写的应用程序
//uniform 修辞的变量的值是从外部传入的,所以在Cg 程
序(顶点程序和片段程序)
//中通常使用uniform 参数修辞函数形参,不容许声明
一个用uniform
//修辞的局部变量!否则编译时会出现错误提示信息
- Varying inputs,即 数据流输入图元信息的各种组成要素 。从应用程序输入到GPU 的数据除了顶点位置数据,还有顶点的法向量数据,纹理坐标数据等。Cg 语言提供了一组语义词,用以表明参数是 由顶点的哪些数据初始化的 。
4、 const 被const 所修辞的变量在初始化之后不能再去改变它的值。 const 修辞符与uniform 修辞符是相互独立的,对一个变量既可以单独使用const 或者uniform,也可以同时使用。
5、 输入\输出修辞符(in\out\inout)
在C\C++中,根据形参值的改变是否会导致实参值的改变,参数传递分为“值传递(pass-by-value)”和“引用传递(pass-by-reference)”。按值传递时,函数不会访问当前调用的实参,函数体处理的是实参的拷贝,也就是形参,所以形参值的改变不会影响实参值;引用传递时,函数接收的是实参的存放地址,函数体中改变的是实参的值。C\C++采取指针机制构建引用传递,所以通常引用传递也称为“指针传递”。Cg 语言中参数传递方式同样分为“值传递”和“引用传递”,但指针机制并不被GPU 硬件所支持,所以 Cg 语言采用不同的语法修辞符来区别“值传递”和“引用传递”。
这些修辞符分别为:
- in : 修辞一个形参只是用于输入, 进入函数体时被初始化,且该形参值的改变不会影响实参值,这是典型的值传递方式。
- out : 修辞一个形参只是用于输出的, 进入函数体时并没有被初始化,这种类型的形参一般是一个函数的运行结果。
- inout : 修辞一个形参既用于输入也用于输出,这是典型的 引用传递 。
void myFunction(out float x); //形参x,只是用于输出
//也可以使用return 语句来代替out 修辞符的使用
void myFunction(inout float x); //形参x,即用于输入时初始化,也用于输出数据
void myFunction(in float x); //形参x,只是用于输入
void myFunction(float x); //等价与 in float x,这种用法和C\C++完全一致
八、 语义词(Semantic)与语义绑定
例子:
void mian_v(float4 position_obj : POSITION,
float3 normal_obj : NORMAL,
out float4 oPosition : POSITION,
out float4 oColor : COLOR,
uniform float4x4 modelViewProj)
………………
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;
float4 main_v(float4 position: POSITION,
out float4 oposition : POSITION,
uniform float4x4 modelViewProj):COLOR
oposition = mul(modelViewProj,position);
float4 ocolor = float4(1.0,0,0,0);
return ocolor;
}
语义词 , 表示输入图元的数据含义(是位置信息,还是法向量信息),也表明这些图元数据存放的硬件资源(寄存器或者纹理缓冲区)。 顶点着色程序和片段着色程序中 Varying inputs 类型的输入,必须和一个语义词相绑定,这称之为绑定语义(binding semantics) 。
1、语义
语义,是两个处理阶段(顶点程序、片段程序)之间的输入\输出数据和寄存器之间的桥梁,同时语义通常也表示数据的含义,如POSITION一般表示参数种存放的数据是顶点位置 。语义,分为输入语义和输入语义;输入语义和输出语义是有区别的。虽然一些参数经常会使用相同的绑定语义词,例如:顶点Shader 的输入参数,POSITION指应用程序传入的顶点位置,而输出参数使用POSITION 语义就表示要反馈给硬件光栅器的裁剪空间位置,光栅器把POSITION 当成一个位置信息。虽然两个语义都命名为POSITION,但却对应着图形流水线上不同的寄存器。
原理:语义概念的提出和图形流水线工作机制大有关系。从前面所讲的 GPU 处理流程中可以看出,一个阶段处理数据,然后传输给下一个阶段,那么每个阶段之间的接口是如何确定的呢?例如:顶点处理器的输入数据是处于模型空间的顶点数据(位置、法向量),输出的是投影坐标和光照颜色;片段处理器要将光照颜色做为输入,问题是“片段处理器怎么知道光照颜色值的存放位置”?在高级语言中(C/C++),数据从接口的一端流向另一端,是因为提供了数据存放的内存位置(通常是指针信息);由于Cg 语言并不支持指针机制,且图形硬件处理过程中,数据通常暂存在寄存器中,故而在Cg 语言中,通过引入语义绑定(binding semantics)机制,指定数据存放的位置,实际上就是将输入\输出数据和寄存器做一个映射关系(在OpenGL Cg profiles 中是这样的,但在DirectX-based Cg profiles 中则并没有这种映射关系)。 根据输入语义,图形处理器从某个寄存器取数据;然后再将处理好的数据,根据输出语义,放到指定的寄存器。
2、 顶点着色程序的输入语义
POSITION、NORMAL、TANGENT、TEXCOORD0---TEXCOORD7、BINORMAL、BLENDWEIGHT、BLENDINDICES、PSIZE;以上绑定语义关键字被Cg 语言的所有vertex profile 所支持,一些profile 支持额外的语义词。
语义词POSITION0 等价于POSITION,其他的语义词也有类似的等价关系。为了说明语义词的含义,举例如下:
in float4 modelPos: POSITION
//表示该参数中的数据是的顶点位置坐标(通常位于模型空间),属于
//输入参
数,语义词POSITION 是输入语义,如果在OpenGL 中
//则对应为接受应用程序传
递的顶点数据的寄存器(图形硬件上)
in float4 modelNormal: NORMAL
//表示该参数中的数据是顶点法向量坐标(通常位于模型空间),属于
//输入参
数,语义词NORMAL 是输入语义,如果在OpenGL 中
//则对应为接受应用程序传
递的顶点法向量的寄存器(图形硬件上)
//上面的参数都被声明为四元向量,通常我们在应用程序涉及的顶点位
//和法向量都是三元向量,至于为什么要将三元向量便为四元向量,又称齐次坐
//顶点位置坐标传入顶点着色程序中转化为四元向量,最后
一元数据为1,
//而顶点法向量传入顶点着色程序中转化为四元向量,最后一元数
据为0
3、 顶点着色程序的输出语义
顶点程序的输出数据被传入到片断程序中,所以 顶点着色程序的输出语义词,通常也是片段程序的输入语义词,不过语义词POSITION除外。
POSITION、COLOR0-COLOR1、TEXCOORD0-TEXCOORD7、PSIZE、FOG ;这些语义词适用于所有的Cg vertex profiles作为输出语义和Cg fragment profiles的输入语义。
顶点着色程序必须声明一个输出变量,并绑定POSITION语义词,该变量中的数据将被用于,且只被用于光栅化! vertex program中的绑定语义(POSITION除外)的输出形参中的数据会传递到fragment program中绑定相同语义的输入形参中。 为了保持顶点程序输出语义和片段程序输入语义的一致性,通常使用相同的struct类型数据作为两者之间的传递,这是一种非常方便的写法,推荐使用。顶点着色程序输出的对象很多,在有些光照或阴影计算中,往往要输出顶点的世界坐标、法向量、光的反射方向、折射方向、投影纹理坐标等数据,这些数据统一放到结构体中方便管理。如:
struct VertexIn
float4 position : POSITION;
float4 normal : NORMAL;
struct VertexScreen
float4 oPosition : POSITION;
//当使用struct结构中的成员变量绑定语义时,需要注意到
//顶点着色程
序中使用的POSITION语义词,是不会被片段程序所使用的
float4 objectPos : TEXCOORD0;
float4 objectNormal : TEXCOORD1;
};
如果需要从顶点着色程序向片段程序传递数据,例如顶点投影坐标、光照信息等,则可以声明另外的参数,绑定到TEXCOORD系列的语义词进行数据传递,实际上TEXCOORD系列的语义词通常都被用于从顶点程序向片段程序之间传递数据。
4、片段 着色程序的输出语义
片段着色程序的输出语义词较少,通常是COLOR。这是因为片段着色程序运行完毕后,就基本到了GPU流水线的末端了。 片段程序必须声明一个out向量(三元或四元),绑定语义词COLOR,这个值将被用作该片断的最终颜色值。如:
void main_f(out float4 color : COLOR)
color.xyz = float3(1.0,1.0,1.0);
color.w = 1.0;
}
九、函数与数组形参
void myFunc(inout float val )
………………
val += 10.0;
float myFunc(float vals[])
float sum = 0.0;
……………………
return sum;
//如果函数没有返回值,函数的返回类型一定要是void,否则编译会出现大量的错误
//Cg 语言支持函数重载(Functon Overlaoding),其方式和C++基本一致,
过形参列表的个数和类型来进行函数区分。
当一个数组作为函数的形参时,Cg 语言中不存在指针机制(图形硬件不支持), 数组作为函数形参,传递的是数组的完整拷贝。 数组类型变量作为函数形参,可以是一维的也可以是多维的,并且不必声明数组长度,即Unsized Array。数组形参不必指定长度。如果指定了函数中形参数组的长度,那么在调用该函数时实参数组的长度和形参数组的长度必须保持一致,如果没有保持一致,编译时会出现错误提示信息。对于函数的形参数组最好不要指定长度,这样就可以片配任意长度的参数数组。例如:
float myFunc(float vals[])
float sum = 0.0;
for(int i = 0; i< vals.length; i++)
sum += vals[i];
return sum;
}
十、入口函数
由于着色程序分为顶点程序和片断程序,两者对应着图形流水线上的不同阶段,所以这两个程序都各有一个入口函数。 顶点程序和片段程序的入口函数形式,已经完全由它们在渲染管线中所处的阶段所决定。在前面已经阐述过,顶点程序接收应用程序传递的顶点数据(通常位于模型坐标空间),然后进行坐标空间转换和光照处理,最后输出投影坐标和计算得到的光照颜色;而片段程序接收从顶点程序输出的数据,并进行像素颜色计算。在片段程序中往往涉及到纹理颜色的处理,其输入参数中常有纹理形参的声明。所以通过观察程序的输入输出语义绑定,就可以区分入口函数对应到顶点程序还是片段程序。如:
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;