学习代码地址
OpenGL(ES)学习一:准备
OpenGL(ES)学习二:绘制一个三角形
两年前看博客和OpenGL超级宝典开始入门,后来接触Unity开发,对3D图形有了比较直观的理解,特别感谢[Unity Shader入门精要
这本书,给了我很多明确的知识,像光照模型、法线贴图等都是在这才懂-_-。
然后就是到最近开始准备明确系统的学习下OpenGL的知识,主要跟随learnOpenGL学习。OpenGL的知识,要么很分散,要么是外国人写的看不懂的书,就像超级宝典,特别是刚开始的时候很多概念都没有建立。这个网站真的超级好,一步步开始。如果能跟着learnOpenGL是最好了,或者中文版learnOpenGL。
准备写一系列的博客,把作为学习笔记,如果能帮助到其他学习者最好了。教学相长,当想着把有一个概念讲给别人是,会更加清晰、严谨的去确认这个概念。很多时候,一些知识在脑袋里就处于“是那么一回事”的状态,而写下来就是让这种理解更明确。
学习代码都在这个项目里,而且做了win/mac/iOS三个平台的测试。iOS上是测试OpenGL ES。
mac和win
1. 需要4个库:glfw, glew, soil 和 glm.
- glfw用来打开窗口、生成OpenGL context、接入键盘鼠标控制等。
- glew提供对应各平台OpenGL统一的接口,不需要自己根据平台不同而再做处理
- soil用来加载图片,用于纹理生成
- glm提供了矩阵、向量等3D变量类型的定义和操作。glm只是头文件,和其他稍有不同。
安装:
1. 使用homebrew下载,除了soil,其他的都有,这个最方便。而soil可以用[stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h)代替。win上用chocolatey,只有glfw.
2. 到网站下载代码,除了glm,其他几个都要编译成静态库才能用,而且要用[cmake](https://cmake.org/download/),因为各个平台不一样。cmake使用官网下载桌面程序(glfw和glew),或者在终端使用cmake命令(soil)。cmake程序使用先点击Configure,然后点Generate,在目标目录生成对应的程序。mac上是xcode,win上是vs,然后运行项目,编译静态库。soil下载好后,进入soil根目录,运行make & make install得到静态库。
3. 或者使用我示例项目里编译好的包。
4. 把静态库和对应的头文件放到项目里。
2.初始配置
建好项目后,在main函数里开始编写。
-
配置GLFW
glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
mac上加上:
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
然后创建窗口:
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
使用窗口构建当前的context:
glfwMakeContextCurrent(window);
设置OpenGL的视野大小,这个是渲染的内容的大小,这里选择和window一样大。
int width,height; glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width, height);
-
初始化GLEW:
glewExperimental = GLFW_TRUE; if (glewInit() != GLEW_OK) { std::cout<< "Failed to initilize GLEW" <
-
循环调用渲染命令
while (!glfwWindowShouldClose(window)) { glfwPollEvents(); //Here is render commands glfwSwapBuffers(window); }
glfwPollEvents处理窗口事件,有了这句窗口才能响应用户才做。
glfwSwapBuffers 是切换前后缓冲区,即有两个缓冲区存储图像,一个是当前显示的,另一个是当前写入的,所以写入后要切换,让刚写入的显示,然后在渲染另一个缓冲区。这样可以让显示和写入同时进行,加快速度。
3.简单测试
在渲染命令位置加上:
glClearColor(1.0f, 1.0f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor设置清除后的颜色,glClear对响应缓冲区进行清除,这里只清除颜色,使用GL_COLOR_BUFFER_BIT。
如果没问题,运行后,窗口填充为刚设置的颜色,即淡黄色。
iOS
iOS上和电脑上比较不同,不需要第三方库管理窗口,而且Apple本身也有一些OpenGL ES上的封装。主要依据Drawing to Other Rendering Destinations这一篇来处理。
- 首先iOS上可以使用GLKit,这个是对OpenGL ES封装后的工具库,而为了学习,就不使用这个,而是自己搭建环境。
- 根据上面的文档,主要就是建立自己的Framebuffer,它包含了一帧画面需要的颜色、深度和模板测试数据这些信息。我们使用绘制命令,把数据输出到Framebuffer里,然后framebuffer由一个和它贡献数据的CAEAGLLayer,由这个layer负责显示。
所以根据文档进行操作:
-
需要一个用来显示的view,新建一个UIView的子类用来封装我们的OpenGL ES绘制操作。
@interface TFGLView : UIView
,然后为了让它能呈现绘制的内容,把它的layer修改成CAEAGLLayer
。
+(Class)layerClass{
return [CAEAGLLayer class];
}
2. 初始化EAGLContext
`_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];`
这里使用OpenGL ES3,所以头文件导入为
`#import `.
然后把这个context设为当前context:
`[EAGLContext setCurrentContext:_context]`
3. 初始化framebuffer
```
glGenBuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
```
`glGenxxx`类型的函数很多,都是用来生成某个东西的,而拿到的一半都是int类型,也就是一个标志而已。比如这里生成一个framebuffer,你不会拿到一个对象,甚至内存地址都没有。_frameBuffer是`GLuint`类型。可以把它们看做一个id,我猜这样都是为了性能处理。写多了面向对象的程序,会对这样的状况稍有不适。
`glBindxxx`也是常见类型,用来表示下面的操作都是针对绑定的对象的。比如这里,下面使用`GL_FRAMEBUFFER`的地方,都是对_frameBuffer的操作,知道你再次绑定了其他对象。
4. 构建colorbuffer,并把它附加到framebuffer上
glGenRenderbuffers(1, &_colorBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorBuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_renderLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorBuffer);
前两句和framebuffer一样,先生成,然后绑定。第三句,给colorbuffer生成存储空间,这里使用了`_context`的方法,就是通过这里实现colorbuffer 和 _renderLayer的内存共享,所以之后_renderLayer可以显示colorbuffer的内容。
注意这里使用了GL_RENDERBUFFER,而不是 _colorBuffer,这就是glBindRenderbuffer绑定的作用.
5. 设置OpenGL ES视野大小位置,即渲染的图像在layer中的位置和大小
```
GLint width,height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
glViewport(0, 0, width, height);
```
同样使用glViewport函数。
5. 检测一下framebuffer的状态,如果没问题就可以开始绘制了
```
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
if(status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"failed to make complete framebuffer object %x", status);
}
```
####简单绘制
glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer);
glClearColor(1.0f, 1.0f, 0.5f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glBindFramebuffer,保证这次渲染的目表是我们构建的framebuffer.
glClearColor设置清除后颜色,glClear清除缓冲区,GL_DEPTH_BUFFER_BIT是深度缓冲区,GL_COLOR_BUFFER_BIT是颜色缓冲区。如果不清除,那么上一次绘制的内容会保留,导致和这次绘制的内容叠加在一起。
然后把渲染结果提交显示:
glBindRenderbuffer(GL_RENDERBUFFER, self.colorBuffer);
[self.context presentRenderbuffer:GL_RENDERBUFFER];
把自定义的view添加到界面上,查看结果,如果显示设置的颜色,就ok了。