iOS全景图片之OpenGL学习笔记

OpenGL版本

在开发OpenGL项目前,需要根据业务需求选择合适的版本。在初始化EAGLContext时指定ES版本号。
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];

EAGLContext

与UIKit中CGContextRef相似,EAGLContext相当于OpenGL绘制句柄或者上下文,在绘制试图之前,需要指定使用创建的上下文绘制
[EAGLContextsetCurrentContext:view.context];

创建和配置GLKit视图

可以以编程方式或使用Interface Builder创建和配置GLKView对象。在使用它绘制之前,必须将其与EAGLContext对象相关联。

  • 以编程方式创建视图时,首先创建上下文,然后将其传递给视图的initWithFrame:context:方法。
  • 从故事板加载视图后,创建上下文并将其设置为视图的上下文属性的值。

GLKit视图会自动创建和配置自己的OpenGL ES framebuffer对象和renderbuffers。可以使用视图的可绘制属性来控制这些对象的属性。如果更改GLKit视图的大小,比例因子或可绘制属性,则会在下次绘制内容时自动删除并重新创建相应的framebuffer对象和renderbuffers。

绘制GLKit视图

GLKView_diagram_2x.png

如图所示,概述了绘制OpenGL ES内容的三个步骤:准备OpenGL ES基础设施,发布绘图命令,并将呈现的内容呈现给Core Animation进行显示。 GLKView类实现了第一和第三步。对于第二步,您将实现一个绘图方法,如下代码中的示例所示。

- (void)drawRect:(CGRect)rect
{
    // Clear the framebuffer
    glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Draw using previously configured texture, shader, uniforms, and vertex array
    glBindTexture(GL_TEXTURE_2D, _planetTexture);
    glUseProgram(_diffuseShading);
    glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m);
    glBindVertexArrayOES(_planetMesh);
    glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT);
}

注意:glClear函数提示OpenGL ES可以丢弃任何现有的帧缓冲区内容,避免了昂贵的内存操作将以前的内容加载到内存中。为了确保最佳性能,您应该在绘制之前始终调用此函数。

定义要绘制图片的顶点坐标和纹理坐标

OpenGL ES中坐标系是和iOS常用的Quartz 2D坐标系是不一样的,Quartz 2D坐标系属于右手坐标系,OpenGL ES属于左手坐标系。先说一下左手坐标系和右手坐标系。
伸出左手,让拇指和食指成“L”形状,拇指向右,食指向上,中指指向起那方,这时就建立了一个“左手坐标系”,拇指、食指和中指分表代表x、y、z轴的正方向。“右手坐标系”就是用右手,如下图所示:

iOS全景图片之OpenGL学习笔记_第1张图片
“左手坐标系”和“右手坐标系”.png

在iOS开发中,屏幕左上角是坐标原点,往右是x轴正方向,往下是y轴正方向。而在OpenGL ES中,屏幕的中点是坐标原点,往右是x轴正方向,往下是y轴正方向,其中z轴的正方向是从屏幕往外的方向,如下图所示:

iOS全景图片之OpenGL学习笔记_第2张图片
OpenGL ES坐标系.png

根据OpenGL ES的坐标系,我们定义一下要绘制的图片的几个顶点,顶点坐标和纹理坐标是放在一个GLfloat数组中管理的,定义一组顶点数据的跨度为5,其中前三个存储顶点坐标,后两个存储纹理坐标,下图一共定义了4个顶点,就是矩形的四个顶点,需要注意的是,虽然坐标都是0.5,但是绘制出来的图形并不是正方形,因为我们用来最终显示的是iPhone屏幕,手机的长和宽并不相等。

iOS全景图片之OpenGL学习笔记_第3张图片
顶点坐标和纹理坐标.png

OpenGL ES不能绘制多边形,只能绘制点,线,三角形,OpenGL可以绘制多边形,由于我们绘制的图片是一个矩形,又两个三角形构成,就是下图中的两个顶点索引(0,1,3)和(1,2,3)组成的三角形拼成一个矩形。

