前言
这里是一篇新手教程,环境是Xcode7+OpenGL ES 2.0,目标写一个OpenGL ES的hello world
。
OpenGL ES系列教程在这里。
OpenGL ES系列教程的代码地址
你的star和fork是我的源动力,你的意见能让我走得更远。
核心思路
通过GLKit,尽量简单地实现把一张图片绘制到屏幕。
效果展示
具体细节
1、新建OpenGL ES上下文
- (void)setupConfig {
//新建OpenGLES 上下文
self.mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; //2.0,还有1.0和3.0
GLKView* view = (GLKView *)self.view; //storyboard记得添加
view.context = self.mContext;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; //颜色缓冲区格式
[EAGLContext setCurrentContext:self.mContext];
}
GLKView* view = (GLKView *)self.view;
这里需要在storyboard里面把view的类设置成GLKView,其他代码是OpenGL ES上下文的创建。
2、顶点数组和索引数组
//顶点数据,前三个是顶点坐标,后面两个是纹理坐标
GLfloat squareVertexData[] =
{
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
0.5, 0.5, -0.0f, 1.0f, 1.0f, //右上
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
-0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下
};
顶点数组里包括顶点坐标,OpenGLES的世界坐标系是[-1, 1],故而点(0, 0)是在屏幕的正中间。
纹理坐标系的取值范围是[0, 1],原点是在左下角。故而点(0, 0)在左下角,点(1, 1)在右上角。
索引数组是顶点数组的索引,把squareVertexData数组看成4个顶点,每个顶点会有5个GLfloat数据,索引从0开始。
3、顶点数据缓存
//顶点数据缓存
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition); //顶点数据缓存
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0); //纹理
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
这是本章节的核心内容。
- glGenBuffers申请一个标识符
- glBindBuffer把标识符绑定到
GL_ARRAY_BUFFER
上 - glBufferData把顶点数据从cpu内存复制到gpu内存
- glEnableVertexAttribArray 是开启对应的顶点属性
- glVertexAttribPointer设置合适的格式从buffer里面读取数据
4、纹理贴图
- (void)uploadTexture {
//纹理贴图
NSString* filePath = [[NSBundle mainBundle] pathForResource:@"for_test" ofType:@"jpg"];
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft, nil];//GLKTextureLoaderOriginBottomLeft 纹理坐标系是相反的
GLKTextureInfo* textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
//着色器
self.mEffect = [[GLKBaseEffect alloc] init];
self.mEffect.texture2d0.enabled = GL_TRUE;
self.mEffect.texture2d0.name = textureInfo.name;
}
- GLKTextureLoader读取图片,创建纹理GLKTextureInfo
- 创建着色器GLKBaseEffect,把纹理赋值给着色器
基础
代码带了很多注释,百度下相应的概念,会有更多解释。
如果对OpengGL ES感兴趣,但是却毫无图形学基础的,可以看看LearnOpenGL教程。
思考题
- 1、代码中有6个顶点坐标,能否使用更少的顶点显示一个图像?
- 2、顶点缓存数组可以不用glBufferData,要如何实现?
- 3、如果把这个图变成左右两只对称的熊猫,该如何改?
这里可以下载demo代码。
思考题答案
思考题1:
可以使用四个顶点,绘制2个三角形 的6个顶点中有2个是重复的,使用索引可以减少重复。
思考题2:
顶点缓存数组可以不用glBufferData,要如何实现?顶点数组可以通过glBufferData放入缓存,也可以直接通过glVertexAttribPointer最后一个参数,直接把顶点数组从CPU传送到GPU。区别:glBufferData里面的顶点缓存可以复用,glVertexAttribPointer是每次都会把顶点数组从CPU发送到GPU,影响性能。
思考题3:
如果把这个图变成左右两只对称的熊猫,该如何改?把屏幕切分成4个三角形,左边两个三角形同上,右边两个三角形的纹理坐标的x值调整即可。
附
一个热血青年想在业余时间做更多的尝试,做一些能帮助别人也能受惠自己的事情。
思来想去,决定延续现在写文章的思路——用自己的经历和知识给职场填坑,让人少走弯路。
网友评论
可以将顶点数据改为如下所示:
GLfloat vertexData[] =
{
0.5, 0.5, -0.0f, 1.0f, 1.0f, //右上
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
-0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下
};
绘制模式改为: glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
这个函数的第二个参数 您这里写的是3
但我再网上看到一钟解析是 每个点的数据个数,我们这里每个点只有x、y2个值所以是2
然后我把3改成2 感觉是一样的结果
不知道大神您怎么理解呢?
最近看了一部分openGL的入门文档,有一个疑惑,iOS 8以后出了Scenekit(库没有看过),在3D模型部分iOS 完全有更加简便的实现方式了。
请问学习openGL除了对加深对原理的了解,还有Scenekit、ARKit所不具备的功能吗?
- (void)uploadVertexArray {
//顶点数据,前三个是顶点坐标,后面两个是纹理坐标
GLfloat squareVertexData[] =
{
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
0.5, 0.5, -0.0f, 1.0f, 1.0f, //右上
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
-0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下
};
GLbyte indices[] =
{
0,1,2,
2,3,0
};
//顶点数据缓存
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);
GLuint texturebuffer;
glGenBuffers(1, &texturebuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, texturebuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition); //顶点数据缓存
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0); //纹理
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//启动着色器
[self.mEffect prepareToDraw];
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
}
思考题3:
GLfloat squareVertexData[] =
{
1, -0.5, 0.0f, 1.0f, 0.0f, //右下
1, 0.5, -0.0f, 1.0f, 1.0f, //右上
0.0, 0.5, 0.0f, 0.0f, 1.0f, //中上
0.0, -0.5, 0.0f, 0.0f, 0.0f, //中下
-1, 0.5, 0.0f, 0.0f, 1.0f, //左上
-1, -0.5, 0.0f, 0.0f, 0.0f, //左下
0.0, -0.5, 0.0f, 1.0f, 0.0f, //中下
0.0, 0.5, 0.0f, 1.0f, 1.0f, //中上
};
GLbyte indices[] =
{
0,1,2,
2,3,0,
4,5,6,
6,7,4
};
修改下count
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, 0);
{
1, -0.5, 0.0f, 0.0f, 0.0f, //右下
1, 0.5, -0.0f, 0.0f, 1.0f, //右上
0.0, 0.5, 0.0f, 1.0f, 1.0f, //中上
0.0, -0.5, 0.0f, 1.0f, 0.0f, //中下
-1, 0.5, 0.0f, 0.0f, 1.0f, //左上
-1, -0.5, 0.0f, 0.0f, 0.0f, //左下
0.0, -0.5, 0.0f, 1.0f, 0.0f, //中下
0.0, 0.5, 0.0f, 1.0f, 1.0f, //中上
};
即可对称。
//顶点数组,前三个是顶点坐标,后面两个是纹理坐标
GLfloat squareVertexData[] =
{
0.5, -0.5, 0.0f, 0.0f, 0.0f, //右下 0
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上 1
-0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下 2
0.5, 0.5, 0.0f, 0.0f, 1.0f, //右上 3
0.0, 0.5, 0.0f, 1.0f, 1.0f, //中上 4
0.0, -0.5, 0.0f, 1.0f, 0.0f, //中下 5
0.0, 0.5, 0.0f, 1.0f, 1.0f, //中上 6
0.0, -0.5, 0.0f, 1.0f, 0.0f, //中下 7
};
//顶点索引
GLuint indices[] =
{
1, 4, 5,
5, 2, 1,
0, 3, 6,
6, 7, 0,
};
不过这里 和你的代码 有点差异 希望楼主在文章中加上
1.缺少GLKView 的代理设置 设置storyboard 的时候和多时候不会去设置这个代理
GLKView *view = (GLKView *)self.view;
view.delegate = self;
2.以及代理方法的实现
/**
* 渲染场景代码
*/
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//启动着色器
[self.mEffect prepareToDraw];
glDrawElements(GL_TRIANGLES, self.mCount, GL_UNSIGNED_INT, 0);
}
四个顶点,可以绘制2个三角形,两个三角形拼接成一个矩形。三角形内的任意一点,根据顶点坐标插值生成对应的顶点和纹理坐标。
顶点缓存数组可以不用glBufferData,要如何实现?
顶点数组可以通过glBufferData放入缓存,也可以直接通过glVertexAttribPointer最后一个参数,直接把顶点数组从CPU传送到GPU。区别:glBufferData里面的顶点缓存可以复用,glVertexAttribPointer是每次都会把顶点数组从CPU发送到GPU,影响性能。
如果把这个图变成左右两只对称的熊猫,该如何改?
把屏幕切分成4个三角形,左边两个三角形同上,右边两个三角形的纹理坐标的x值调整即可。