CG语言基础
一、Cg语言的特性:
Cg是一种类C的语言,他沿用了大量的C语言特性,但是也有不同之处。Cg语言是脚本性语言,不能编译,调试和断点;并且Cg语言的主要用途是绘制像素颜色到屏幕上,他不能处理文字类的内容。Cg语言对单精度浮点的数据类型是非常敏感的;Cg语言因为GPU的并发特性所以导致Cg语言的所有执行过程是并发式的;
二、Cg语言的语法特性:
1.基本数据类型:
float:32位浮点数据
float _Specular = 1.0
half:16位浮点数据
half _Specular = 1.0
Int:32位整形数据
int _Specular = 1
fixed:12位浮点数据
fixed _Specular = 1.0
bool:布尔数据,用于判断if和条件操作符
if(logocolor.a == 0)
{
colors = finalColor.rgb;
} else {
colors = diffuses.rgb;
}
Sampler,纹理对象的句柄,6类:
Sampler,sampler1D,sampler2D,sampler3D,samplerCUBE和samplerRECT
sampler2D _Diffuse;
2、特殊的数据类型:
向量:一共拥有4种纬度的向量,1 - 4之间,不得低于1不得高于4;
例如:float3 a; half4 h; fixed2 m; int1 d;
向量初始化或赋值:float4 a = float4(1,2,3,4); float3 b; b = float3(0,2,3);
注意:对于向量的赋值和初始化,我们必须保证值向量的纬度一定要和变量向量的维度相等,在添值的过程中要保证所添值的数量不得多也不得少;
矩阵:矩阵的行和列都最大不得大于4维,最小不得小于1位;
例如:
float2x4 a;// 表示这是一个2行4列的矩阵; half3x3 m;// 表示是一个3 X 3的方阵;
矩阵赋值和初始化 float2x2 rtX = {sin@, 0, 0, cos@};// 注意,矩阵在赋值的时候一定要人为的分开行与行之间的关系,但是程序只认识顺序的赋值;
注意:CG中向量,矩阵和数组是完全不同的,向量和矩阵是内置的数据类型,而数组则是一种数据结构,不是内置数据类型。
3、数组:
因为Cg是显卡操作语言,他没有相应的内存,所以对于Cg语言来讲就不可能存在空间申请,那么Cg的数组就必须在定义的时候指定大小,除非数组出现在函数的行参当中,我们可以不指定数组大小;
一维数组: 类型(数据类型,向量,矩阵类型) 数组名[数组的个数];
二维数组: 类型(数据类型,向量,矩阵类型) 数组名[数组的行数][数组的列数];
数组的赋值:float a[2] = {1, 2}; float a[2][2] = PW_1,2}, {3, 4;
一维数组的赋值一定要和矩阵分开,他们不是同一种关系;
数组可以获取长度;
float2 b[2];
int arrayLen = b.length; // 结果2
float m[3][4];
arrayLen = m.length;// 结果是3;
arrayLen = m[1].length;// 结果是4;
4、结构体:
结构体是一种自定义数据类型,他可以把所有的数据类型进行集合式的包装,起到了数据封装的作用;
结构体中可以定义任意类型的变量,变量在定义的时候可以同时初始化。与高级语言不通,Cg没有修饰词,所以struct中的所有变量和函数都是公共的;
struct appdata_full
{ float normal:NORMAL; };
5.CG表达式与控制语句
关系操作符:<,>,<=,>=,!=,==,
if(logocolor.a == 0)
逻辑操作符:&&,||,!
数学操作符:+,—,*,/,%
移位操作符:>>
Swizzle操作符:float a=1.0,float4 b = a.xxxx;这种用法我们有时候会用到
控制流语句:if ,if-else while ,for,break
6、点操作符:
点操作符只能针对Cg的:向量和结构体;
向量点操作:
对于向量他可以按照坐标顺序和色彩顺序访问向量中的每个元素: xyzw, rgba;
注意:他和向量的维度有关,绝对不能超过向量的维度进行访问;
float2 a = float2(1,2); float3 b = a.xyx; float4 m = a.yyyy; float3 m1 = a.xyz;// 这是错误的!因为超出了维度的访问范围;
结构的点操作:
v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
和普通编译语言是一样的;
7、行参修饰词:
in: 表示当前行参是只能输入的;
out:表示当前行参用来输出数据的;
inout: 表示可以进行输入和输出;
8、离散数据图元信息:
这些离散的图元信息是引擎向着色器下达的数据通知内容,比如纹理贴图这样的大数据;
离散图元变量的关键字 : uniform;
特性:
(1)只读性,只要标有uniform标签的变量,这个变量我们就不能初始化也不能修改其内容;
(2)使用范围特性,只能存在于入口函数的行参上或者是全局的变量身上;
(3)他只能支持引擎向着色器传入的数据,不能向外提交数据;
9、语义:
为了表达当前变量是指定的数据范围,我们提出语义的概念。语义可以作为输入也可以作为输出。语义的定义只能存在于入口函数的行参和返回值上。
常用的语义:
POSITION : 表示当前这个变量表示的是坐标,我们需要使用float4向量来作为变量的类型;
COLOR: 表示当前这个变量是颜色表示,我们需要使用float4/ half4 / fixed4 来表示颜色信息;
NORMAL: 表示当前这个变量是法线坐标,因为法线描述的是一个点的特性,所以他没有坐标系关系,那么我们就可以使用float3向量;
TEXCOORD0 – TEXCOORD7: 这样的语义可以表示任意的数据类型,但是我们经常使用 TEXCOORD0来表示贴图UV坐标用float4向量;
TANGENT:表示顶点角的正切值,用float3传递;
语义的输入性和输出性,需要和行参中的修饰词结合表示,in 表示当前语义是输入,out 表示当前语义是输出型的,inout表示支持输入和输出;
语义的写法:
(1) 作为行参来实现: [in|out|inout] <数据类型> <行参名> [: <语义词>]
(2) 作为返回值来实现: <返回值类型不能是void> <入口函数名>([行参列表]) [: <语义词>] {}
(3) 作为结构体来实现:
float3 worldLightDir:TEXCOORD1;
(4) 作为全局非静态实现:<数据类型> <行参名> [: <语义词>];
10、Cg语言的入口函数:
因为Cg语言是一种特殊的显卡语言,他需要同时处理显卡的两个工作部分,一个是点的位置描述,另外一个是点的颜色值描述。所以Cg语言有两个入口函数,他们分别是:顶点程序、片段/片装源/像素程序;
其它的函数我们称为内部函数。
注意:Cg的入口函数没有特定的函数面,我们都是后期自己制定出来的,所以如果我们想让某个函数成为入口函数就一定要通知引擎这个函数的名字;
入口函数的特性:
(1)顶点入口函数:顶点入口函数是唯一可以和引擎沟通的函数,所以语义数据的传入都需要在顶点入口函数中完成。顶点入口函数必须履行向光栅硬件传递变换后的坐标,所以顶点入口函数必须有一个返回型的语义,而且必须是POSITION类型的语义。同时他可以向我们片段入口函数传递响应的数据。
(2)片段入口函数:片段程序的主要责任是给当前这个像素点设定颜色的,所以我们需要在片段程序中返回一个颜色语义,类型必须是COLOR,而且只能向外输出COLOR,因为他是最后一道程序,所以其他的语义抛出是无任何意义的。入口函数中传回来 POSITION, NORMAL, TANGENT等语义,片段程序不能读也不能写,对于COLOR0 – COLOR1 和 TEXCOORD0 – TEXCOORD7这样的语义片段程序可以读写;
三、Unity3D引擎中ShaderLab语言与Cg的结合
因为Cg属于可编程着色器程序,所以在SubShader必须用Pass过程块;
在着色器中,我们需要两个特定的命令夹住Cg语言,这样ShaderLab才认识你的Cg代码:
CGPROGRAM
。
。
。
ENDCG
因为Cg语言的入口函数没有特定,所以我们需要通知ShaderLab哪两个入口函数是我们的顶点和片段的入口函数。我们需要借助以下命令:
// 顶点入口函数的设定
pragma vertex 入口函数名
// 片段入口函数的设定
pragma fragment 入口函数名
注意:可编程着色器只要写了入口函数就一定要按照同样的名字来实现入口函数;
Cg语言也支持对于第三方的代码调用,如果需要调用第三方的代码,我们需要使用: #include “需要包含的代码文件.cginc” 这样的关键字;
对于Unity引擎,他为我们提供了两个cginc文件,方便了我们与引擎的沟通;
UnityCG.cginc :当前文件提供了Unity引擎中大部分的位置坐标和方向的内容;
Lighting.cginc:当前文件提供了Unity引擎中灯光的操作;
当我们Cg需要使用我们ShaderLab语言的属性时,我们必须借助 uniform这个关键字。需要注意,uniform变量的名字必须和我们属性的名字一模一样;
对于Cg语言来说,如果我们直接定义uniform变量,而不在我们的ShaderLab属性列表中定义,那么我们的Unity引擎同样也可以向这个变量传递数据。但是这个变量在我们的Inspector面中无法显示出来;
1.从应用程序传递到GPU的数据,分为图元信息数据(在GPU处理的基本数据如顶点位置信息等)和其他的离散数据(在GPU运行流程中不会发生变化,如材质对光的反射、折射信息),这两种输入数据如何区分?
Cg 语言提供了一组语义词,用以表明参数是由顶点的哪些数据初始化的,Uniform 修辞一个参数,表示该参数的值由外部应用程序初始化并传入
- 从应用程序传递到GPU中的图元信息如何区分类型,即,顶点程序怎么知道一个数据是位置数据,而不是法向量数据?
语义词,表示输入图元的数据含义(是位置信息,还是法向量信息),也表明这些图元数据存放的硬件资源(寄存器或者纹理缓冲区)。顶点着色程序和片段着色程序中 Varying inputs 类型的输入,必须和一个语义词相绑定
3.顶点着色程序与片段着色程序之间的数据传递如何进行?
根据输入语义,图形处理器从某个寄存器取数据;然后再将处理好的数据,根据输出语义,放到指定的寄存器,语义,是两个处理阶段(顶点程序、片段程序)之间的输入\ 输出数据和寄存器之间的桥梁,同时语义通常也表示数据的含义,POSITION 一般表示参数种存放的数据是顶点位置