每个OpenGL ES 3.0程序要求一个顶点着色器和一个片段着色器去渲染一个图形。着色器概念是API 的中心,本篇将介绍着色器语言部分包含下面几项
1、变量和变量类型
2、矢量和矩阵创建及选择
3、常量
4、结构和阵列
5、运算符、流控制和函数
6、属性、只读变量和变量
7、预处理和指令
8、只读变量和变量压缩
9、精度控制和不变性
一、变量和变量类型
计算机图形学中,转换有两种基本的数据类型:矢量和矩阵。下图是OpenGL ES 着色器编程语言数据类型
变量可以在声明时初始化,或以后初始化,初始化是通过构造函数进行,也可做类型转换。
float myFloat = 1.0; float myFloat2 = 1; // ERROR: invalid type conversion bool myBool = true; int myInt = 0; int myInt2 = 0.0; // ERROR: invalid type conversion myFloat = float(myBool); // Convert from bool -> float myFloat = float(myInt); // Convert from int -> float myBool = bool(myInt); // Convert from int -> bool
矢量同样可以转换
vec4 myVec4 = vec4(1.0); // myVec4 = {1.0, 1.0, 1.0, // 1.0} vec3 myVec3 = vec3(1.0,0.0,0.5); // myVec3 = {1.0, 0.0, 0.5} vec3 temp = vec3(myVec3); // temp = myVec3 vec2 myVec2 = vec2(myVec3); // myVec2 = {myVec3.x, // myVec3.y} myVec4 = vec4(myVec2, temp); // myVec4 = {myVec2.x, // myVec2.y, // temp.x, temp.y}
矩阵转换
mat3 myMat3 = mat3(1.0, 0.0, 0.0, // First column 0.0, 1.0, 0.0, // Second column 0.0, 1.0, 1.0); // Third column
二、矢量和矩阵元素
矩阵元素能够通过两种方式获取,使用“.”操作符或者数组下标。依据被给的元素的组成,每个被给的矩阵都能使用{x, y, z, w}, {r, g, b, a},或{s, t, r, q}表示。使用三种不同的命名表是因为有三种坐标顶点、颜色和贴图。x, r, 或s 表示矩阵里的第一个元素,不同的命名方式仅仅是为了使用方便。或者说你可以使用矩阵时混合使用矩阵命名方式,(但不能使用.xgr,只能一次使用一种命名规则)。当使用“.”时,你也可以重新排列一个矩阵。例如,
vec3 myVec3 = vec3(0.0, 1.0, 2.0); // myVec3 = {0.0, 1.0, 2.0} vec3 temp; temp = myVec3.xyz; // temp = {0.0, 1.0, 2.0} temp = myVec3.xxx; // temp = {0.0, 0.0, 0.0} temp = myVec3.zyx; // temp = {2.0, 1.0, 0.0}
矩阵也可以使用[]操作符,在这种下标模式[0]代表x, [1]代表y。矩阵被认为是多个矢量组成的,例如mat2 被考虑是两个vec2s,mat3 是3 个vec3s。对矩阵,单独的列被使用列下标[]选中。下面是例子:
mat4 myMat4 = mat4(1.0); // Initialize diagonal to 1.0 (identity) vec4 colO = myMat4[0]; // Get colO vector out of the matrix float ml_l = myMat4[1][1]; // Get element at [1][1] in matrix float m2_2 = myMat4[2].z; // Get element at [2][2] in matrix
三、常量
常量是在着色器中不可改变的数据类型。使用const修饰,必须在声明时初始化。
const float zero = 0.0; const float pi = 3.14159; const vec4 red = vec4(1.0, 0.0, 0.0, 1.0); const mat4 identity = mat4(1.0);
四、结构和数组
结构
像C 语言一样,可以集合几种变量成为结构。在OpenGL ES 结构如下:
struct fogStruct { vec4 color; float start; float end; } fogVar;
这将产生新的变量类型fogStruct,和新的变量名fogVar。使用能够初始化构造函数变量。定义一个结构类型,定义一个结构,一个同名的构造函数也被定义,必须是一对一的。上面的结构能够被使用下面的语法初始化:
fogVar = fogStruct(vec4(0.0, 1.0, 0.0, 0.0), // color 0.5, // start 2.0); // end
结构的构造基于结构的类型,它把每个成员做输入参数。访问结构中的元素和C 语言相同。
vec4 color = fogVar.color; float start = fogVar.start; float end = fogVar.end;
数组
OpenGL ES 数组语法和C 语言非常类似,索引以0 开始。下面是创建数组的例子:
float floatArray[4]; vec4 vecArray[2];
float a[4] = float[](1.0, 2.0, 3.0, 4.0);
float b[4] = float[4](1.0, 2.0, 3.0, 4.0);
vec2 c[2] = vec2[2](vec2(1.0), vec2(1.0));
五、操作符
这些运算符使用和C 语言非常类似。但OpenGL ES 语法有严格的语法限制,执行运算符的变量必须有相同的类型,二进制运算符(*, /, +, -)必须是浮点变量或者是整型变量。乘运算符能够在浮点、矢量、矩阵的组合中运行。例如:
float myFloat; vec4 myVec4; mat4 myMat4; myVec4 = myVec4 * myFloat; // Multiplies each component of myVec4 // by a scalar myFloat myVec4 = myVec4 * myVec4; // Multiplies each component of myVec4 // together (e.g., myVec4 ^ 2 ) myVec4 = myMat4 * myVec4; // Does a matrix * vector multiply of // myMat4 * myVec4 myMat4 = myMat4 * myMat4; // Does a matrix * matrix multiply of // myMat4 * myMat4 myMat4 = myMat4 * myFloat; // Multiplies each matrix component by // the scalar myFloat
比较运算符(==, !=, <, etc.)仅能够执行标量,矢量有专门的比较函数
六、函数
函数声明和C 语言一样,函数使用前,必须定义,它的原型必须给出。使用时非常类似C 语言。不同是参数使用上,提供特殊的变量限定词,指示变量是否能够被函数修改。那些限定词如下表:
使用如下:
vec4 myFunc(inout float myFloat, // inout parameter out vec4 myVec4, // out parameter mat4 myMat4); // in parameter (default)
散射光计算函数。
vec4 diffuse(vec3 normal, vec3 light, vec4 baseColor) { return baseColor * dot(normal, light); }
OpenGL ES 函数不能递归,原因是一些编译工具执行这个函数时,这会让这个函数在线执行,最后使GPU 产生问题。
着色器语言也提供了内置函数。下面的例子是,在片段着色器中计算基本反射光的着色器代码。
float nDotL = dot(normal , light); float rDotV = dot(viewDir, (2.0 * normal) * nDotL C light); float specular = specularColor * pow(rDotV, specularPower);
七、流控制声明
控制语句语法和C 类似,if-then-else 逻辑也被使用,例如:
if(color.a < 0.25) { color *= color.a; } else { color = vec4(0.0); }
条件表达式的结果必须是布尔值。或者说表达式基于布尔值计算,或者计算结果是布尔值(例如比较运算)。
八、Uniforms
Uniform
是变量类型的一种修饰符。。uniform 是OpenGL ES 中被输入着色器的只读值。。uniform被使用存储各种着色器需要的数据,例如:转换矩阵、光照参数或者颜色。基本上各种输入着色器的常量参数像顶点和片段(但在编译时并不知道)应该是uniform。uniform 应该使用修饰词被声明为全局变量,如下:
uniform mat4 viewProjMatrix;
uniform mat4 viewMatrix;
uniform vec3 lightPosition;
uniform 的空间被顶点着色器和片段着色器分享。也就是说顶点着色器和片段着色器被链接到一起进入项目,它们分享同样的uniform。因此一个在顶点着色器中声明的uniform,相当于在片段着色器中也声明过了。当应用程序装载uniform 时,它的值在顶点着色器和片段着色器都可用。
另一个需要注意的是,uniform 被存储在硬件被称为常量存储,这是一种分配在硬件上的存储常量值的空间。因为这种存储需要的空间是固定的,在程序中这种uniform 的数量是受限的。这个限制能通过读gl_MaxVertexUniformVectors 和gl_MaxFragmentUniformVectors编译变量得出。( 或者用GL_MAX_VERTEX_UNIFORM_VECTORS 或GL_MAX_FRAGMENT_UNIFORM_ VECTORS 为参数调用glGetIntegerv)OpenGL ES 3.0必须至少提供256 个顶点着色器uniform 和224个片段着色器uniform。但可以更多,
Uniform Blocks
uniform TransformBlock { mat4 matViewProj; mat3 matNormal; mat3 matTexGen; }; layout(location = 0) in vec4 a_position; void main() { gl_Position = matViewProj * a_position; }
九、属性
OpenGL ES 着色器语言的另一个变量是属性。属性变量仅仅在顶点着色器中被使用,逐顶点的指定顶点着色器的输入。典型的被用来储存位置、法线、贴图坐标和颜色数据。关键是懂得属性是每个顶点被绘制的详细数据。它实际上是着色器的使用者决定什么数据是属性。
uniform mat4 u_matViewProjection; attribute vec4 a_position; attribute vec2 a_texCoord0; varying vec2 v_texCoord; void main(void) { gl_Position = u_matViewProjection * a_position; v_texCoord = a_texCoord0; }
像uniform 一样,硬件对顶点着色器的属性变量数量有限制。最大的属性数通过工具编译支持gl_MaxVertexAttribs 确定。(或者使用GL_MAX_VERTEX_ATTRIBS 为参数调用glGetIntegerv 查询)最小数是8。如果为确保你的程序能在任何OpenGL ES 2.0 工具上编译,确保你的属性不超过8。
变量被用来存储顶点着色器的输出和片段着色器的输入。基本上每个顶点着色器把输出数据转变成一个或更多片段着色器的输入。那些变量也被片段着色器声明(类型需匹配),并且在光栅化阶段被线性插补变成图元。
varying vec2 texCoord;
varying vec4 color;
变量也有数量的限制(硬件上它们被用来做插补)。工具支持的最大数目是gl_MaxVaryingVectors ( 使用GL_MAX_VARYING_VECTORS 为参数调用glGetIntegerv 查询)。最大数目是8.
下面是顶点着色器和片段着色器的变量如何匹配的声明
// Vertex shader uniform mat4 u_matViewProjection; attribute vec4 a_position; attribute vec2 a_texCoord0; varying vec2 v_texCoord; // Varying in vertex shader void main(void) { gl_Position = u_matViewProjection * a_position; v_texCoord = a_texCoord0; }
// Fragment shader precision mediump float; varying vec2 v_texCoord; // Varying in fragment shader uniform sampler2D s_baseMap; uniform sampler2D s_lightMap; void main() { vec4 baseColor; vec4 lightColor; baseColor = texture2D(s_baseMap, v_texCoord); lightColor = texture2D(s_lightMap, v_texCoord); gl_FragColor = baseColor * (lightColor + 0.25); }
十、预处理指令
#define #undef #if #ifdef #ifndef #else #elif #endif
__LINE__ // 被着色器当前的行数取代
__FILE__ // 在 OpenGL ES 中总是0
__VERSION__ // OpenGL ES 着色器语言版本 (e.g., 100)
GL_ES // 被 ES 着色器定义为值1
其他
精度控制符,lowp,highp,mediump.对所有基于浮点的变量默认的精度是浮点值,基于整型的变量默认的精度是整型。在着色器中, 如果没有定义指定精度,默认对int 和float 的精度都是高。换句话说在顶点着色器中没有指定进度控制的变量将是高质量。而片段着色器规则却不同。对浮点值没有默认的精度控制。每个着色器必须声明默认的着色器浮点精度或指定每个浮点变量的精度。即OpenGL ES 2.0 不要求片段着色器支持高精度。决定是否高精度被支持是片段着色器是否定义了GL_FRAGMENT_PRECISION_HIGH 宏(和工具输出OES_fragment_precision_high 扩展字符串)。
#ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif
最后要讨论的题目是不变性。在OpenGL ES 着色器编程语言里,invariant 是被用于任何顶点着色器的变量输出的关键字。它意味着什么和为什么需要他呢。着色器被编译时可能进行优化,一些指令被重新整理。指令重新整理意味着两个着色器之间平等的计算不保证产生相同的结果。这是个问题在多通道着色器特殊情况下,依稀物体使用透明混合来绘制时。如果精度被使用来计算输出位置是不准确一样,精度的不同将导致artifacts。这在Z fighting情况下经常发生。每个像素很小的Z 精度不同引起了不同的反光。下面的例子显示了进行多通道渲染时invariance 对得到正确的结果是非常重要的。下面的圆环被绘制用两种方式。片段着色器先计算镜面反射光,再计算环境光和散射光。顶点着色器不使用invariance,因此小的精度不同引起了Z 光在图
invariant gl_Position;
invariant varying texCoord;
一旦输出被宣布为invariance,同样的计算输入相同,编译器保证输出结果相同。例如你有两个顶点着色器使用多通道图像投射矩阵依据输入计算输出。你能保证那些位置是invariance。
uniform mat4 u_viewProjMatrix; attribute vec4 a_vertex; invariant gl_Position; void main { // gl_Position = u_viewProjMatrix * a_vertex; // Will be the same // value in all // shaders with the // same viewProjMatrix // and vertex }