在上一篇我们学习了如何搭建IOS下openGL的开发环境,接下来我们来学习如何绘制一个多边形。
在2.0之前,es的渲染采用的是固定管线,何为固定管线,就是一套固定的模板流程,局部坐标变换 -> 世界坐标变换 ->观察坐标变换->背面消除->光照->裁剪->投影->视口计算->光栅化,程序员只需要调用固定的api修改一些配置参数就可以完成整个渲染流程了。而到了2.0,固定管线改成了可编程管线,我们对整个渲染流程可以再编程,没有固定的api给你调用,一切都依靠shader来完成。那么什么是shader呢:
Shader分为顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)。其中Vertex Shader主要负责顶点的几何关系等的运算,Pixel Shader主要负责片源颜色等的计算。
我们使用的是openGLES2.0。因此必然会用到shader,shader使用OpenGL着色语言(GLSL―OpenGL Shading Language)编写,在这里就不仔细介绍。
首先,设置好缓存
GLuint _renderBuffer; GLuint _frameBuffer; glGenRenderbuffers(1, &_renderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); [_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable: (id<EAGLDrawable>)self.layer]; glGenFramebuffers(1, &_frameBuffer); glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
在设置缓存之前,最好先清理一次,避免重复设置
if (_frameBuffer) { glDeleteFramebuffers(1, &_frameBuffer); _frameBuffer = 0; } if (_renderBuffer) { glDeleteRenderbuffers(1, &_renderBuffer); _renderBuffer = 0; }
设置好缓存之后开始编译着色器,基本上编译着色器的步骤都是固定,图像处理,滤镜实现基本都是通过着色器来完成。以下是编译着色器的代码。
GLuint _program; GLunit _positionSlot; _program = [MTShaderOperations compileShaders:@"Vertex" shaderFragment:@"Fragment"]; glUseProgram(_program); _positionSlot = glGetAttribLocation(_program, "Position");
glUseProgram(_program);
//绑定着色器中的参数
_positionSlot = glGetAttribLocation(_program, "Position");
+ (GLuint)compileShaders:(NSString *)shaderVertex shaderFragment:(NSString *)shaderFragment { // 1 vertex和fragment两个shader都要编译 GLuint vertexShader = [MTShaderOperations compileShader:shaderVertex withType:GL_VERTEX_SHADER]; GLuint fragmentShader = [MTShaderOperations compileShader:shaderFragment withType:GL_FRAGMENT_SHADER]; // 2 连接vertex和fragment shader成一个完整的program GLuint _glProgram = glCreateProgram(); glAttachShader(_glProgram, vertexShader); glAttachShader(_glProgram, fragmentShader); // link program glLinkProgram(_glProgram); // 3 check link status GLint linkSuccess; glGetProgramiv(_glProgram, GL_LINK_STATUS, &linkSuccess); if (linkSuccess == GL_FALSE) { GLchar messages[256]; glGetProgramInfoLog(_glProgram, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } return _glProgram; }
+ (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType { // 1 查找shader文件 NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"glsl"]; NSError* error; NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error]; if (!shaderString) { NSLog(@"Error loading shader: %@", error.localizedDescription); exit(1); } // 2 创建一个代表shader的OpenGL对象, 指定vertex或fragment shader GLuint shaderHandle = glCreateShader(shaderType); // 3 获取shader的source const char* shaderStringUTF8 = [shaderString UTF8String]; int shaderStringLength = (int)[shaderString length]; glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength); // 4 编译shader glCompileShader(shaderHandle); // 5 查询shader对象的信息 GLint compileSuccess; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); if (compileSuccess == GL_FALSE) { GLchar messages[256]; glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } return shaderHandle; }
当然,要编译着色器,首先我们要写好着色器代码。绘制多边形的着色器代码十分简单,如果有不懂得童鞋可以参考OpenGL着色语言(GLSL―OpenGL Shading Language)
新建一个empty文件,然后将后缀改成glsh就可以开始写着色器代码了。
Vertex Shader代码如下:
attribute vec4 Position; void main(void) { gl_Position = Position; }
Fragment Shader代码如下
void main(void) { gl_FragColor = vec4(0,1,1, 1.0); }
编译好着色器之后,我们就可以开始绘制图形了,在这里要注意的是在openGLES中只能画三角形,所有的图形都是由三角形组成的。
还有,在定义顶点时要注意openGL中的坐标系不同于UIKit。
首先定义顶点数组
const GLfloat vertices[] = { -1, -1, 0, //左下 1, -1, 0, //右下 -1, 1, 0, //左上 1, 1, 0 }; //右上
绘制图形
//传入顶点参数
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(_positionSlot);
//绘制多边形
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
在上面我们画出了多边形,这个多边形是顶点数组按照顺序画出所有的三角形,在不少情况下,并不能符合我们的要求。我们可以试着将上面最后一句改成
glDrawArrays(GL_TRIANGLE_STRIP, 1, 4);
得到的图像如下
由于openGL只能画三角形,因此在顶点数组中,顶点按照怎样的顺序链接十分重要。因此在openGLES中提供了另一个方法。
//定义索引数组 const GLubyte indices[] = { 0,1,2, 1,2,3 };
//根据索引数组的顺序画出多边形
glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(indices[0]), GL_UNSIGNED_BYTE, indices);
这样我们就画出了一个四边形,其他多边形也是一样的步骤。
下一篇章我们会讲如何传入一个纹理,敬请期待。