关于GLKit的推断

Title: 关于GLKit的推断
Date: 2016-05-09 22:00
Modified: 2016-05-09 22:00
Category: OpenGL/ES
Tags: OpenGLES GLKit AGLKContext
Slug: opengles_GLKit
Authors: lizhonghuan
Summary:本篇是对OpenGLES_Ch2_1中的缓存管理代码的简单重用和重构,用以加深对GLKView的理解。苹果的GLKit框架封装了gl的具体实现,以便使用gl的用户减少编写gl的代码量以及避免不必要的错误。

本文介绍两个封装类,AGLKContextAGLKVertexAttribArrayBuffer,前者是内建的EAGLContext的简单子类,后者封装了使用OpenGLES2.0顶点属性数组缓存的7个步骤;

AGLKContext.m

// This method sets the clear (background) RGBA color.
// The clear color is undefined until this method is called.
- (void)setClearColor:(GLKVector4)clearColorRGBA
{
   clearColor = clearColorRGBA;
    
   NSAssert(self == [[self class] currentContext],
      @"Receiving context required to be current context");
      
   glClearColor(
      clearColorRGBA.r, 
      clearColorRGBA.g, 
      clearColorRGBA.b, 
      clearColorRGBA.a);
}
/////////////////////////////////////////////////////////////////
// This method instructs OpenGL ES to set all data in the
// current Context's Render Buffer(s) identified by mask to
// colors (values) specified via -setClearColor: and/or
// OpenGL ES functions for each Render Buffer type.
- (void)clear:(GLbitfield)mask
{
   NSAssert(self == [[self class] currentContext],
      @"Receiving context required to be current context");
      
   glClear(mask);
}

函数原型: void glClear(GLbitfield mask);
参数说明:
GLbitfield:可以使用 | 运算符组合不同的缓冲标志位,表明需要清除的缓冲,例如glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)表示要清除颜色缓冲以及深度缓冲,可以使用以下标志位

  GL_COLOR_BUFFER_BIT:    当前可写的颜色缓冲
  GL_DEPTH_BUFFER_BIT:    深度缓冲
  GL_ACCUM_BUFFER_BIT:    累积缓冲    
  GL_STENCIL_BUFFER_BIT:  模板缓冲

函数说明:
glClear()函数的作用是用当前缓冲区清除值,也就是glClearColor或者glClearDepth、glClearIndex、glClearStencil、glClearAccum等函数所指定的值来清除指定的缓冲区,也可以使用glDrawBuffer一次清除多个颜色缓存。

以上两个函数的封装为渲染提供背景,并且利用glClearColor()将上下文帧缓存中的每个像素颜色设置为该颜色值;该类中的其他函数是OpenGL的相关函数,留着以后再研究;

AGLKVertexAttribArrayBuffer.m

接下来我们说一下在开篇中谈到的7个步骤;

  • NSParameterAssert
    有的方法的条件可以满足可以不满足,都不影响执行。但如果你希望程序在某些条件不满足的时候产生错误告诉你,就用nsparameterassert让程序崩溃。

  • glGenBuffers

// This method creates a vertex attribute array buffer in
// the current OpenGL ES context for the thread upon which this 
// method is called.
- (id)initWithAttribStride:(GLsizei)aStride
   numberOfVertices:(GLsizei)count
   bytes:(const GLvoid *)dataPtr
   usage:(GLenum)usage;
{
   NSParameterAssert(0 < aStride);
   NSAssert((0 < count && NULL != dataPtr) ||
      (0 == count && NULL == dataPtr),
      @"data must not be NULL or count > 0");
      
   if(nil != (self = [super init]))
   {
      stride = aStride;
      bufferSizeBytes = stride * count;
      
      glGenBuffers(1,                // STEP 1
         &name);
      glBindBuffer(GL_ARRAY_BUFFER,  // STEP 2
         self.name); 
      glBufferData(                  // STEP 3
         GL_ARRAY_BUFFER,  // Initialize buffer contents
         bufferSizeBytes,  // Number of bytes to copy
         dataPtr,          // Address of bytes to copy
         usage);           // Hint: cache in GPU memory
         
      NSAssert(0 != name, @"Failed to generate name");
   }
   return self;
}   

glGenBuffers()创建缓存对象并且返回缓存对象的标示符。它需要2个参数:第一个为需要创建的缓存数量,第二个为用于存储单一ID或多个ID的GLuint变量或数组的地址。

void glGenBuffers(GLsizei n, GLuint *buffers);
//在buffers数组中返回当前n个未使用的名称,表示缓冲区对象
GLboolean glIsBuffer(GLuint buffer);
//判断是否是缓冲区对象
  • glBindBuffer

当缓存对象创建之后,在使用缓存对象之前,我们需要将缓存对象连接到相应的缓存上。
glBindBuffer()有2个参数:target与buffer。

void glBindBuffer(GLenum target, GLuint buffer)

target告诉顶点缓存对象(VBO)该缓存对象将保存顶点数组数据还是索引数组数据:GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY。任何顶点属性,如顶点坐标、纹理坐标、法线与颜色分量数组都使用GL_ARRAY_BUFFER。用于glDraw[Range]Elements()的索引数据需要使用GL_ELEMENT_ARRAY绑定。注意,target标志帮助VBO确定缓存对象最有效的位置,如有些系统将索引保存AGP或系统内存中,将顶点保存在显卡内存中。
当第一次调用glBindBuffer(),VBO用0大小的内存缓存初始化该缓存,并且设置VBO的初始状态,如用途与访问属性。

  • glBufferData

