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视图
如图所示,概述了绘制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开发中,屏幕左上角是坐标原点,往右是x轴正方向,往下是y轴正方向。而在OpenGL ES中,屏幕的中点是坐标原点,往右是x轴正方向,往下是y轴正方向,其中z轴的正方向是从屏幕往外的方向,如下图所示:
根据OpenGL ES的坐标系,我们定义一下要绘制的图片的几个顶点,顶点坐标和纹理坐标是放在一个GLfloat数组中管理的,定义一组顶点数据的跨度为5,其中前三个存储顶点坐标,后两个存储纹理坐标,下图一共定义了4个顶点,就是矩形的四个顶点,需要注意的是,虽然坐标都是0.5,但是绘制出来的图形并不是正方形,因为我们用来最终显示的是iPhone屏幕,手机的长和宽并不相等。
OpenGL ES不能绘制多边形,只能绘制点,线,三角形,OpenGL可以绘制多边形,由于我们绘制的图片是一个矩形,又两个三角形构成,就是下图中的两个顶点索引(0,1,3)和(1,2,3)组成的三角形拼成一个矩形。
- (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分别表示顶点坐标和纹理坐标两个变量的属性索引。(顶点索引不是顶点数据,这里我们不需要管这个值)
上面的程序最需要注意的是变量的偏移很重要,如果把GLfloat写成CGFloat,会导致图片渲染不出来。
参考文献:
http://www.jianshu.com/p/23e938fab9ca
http://www.jianshu.com/p/954339d57541