原帖地址:http://blog.csdn.net/n5/article/details/7242306
1 介绍OpenGLES
gles由KhronosGroup创立,目前有3个版本1.0,1.1(统称1.x)和2.0。ES1.0,1.1由OpenGL1.3,1.5继承而来,ES2.0由OpenGL2.0继承而来。
OpenGLES 2.0的specifications有两份:theOpenGL ES 2.0 API specification和theOpenGL ES Shading Language Specification(OpenGL ES SL)。
下图是ES2.0的图形流水线,灰色的方框是可编程阶段。
VertexShader
vs实现了一种作用于顶点的通用可编程方法。Vs的输入由以下几种组成:
1)Attributes– 由顶点数组提供的Per-vertexdata。
2)Uniforms– vs使用的常量数据
3)Samplers– 一种特殊的uniform,用于表示vs使用的纹理。Samplers对于vs是可选的。
4)Shaderprogram – vs程序,源代码或者可执行的,描述了对于顶点进行的操作。
Vs的输出叫做varying变量。在primitiverasterization阶段,varying的值对于每个生成的fragment进行计算,并将结果作为输入传递给fragmentshader。从赋予图元每个顶点的varying值为每个fragment生成一个varying值的机制叫做插值(interpolation)。
下图是vs的输入输出图:
Vs可用来进行传统的基于顶点的操作,例如使用矩阵变换位置、进行光照计算产生一个per-vertex的颜色、生成或变换纹理坐标。然而,因为VS是一个程序,VS可以用来进行任意的自定义顶点变换。
一个简单的VS:
uniformmat4 u_mvpMatrix;
attributevec4 a_position;
attributevec4 a_color;
varyingvec4 v_color;
voidmain()
{
v_color= a_color;
gl_Position= u_mvpMatrix*a_position;
}
其中gl_Position是一个内置的varying,不用声明,vs必须向它写入。Main函数是shader唯一的入口点。
PrimitiveAssembly 图元装配阶段
vs之后的流水线阶段就是图元装配,所谓图元是指可以用OpenGLEs绘制命令绘制的基本几何体。图元由顶点组成,顶点有各种属性,如位置和颜色。vs使用这些属性进行计算。在图元集成阶段,经过shade的顶点被集成进一个个独立的几何图元中,例如三角形,线或点精灵。对于每个图元,必须对其进行viewfrustum裁剪。如果图元部分在viewfrustum中,会被clip,如果图元完全在viewfrustum外则被丢弃。clipping之后,顶点的位置被转换到屏幕坐标系。另外,背面剔除也会在这阶段进行。这之后,图元就准备被传送到下一个阶段-光栅化。
Rasterization光栅化阶段
光栅化将图元转化成二维片段(fragments)的集合,这些片段将被fragmentshader处理。Fragment表示了可以被绘制到屏幕上的像素。(注:图元的属性是之前VS输出的varying,在光栅化阶段,基于图元的varying值被插值计算为基于片段的varying值)
FragmentShader
Fs实现了一种作用于片段的通用可编程方法。FS对光栅化阶段产生的每个片段进行操作。
Fs的输入:
1)Varyingvariables :Vs输出的varying经过光栅化插值后对每个片段产生的值。
2)Uniforms:FS使用的常量数据
3)Samplers:一种特殊类型的uniform,表示FS使用的纹理
4)Shaderprogram:FS程序,代码或可执行的,描述了对于片段的操作。
Fs可以丢弃片段或者为片段生成一个颜色,输出保存到gl_FragColor中。
光栅化阶段生成的color,depth,stencil和窗口坐标(Xw,Yw)将会成为流水线per-fragment操作阶段的输入(FS之后的阶段)。
一个简单的FS程序:
precisionmediump float;
varyingvec4 v_color; // input vertex color from vertex shader,VS必须写这个v_color
voidmain(void)
{
gl_FragColor= v_color; //gl_FragColor是唯一的输出
}
Per-FragmentOperations阶段
Fs之后就是逐片段操作阶段。光栅化产生的一个片段,具有窗口坐标(Xw,Yw),只能修改framebuffer中位置在(Xw,Yw)的像素。
下图是Per-FragmentOperations阶段的操作过程:
其中PixelOwnershipTest是用来决定framebuffer中某个位置的像素是否属于OpenGLES的context,比如如果OpenGLES的显示窗口被其他窗口遮住了,则一些像素就通不过这个测试,也就不会被OpenGLES显示。(似乎程序不需要控制这个)
Scissortest:测试(Xw,Yw)的片段是否在scissor矩形内,如果不在,片段被丢弃。
Stenciland depth test:测试incomingfragment的stencil和depth值,决定片段是否被丢弃。
Blending:将新产生的片段的颜色和framebuffer中相应位置像素的颜色进行混合。
Dithering:抖动
在per-fragment阶段之后,(Xw,Yw)处的片段要么被拒绝要么会产生一个fragmentcolor,depth,stencil值写入到framebuffer的(Xw,Yw)位置。Fragmentcolor, depth, stencil值是否真的写入framebuffer还取决于相应的writemasks是否enable。
另外,OpenGLES2.0也提供了从framebuffer回读像素的接口,但只有colorbuffer可以回读,depth和stencil值是读不到的。
alphatest不再在pre-fragmentstage中支持,需要在fragmentshader中实现
logicOp被去掉了,因为很少使用。(alphatest和logicop在OpenGL2.0和OpenGLES1.x中是存在的)
ES2.0和ES1.1的兼容性
ES2.0不向后兼容ES1.1,2.0不支持1.1中的固定功能流水线。2.0用vertexshader代替了1.1中的固定功能顶点处理单元,即:顶点T&L,纹理坐标生成和变换,顶点颜色计算。fragmentshader代替了1.1的固定功能texturecombine单元,即为每个textureunit实现一个texturecombine stage。
(不同于OpenGL2.0,OpenGL2.0是完全向后兼容的,同时支持可编程流水线和固定流水线。)
EGL简介
OpenGLEs命令需要一个renderingcontext和一个drawingsurface。Renderingcontext存储OpenGLEs状态。Drawingsurface是图元绘制上去的surface,drawingsurface指定了渲染所需的buffer的类型,例如colorbuffer, depth buffer和stencilbuffer,以及每种buffer的位数。
OpenGLES API不提供如何创建renderingcontext并且attach到natviewindow system。EGL是一个在OpenGLEs和native windowsystem之间的接口。然而厂商在实现OpenGLEs时并不一定提供EGL,不同平台可能有各自的接口。
2 Shaders and Programs
-----------创建编译shader---------------------------
Gluintshader = glCreateShader(type);
glShaderSource(shader, 1,&shaderSrc, NULL);
glCompileShader(shader);
-----------program------------------------------------
创建:
Gluintprogram = glCreateProgram();
Attach:
glAttachShader(program,shader);
1)一个programobject必须且仅能attach一个vs和一个 fs
attach可以在任意时刻进行,哪怕shader没有compile,甚至没有source。
可用glDetachShader从program上移除shader。
如果一个shader已经被attach到一个programobject上,调用glDeleteShader不会立即删除这个shader,这个shader会被标记为待删除,一旦这个shader不再attach到任何programobject上,这个shader的内存将被释放。
Link:
当两个shader被attach到program上,并且shader已经成功compile,使用glLinkProgram连接program。
1)linker会检查,确保fs使用的所有varyingvariables会被vs写入,并且被声明成同样的类型
2)linker会检查,确保vs和fs中声明的uniforms具有匹配的类型
3)linker会确保最终的program适合实现的限制(即,attribute,uniform, varying的数量,使用的指令)
link阶段会产生最终的硬件指令(link是在GPU上进行的)
有些问题如纹理没有被绑定到samper,在Link时是不能查到的,可使用glValidateProgram(program);验证program是否可以在当前状态下执行。这个操作很慢,一般只在debug版本中使用。
使用:
glUseProgram(program);
-----------uniformsand attributes------------------------------------
uniform: 应用程序通过通过es2.0API传送给shader的只读变量;uniform在programobject中是共享的,即一个programobject只有一组uniforms。如果uniform同时在vs和fs中声明,必须具有相同的类型,并且在这两个shader中该uniform的值是一样的。在link时,linker会为program中的每个activeuniform分配一个uniformlocation。应用程序使用这个uniformlocation作为标识来载入uniform的值。(location需要手动查询得到)
所谓“active”的uniform是值在program中实际使用的uniform,如果仅仅是声明不算。
载入uniform值,首先使用
glGetActiveUniform(program,index, bufSize, &length, &size, &type, uniform_name);
获得uniform的名字,以及数据类型type,数组元素数size(如果不是数组size就是1)
然后使用
location= glGetUniformLocation(program, uniform_name);
获得uniform的location。这样就可以使用一系列的glUniform*函数载入uniform的值。
使用glUniform*的时候,不用programhandle作为参数,因为这是针对当前use的program的。但是一旦为一个program中的uniform设置了值后,这个值将会保存在这个program中。
Gettingand Setting Attributes:
@seeChapter6 “Vertex Attributes, Vertex Arrays and Buffer Objects”
3 OpenGL ES Shading language (GLESSL)
数据类型
float,int, bool
float,vec2, vec3, vec4
int,ivec2, ivec3, ivec4
bool,bvec2, bvec3, bvec4
mat2,mat3, mat4
变量声明
floatspecularAtten;
vec4vPosition;
mat4mViewProjection;
ivec2vOffset;
变量可以在声明时初始化也可以以后初始化。初始化通过使用构造器完成,构造器也被用作类型转换。
变量构造器
GLESSL的类型转换是很严格的,变量只能被赋值给相同类型的变量或者和相同类型的变量一起计算。为了进行类型转换,语言中有很多构造器。
FloatmyFloat = 1.0;
boolmyBool = true;
intmyInt = 0;
myFloat= float(myBool); //convert from bool-> float
myFloat= float(myInt);
myBool= bool(myInt);
向量的构造:
单标量参数-向量所有分量值设置为该标量
多标量或向量参数构造– 向量分量从左到右赋值,参数必须够用
vec4myVec4 = vec4(1.0); // myVec4={1.0,1.0,1.0,1.0}
vec3myVec3 = vec3(1.0, 0.0, 0.5);
vec3temp = vec3(myVec3);
vec2myVec2 = vec2(myVec3); //myVec2 = {myVec3.x, myVec3.y}
myVec4= vec4(myVec2, temp, 0.0); //myVec4={myVec2.x, myVec2.y, temp.x, 0.0}
矩阵的构造:
如果只提供一个标量参数,这个值被设置在矩阵对角线上,例如mat4(1.0)会构造一个4X4单位阵。
矩阵可用多个向量构造;可用多个标量构造;或者向量和标量混搭构造。
访问向量和矩阵的成员
向量可用点访问(”.”) 或数组下标访问。可以用{x,y,z,w},{r,g,b,a},{s,t,r,q}这几种分量名字,但不可混用。
向量可通过点访问重新排序,如:
vec3myVec3 = vec3(0.0, 1.0, 2.0);
vec3temp;
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开始,[0]=x,[1]=y,[2]=z;下标如果不是常量,gles2.0可能不支持。
矩阵被看出是由很多向量组成。例如mat3是由3个vec3组成。矩阵的列用数组下标选择。
然后每个列向量使用向量访问规则。例如:
mat4myMat4 = mat4(1.0); //单位阵
vec4col0 = myMat4[0];
floatm1_1 = myMat4[1][1];
floatm2_2 = myMat4[2].z;
常量
可定任意基本类型的常量。常量变量在shader中值不改变。常量必须在声明时初始化。
const float zero = 0.0;
constfloat pi = 3.14159;
constvec4 red = vec4(1.0, 0.0, 0.0, 1.0);
constmat4 identity = mat4(1.0);
结构体
structfogStruct
{
vec4color;
floatstart;
floatend;
}fogVar;
fogVar= fogStruct(vec4(0.0,1.0,0.0,0.0), 0.5, 2.0);
数组
floatfloatArrary[4];
vec4vecArray[2];
关于数组的重要事项:
1)很多OpenGLEs实现不允许使用无法在编译时确定值的变量去索引数组。
2)不能在创建时初始化数组-没有这样的语法。因此,数组不能是常量。数组元素需要逐个初始化。
操作符
操作必须使用于具有相同基本类型的变量之间
*, / , + , - (基本类型必须是float或者int,乘法可在floats,vectors,matrices之间组合)
++, --
=
+=,-=, *=, /=
==,!=, <, >, <=, >= (只能作用于标量,要比较向量使用内置函数)
&&
^^(逻辑异或)
||
函数
和c类似,最大的区别在于参数的传入方式。OpenGLES提供了特定的修饰符来定义参数是否可以被函数修改:
in (默认,如果不指定)passedby value,不会被函数修改
inout passed by reference, 如果被修改,函数返回后改变值。
out 变量的值不传入函数,函数返回时变量修改。
例子:
vec4myFunc(inout float myFloat, out vec4 myVec4, mat4 myMat4);
例子,计算diffuse光照的函数:
vec4diffuse(vec3 normal, vec3 light, vec4 baseColor)
{
returnbaseColor * dot(normal, light);
}
另外,函数不可以递归,因为一些实现会以inline的方式实现函数调用。
内置函数
使用例子,计算高光:
floatnDotL = dot(normal, light);
floatrDotV = dot(viewDir, (2.0*normal)*nDotL - light);
floatspecular = specularColor * pow(rDotV, specularPower);
控制流
和c类似,但有很多限制。
1)判断表达式必须是bool类型(bool变量或者比较表达式),因为glessl不支持隐式转换。
2)使用循环有各种限制,归结为:OpenGLES必须在编译时知道迭代数。
必须只有一个循环迭代变量,并且只能使用简单的表达式增加或减少(i++,i--,i+=constant,i-=constant);
循环的结束条件判断必须是循环索引和一个常量表达式之间的比较;
不能在循环中改变迭代变量的值;
基本上,gles2不需要实现真正支持loop,它这样限制loop是为了让编译器可以将loop展开。
Uniforms
uniform变量保存应用程序通过OpenGLES2.0API传给shader的只读值。uniform可以保存shader使用的各种数据,如变换矩阵,光的参数或者颜色。基本上,shader需要使用的任意参数如果对于所有的顶点和片段都是常量(但在编译时不知道)应该传入给uniform。
Uniform变量在全局范围内定义:
uniformmat4 viewProjMatrix;
uniformmat4 viewMatrix;
uniformvec3 lightPosition;
uniform存储于硬件上的“constantstore”, 可使用的数量有限制。OpenGLES2.0至少支持128个vertexuniform vectors和16个fragmentuniform vectors。
Attributes
只在vs中使用,用于指定per-vertex的输入。通常保存位置,法线,纹理坐标,颜色等数据。
attributevec4 a_position;
attributevec4 a_texCoord0;
attribute数量的限制:至少是8。
Varyings
vs的输出,fs的输入(在光栅化时进行了图元内线性插值)。在vs,fs中的声明必须一致。
varyingvec2 texCoord;
varyingvec4 color;
varying在硬件上就是interpolator(插值器),数量至少为8.
Preprocessorand Directives 预处理器和指令
宏定义和测试指令:
#define
#undef
#if
#ifdef
#ifndef
#else
#elif
#endif
宏不能用参数定义;#if,#else,#elif可以使用defined测试某个宏是否定义。
预定义的宏:
__LINE__
__FILE__ //ES2.0中总是0
__VERSION__//OpenGL ES Sl的版本(e.g,100)
GL_ES //1
#error指令会在编译shader时引发一个编译错误并将message写入infolog。
#pragma指定用于编译器实现相关的指令。
#version指令用于表示glesslshader语言的版本,必须写在最上面,目前版本是#version 100
#extension用于厂商对语言的扩展。
Uniformand Varying Packing
存储布局为4Xn网格,packing自动进行。
PrecisionQualifiers
用于指定任意基于float或者int类型的变量。
highpvec4 position;
varyinglowp vec4 color;
mediumpfloat specularExp;
如果变量没有指定精度,则使用默认精度。默认精度在shader代码头部指定:
precisionhighp float; //用于所有基于浮点数值的变量
precisionmediump int; //用于所有基于整型值的变量
在vertexshader中,如果没有指定默认精度,则int和float的默认精度都是highp。对于fragmentshader,float类型的默认精度没有默认值,必须显示的声明。并且在fs中,不一定支持highp,可查询得知是否支持(GL_FRAGMENT_PRECISION_HIGH定义,或者查询OES_fragment_precision_high扩展)。
#ifdefGL_FRAGMENT_PRECISION_HIGH
precisionhighp float;
#else
precisionmediump float;
#endif
Invariance
invariant关键字可以作用于vs输出的任意varying变量上。
shader在编译时,编译器可能进行优化,导致指令被重排。这意味着两个shader间相同的计算,不一定产生精确相等的结果。这对于multipass渲染来说是个问题,一个物体被渲染多次,如果计算出来的位置有差别,就会有瑕疵。比如产生z-fighting。
使用invariant可以在写shader时指定如果使用了相同的计算,输出的结果必须精确一致。
invariant关键字可以使用在varying声明上或者已经声明的varying上。
invariantgl_Position; //内置的已经声明的varying,使用invariant
invariantvarying texCoord; //声明时使用invariant
可以使用#pragma指令器然所有的变量invariant:
#pragmaSTDGL invariant(all)
注意,为了实现invariant,编译器限制了优化,所有仅当需要时才使用invariant。
4 Vertex Attributes, Vertex Arrays, VBO
什么是attributes?
如何指定attributes数据和他们支持的数据类型?
如何将attribute的索引和vertexshader中相应的顶点attribute名字绑定?
什么是attributes?
顶点数据或称为顶点属性,是给每个顶点指定的数据,可以每个顶点逐一指定也可所有顶点使用一个常量值。
在opengles 1.1中,顶点属性有预定义的名字,例如position,normal,color,texturecoordinates。因为固定流水线只需要这些顶点属性,所以预定义这些属性是合理的。在可编程流水线中,开发者需要在vertexshader中使用他们自己的顶点属性名字,因此对于gles2.0,用户自定义顶点属性名是必须的,既然如此,预定义名字也就不需要了。
如何指定顶点属性数据?
逐个顶点指定属性数据使用vertexarray;图元的所有顶点使用相同数据则使用常量值。
查询支持的最大顶点属性数,用glGetIntegerv(GL_MAX_VERTEX_ATTRIBS,&maxVertexAttribs);
这个值至少为8。
图元中所有顶点如果使用相同属性值就只用常量顶点属性
常量顶点属性只能使用GLfloat,通过glVertexAttrib1/2/3/4f[v]指定。例如:
void glVertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z);
这个函数将(x,y,z,1.0)设定到索引为index的顶点属性上。
顶点数组
vertex arrays为每个顶点指定属性数据,他们是存储在应用程序地址空间的buffer(即client space)。
vertex array提供了一种高效而灵活的方式来指定顶点属性数据。要指定vertex array,需要使用
glVertexAttribPointer函数:
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr);
index: 指定通用顶点属性的索引,取值为0到支持的最大顶点属性数-1
size: 顶点数组中,index所引用的顶点属性的组件数。有效值为1-4
type: 数据格式。有效值为:GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_FLOAT, GL_FIXED, GL_HALF_FLOAT_OES
normalized: 用于指出是否非浮点数据格式在被转换为浮点数时是否需要归一化。
stride: size参数所指定的顶点属性组件们是为逐顶点顺序存储在顶点数组中的。stride指出了顶点i和顶点i+1数据之间的差值。如果stride是0,所有顶点的属性数据是序列存储的。如果stride>0,stride作为一个pitch值来获取下一个顶点的数据。
顶点数组存储方式:
1)array of structures: 所有顶点属性存储在同一个单一的buffer中
2)structure of arrays: 每种顶点属性存储在一个独立的buffer中
在vertex shader中定义attribute:
attribute vec4 a_position;
attribute vec2 a_texcoord;
attribute vec3 a_normal;
attribute修饰符只能出现在vertex shader中,在fragment shader中会导致编译错误;attribute只能是float, vec2, vec3, vec4, mat2, mat3, mat4类型的。attribute变量不能是数组或者结构。
ES2.0实现支持GL_MAX_VERTEX_ATTRIBS个vec4类型的vertex attributes。float, vec2, vec3被计算做一个vec4。mat2,mat3,mat4分别计算为2,3,4个vec4。不像uniform和varying变量可以被自动pack,attribute不会被pack。
attribute变量在shader中是只读的。
在vs中定义的attribute如果没有使用是不会被认为激活的,也不会计算为使用的容量。
如果attribute的总量超过了GL_MAX_VERTEX_ATTRIBS,vertex shader会link失败。
一旦program成功被link,我们可以查出这个program的vs使用的active的vertex attribute数量:
glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &numActiveAttribs);