当缓存初始化之后,你可以使用glBufferData()将数据拷贝到缓存对象。

void glBufferData(GLenum target,GLsizeiptr size, const GLvoid* data, GLenum usage);

第一个参数target可以为GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY。size为待传递数据字节数量。第三个参数为源数据数组指针,如data为NULL,则VBO仅仅预留给定数据大小的内存空间。最后一个参数usage标志位VBO的另一个性能提示,它提供缓存对象将如何使用:static、dynamic或stream、与read、copy或draw。
VBO为usage标志指定9个枚举值:
GL_STATIC_DRAW
GL_STATIC_READ
GL_STATIC_COPY
GL_DYNAMIC_DRAW
GL_DYNAMIC_READ
GL_DYNAMIC_COPY
GL_STREAM_DRAW
GL_STREAM_READ
GL_STREAM_COPY
”static“表示VBO中的数据将不会被改动(一次指定多次使用),”dynamic“表示数据将会被频繁改动(反复指定与使用),”stream“表示每帧数据都要改变(一次指定一次使用)。”draw“表示数据将被发送到GPU以待绘制(应用程序到GL),”read“表示数据将被客户端程序读取(GL到应用程序),”copy“表示数据可用于绘制与读取(GL到GL)。

// A vertex attribute array buffer must be prepared when your 
// application wants to use the buffer to render any geometry. 
// When your application prepares an buffer, some OpenGL ES state
// is altered to allow bind the buffer and configure pointers.
- (void)prepareToDrawWithAttrib:(GLuint)index
   numberOfCoordinates:(GLint)count
   attribOffset:(GLsizeiptr)offset
   shouldEnable:(BOOL)shouldEnable
{
   NSParameterAssert((0 < count) && (count < 4));
   NSParameterAssert(offset < self.stride);
   NSAssert(0 != name, @"Invalid name");
    
   glBindBuffer(GL_ARRAY_BUFFER,     // STEP 2
      self.name);
    
   if(shouldEnable)
   {
      glEnableVertexAttribArray(     // Step 4
         index); 
   }
    
   glVertexAttribPointer(            // Step 5
      index,               // Identifies the attribute to use
      count,               // number of coordinates for attribute
      GL_FLOAT,            // data is floating point
      GL_FALSE,            // no fixed point scaling
      (self.stride),       // total num bytes stored per vertex
      NULL + offset);      // offset from start of each vertex to 
                           // first coord for attribute
    #ifdef DEBUG
       {  // Report any errors 
          GLenum error = glGetError();
          if(GL_NO_ERROR != error)
          {
             NSLog(@"GL Error: 0x%x", error);
          }
       }
    #endif
}
  • glEnableVertexAttribArray

第4步和第5步理解起来有点困难,暂时放下,之后研究OpenGL的时候细研究,这里给出原书中的摘录;

在第4步中,通过调用glEnableVertexAttribArray()来启动顶点缓存渲染操作;OpenGLES所支持的每一个渲染操作都可以单独地使用保存在当前OpenGLES上下文中的设置来开启或关闭。
在第5步中,glVertexAttribPointer()告诉OpenGLES顶点数据在哪里,以及怎么解释为每个顶点保存的数据。第一个参数指示当前绑定的缓存包含每个顶点的位置信息;第二个参数指示每个位置有3个部分;第三个参数告诉OpenGLES每个部分都保存为一个浮点类型的值;第四个参数告诉OpenGLES小数点固定数据是否可以被改变;第五个参数叫做“步幅”,它指定了每个顶点的保存需要多少个字节,换句话说,步幅指定了GPU从一个顶点的内存开始位置转到下一个顶点的内存开始位置需要跳过多少字节,sizeof(GLKVector3)指示在缓存中没有额外的字节,即顶点位置数据是密封的,在一个顶点缓存中保存除了每个顶点位置的X Y Z坐标之外的其他数据也是可能的;

  • glVertexAttribPointer
void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,const GLvoid * pointer);

参数:
index
指定要修改的顶点属性的索引值
size
指定每个顶点属性的组件数量。必须为1、2、3或者4。初始值为4。(如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a))
type
指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。
normalized
指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)。
stride
指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。
pointer
指定第一个组件在数组的第一个顶点属性中的偏移量。该数组与GL_ARRAY_BUFFER绑定,储存于缓冲区中。初始值为0;

// Submits the drawing command identified by mode and instructs
// OpenGL ES to use count vertices from the buffer starting from
// the vertex at index first. Vertex indices start at 0.
- (void)drawArrayWithMode:(GLenum)mode
   startVertexIndex:(GLint)first
   numberOfVertices:(GLsizei)count
{
   NSAssert(self.bufferSizeBytes >= 
      ((first + count) * self.stride),
      @"Attempt to draw more vertex data than available.");
      
   glDrawArrays(mode, first, count); // Step 6
}
  • glDrawArrays

glDrawArrays()第一个参数会告诉GPU怎么处理在绑定的顶点缓存内的顶点数据;第二个参数和第三个参数分别指定缓存内的需要渲染的第一个顶点的位置和需要渲染的顶点的数量;

// This method deletes the receiver's buffer from the current
// Context when the receiver is deallocated.
- (void)dealloc
{
    // Delete buffer from current context
    if (0 != name)
    {
        glDeleteBuffers(1, &name); // Step 7 
        name = 0;
    }
}
  • glDeleteBuffers

删除不需再需要的顶点缓存和上下文,该方法保证Cocoa Touch收回上下文使用的内存和其他资源。

本例的代码下载链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容