顶点索引.png
- (void)setupOpenGL {
    
    [EAGLContext setCurrentContext:_context];
    glEnable(GL_DEPTH_TEST);
    // 顶点
    GLfloat *vVertices  = NULL;
    // 纹理
    GLfloat *vTextCoord = NULL;
    // 索引
    GLushort *indices   = NULL;
    int numVertices     = 0;
    _numIndices         = esGenSphere(200, 1.0, &vVertices, &vTextCoord, &indices, &numVertices);
    // 加载顶点索引数据
    // 创建索引buffer并将indices的数据放入
    glGenBuffers(1, &_vertexIndicesBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vertexIndicesBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, _numIndices*sizeof(GLushort), indices, GL_STATIC_DRAW);
    // 加载顶点坐标
    // 创建顶点buffer并将vVertices中的数据放入
    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices*3*sizeof(GLfloat), vVertices, GL_STATIC_DRAW);
    //设置顶点属性,对顶点的位置,颜色,坐标进行赋值
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*3, NULL);
 
    // 创建纹理buffer并将vTextCoord数据放入
    glGenBuffers(1, &_vertexTexCoord);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexTexCoord);
    glBufferData(GL_ARRAY_BUFFER, numVertices*2*sizeof(GLfloat), vTextCoord, GL_DYNAMIC_DRAW);
    
    //设置纹理属性,对纹理的位置,颜色,坐标进行赋值
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*2, NULL);

    // 将图片转换成为纹理信息
    NSString *imagePath = [[NSBundle mainBundle] pathForResource:self.imageName ofType:self.imageNameType];
    
    // 由于OpenGL的默认坐标系设置在左下角, 而GLKit在左上角, 因此需要转换
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],
                             GLKTextureLoaderOriginBottomLeft,
                             nil];
    
    _textureInfo = [GLKTextureLoader textureWithContentsOfFile:imagePath options:options error:nil];
    
    // 设置着色器的纹理
    _effect                    = [[GLKBaseEffect alloc] init];
    _effect.texture2d0.enabled = GL_TRUE;
    _effect.texture2d0.name    = _textureInfo.name;
}

//启用顶点位置(坐标)数组,之前说过opengl是状态机,需要什么状态就启动什么状态
glEnableVertexAttribArray(GLKVertexAttribPosition);

 GLfloat vertexs[] = {
        -0.5, -0.5, 0,     0.0, 0.0,   //左下
        -0.5,  0.5, 0,     0.0, 1.0,   //左上
         0.5,  0.5, 0,     1.0, 1.0,   //右上
         0.5, -0.5, 0,     1.0, 0.0,   //右下
    };
启用通用顶点属性
 /*
  index:指定通用顶点数据的索引,这个值的范围从0到支持的最大顶点属性数量减1
  功能:用于启用通用顶点属性
*/
void glEnableVertexAttribArray(GLuint index);
禁止通用顶点属性
 /*
  index:指定通用顶点数据的索引,这个值的范围从0到支持的最大顶点属性数量减1
*/
void glDisableVertexAttribArray(GLuint index);
顶点数组设置值
index: 通用顶点属性索引
size: 顶点数组中为顶点属性指定的分量数量,取值范围1~4
type: 数据格式 ,两个函数都包括的有效值是
      GL_BYTE  GL_UNSIGNED_BYTE  GL_SHORT  GL_UNSIGNED_SHORT  GL_INT  GL_UNSIGNED_INT
      glVertexAttribPointer还包括的值为:GL_HALF_FLOAT GL_FLOAT 等
normalized: 仅glVertexAttribPointer使用,表示非浮点数据类型转换成浮点值时是否应该规范化
stride: 每个顶点由size指定的顶点属性分量顺序存储。stride指定顶点索引i和i+1表示的顶点之间的偏移。
    如果为0,表示顺序存储。如果不为0,在取下一个顶点的同类数据时,需要加上偏移。
ptr: 如果使用“顶点缓冲区对象”,表示的是该缓冲区内的偏移量。

void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr);
// 取值为“整数”版本
void glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr);

这里由于我们使用iOS封装好的GLkit框架,不需要我们设置着色器程序,里面有内置的设置好的index位置,就是下面的变量:

typedef NS_ENUM(GLint, GLKVertexAttrib)
{
    GLKVertexAttribPosition,
    GLKVertexAttribNormal,
    GLKVertexAttribColor,
    GLKVertexAttribTexCoord0,
    GLKVertexAttribTexCoord1
} NS_ENUM_AVAILABLE(10_8, 5_0);

GLKit里面的GLKVertexAttribPosition和GLKVertexAttribTexCoord0分别表示顶点坐标和纹理坐标两个变量的属性索引。(顶点索引不是顶点数据,这里我们不需要管这个值)

启用顶点数组和指定顶点属性.png

上面的程序最需要注意的是变量的偏移很重要,如果把GLfloat写成CGFloat,会导致图片渲染不出来。

参考文献:
http://www.jianshu.com/p/23e938fab9ca
http://www.jianshu.com/p/954339d57541

你可能感兴趣的:(iOS全景图片之OpenGL学习笔记)