`
java-mans
  • 浏览: 11387702 次
文章分类
社区版块
存档分类
最新评论

《OpenGLES 2.0 Programming Guide》学习笔记(持续更新)

 
阅读更多

《OpenGLES 2.0 Programming Guide》 学习笔记


1 介绍OpenGLES

glesKhronosGroup创立,目前有3个版本1.0,1.1(统称1.x)2.0ES1.0,1.1OpenGL1.3,1.5继承而来,ES2.0OpenGL2.0继承而来。

OpenGLES 2.0specifications有两份:theOpenGL ES 2.0 API specificationtheOpenGL ES Shading Language Specification(OpenGL ES SL)

下图是ES2.0的图形流水线,灰色的方框是可编程阶段。

VertexShader

vs实现了一种作用于顶点的通用可编程方法。Vs的输入由以下几种组成:

1Attributes– 由顶点数组提供的Per-vertexdata

2Uniforms– vs使用的常量数据

3Samplers– 一种特殊的uniform,用于表示vs使用的纹理。Samplers对于vs是可选的。

4Shaderprogram – 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的输入:

1Varyingvariables Vs输出的varying经过光栅化插值后对每个片段产生的值。

2UniformsFS使用的常量数据

3Samplers:一种特殊类型的uniform,表示FS使用的纹理

4ShaderprogramFS程序,代码或可执行的,描述了对于片段的操作。

Fs可以丢弃片段或者为片段生成一个颜色,输出保存到gl_FragColor中。

光栅化阶段生成的color,depth,stencil和窗口坐标(Xw,Yw)将会成为流水线per-fragment操作阶段的输入(FS之后的阶段)。

一个简单的FS程序:

precisionmediump float;

varyingvec4 v_color; // input vertex color from vertex shaderVS必须写这个v_color

voidmain(void)

{

gl_FragColor= v_color; //gl_FragColor是唯一的输出

}

Per-FragmentOperations阶段

Fs之后就是逐片段操作阶段。光栅化产生的一个片段,具有窗口坐标(Xw,Yw),只能修改framebuffer中位置在(Xw,Yw)的像素。

下图是Per-FragmentOperations阶段的操作过程:

其中PixelOwnershipTest是用来决定framebuffer中某个位置的像素是否属于OpenGLEScontext,比如如果OpenGLES的显示窗口被其他窗口遮住了,则一些像素就通不过这个测试,也就不会被OpenGLES显示。(似乎程序不需要控制这个)

Scissortest:测试(Xw,Yw)的片段是否在scissor矩形内,如果不在,片段被丢弃。

Stenciland depth test:测试incomingfragmentstencildepth值,决定片段是否被丢弃。

Blending:将新产生的片段的颜色和framebuffer中相应位置像素的颜色进行混合。

Dithering:抖动

per-fragment阶段之后,(Xw,Yw)处的片段要么被拒绝要么会产生一个fragmentcolor,depth,stencil值写入到framebuffer(Xw,Yw)位置。Fragmentcolor, depth, stencil值是否真的写入framebuffer还取决于相应的writemasks是否enable

另外,OpenGLES2.0也提供了从framebuffer回读像素的接口,但只有colorbuffer可以回读,depthstencil值是读不到的。

alphatest不再在pre-fragmentstage中支持,需要在fragmentshader中实现

logicOp被去掉了,因为很少使用。(alphatestlogicopOpenGL2.0OpenGLES1.x中是存在的)

ES2.0ES1.1的兼容性

ES2.0不向后兼容ES1.1,2.0不支持1.1中的固定功能流水线。2.0vertexshader代替了1.1中的固定功能顶点处理单元,即:顶点T&L,纹理坐标生成和变换,顶点颜色计算。fragmentshader代替了1.1的固定功能texturecombine单元,即为每个textureunit实现一个texturecombine stage

(不同于OpenGL2.0,OpenGL2.0是完全向后兼容的,同时支持可编程流水线和固定流水线。)

EGL简介

OpenGLEs命令需要一个renderingcontext和一个drawingsurfaceRenderingcontext存储OpenGLEs状态。Drawingsurface是图元绘制上去的surfacedrawingsurface指定了渲染所需的buffer的类型,例如colorbuffer, depth bufferstencilbuffer,以及每种buffer的位数。

OpenGLES API不提供如何创建renderingcontext并且attachnatviewindow systemEGL是一个在OpenGLEsnative 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

  1. attach可以在任意时刻进行,哪怕shader没有compile,甚至没有source

  2. 可用glDetachShaderprogram上移除shader

  3. 如果一个shader已经被attach到一个programobject上,调用glDeleteShader不会立即删除这个shader,这个shader会被标记为待删除,一旦这个shader不再attach到任何programobject上,这个shader的内存将被释放。

Link:

当两个shaderattachprogram上,并且shader已经成功compile,使用glLinkProgram连接program

1)linker会检查,确保fs使用的所有varyingvariables会被vs写入,并且被声明成同样的类型

2)linker会检查,确保vsfs中声明的uniforms具有匹配的类型

3)linker会确保最终的program适合实现的限制(即,attribute,uniform, varying的数量,使用的指令)

link阶段会产生最终的硬件指令(link是在GPU上进行的)

有些问题如纹理没有被绑定到samper,在Link时是不能查到的,可使用glValidateProgram(program);验证program是否可以在当前状态下执行。这个操作很慢,一般只在debug版本中使用。

使用:

glUseProgram(program);

-----------uniformsand attributes------------------------------------

uniform: 应用程序通过通过es2.0API传送给shader的只读变量;uniformprogramobject中是共享的,即一个programobject只有一组uniforms。如果uniform同时在vsfs中声明,必须具有相同的类型,并且在这两个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);

获得uniformlocation。这样就可以使用一系列的glUniform*函数载入uniform的值。

使用glUniform*的时候,不用programhandle作为参数,因为这是针对当前useprogram的。但是一旦为一个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是由3vec3组成。矩阵的列用数组下标选择。

然后每个列向量使用向量访问规则。例如:

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至少支持128vertexuniform vectors16fragmentuniform 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中,如果没有指定默认精度,则intfloat的默认精度都是highp。对于fragmentshaderfloat类型的默认精度没有默认值,必须显示的声明。并且在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);


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics