IOS OpenGLES2.0 绘制三角形需要以下步骤
1.初始化OpenGL相关的CAEAGLLayer和EAGLContext
2.创建OpenGL program + Vertex Shader + Fragment Shader
3.创建顶点数据, 并且传入OpenGL
4.告诉OpenGL如何使用这些顶点数据
5.绘制.
第一步配置, 请参考IOS OpenGLES2.0 入门01 清空屏幕
第二步, 关于OpenGL的Vertex Shader 和 Fragment Shader
Shader的创建和使用
如果创建一个Shader, 使用
glCreateShader (GLenum type);
type可以为GL_VERTEX_SHADER或GL_FRAGMENT_SHADER, 分别表示Vertex Shader和Fragment Shader
创建完Shader后, 使用
glShaderSource (GLuint shader, GLsizei count, const GLchar* const *string, const GLint* length);
给Shader”赋值”, 这里的”赋值”表示说, 将Shader源码给它
然后调用
glCompileShader (GLuint shader);
编译Shader
这样一个Shader就编译完成了, 至于要使用Shader的话, 必须依赖于program,
OpenGL创建Program, GLuint program = glCreateProgram();
然后使用glAttachShader (GLuint program, GLuint shader);将shader和program关联
然后调用glLinkProgram(GLuint program);链接program
最后调用glUseProgram(GLuint program);开始使用program
一个简单的Vertex Shader源码
attribute vec4 position;
void main(void) {
gl_Position = position;
}
一个简答的Fragment Shader源码
void main(void) {
gl_FragColor = vec4(1, 0, 0, 1);
}
glsl(OpenGL Shader Language)一般来说有四个关键字
1.const 作用跟c/c++差不多, 主要用来定义常量
2.attribute, 只能在Vertex Shader中使用, 由应用程序传入值给它
3.uniform, Vertex Shader和Fragment Shader都可以使用但是不能修改, 由应用程序传入
4.varying, 用于Vertex Shader传值给Fragment Shader, 应用程序无法访问
gl_Position表示输出的位置
gl_FragColor表示输出的颜色
Vertex Shader源码, 由应用程序传入一个vec4(长度为4的数组)类型的position, 然后不做任何操作就赋值给gl_Position
Fragment Shader源码, 将输出的颜色固定为vec4(1, 0, 0, 1), 为RGBA格式, 所以是红色透明度为1, 注意, 这边只是将Vertex的颜色置成红色, 其他点得颜色, 会变成是顶点之间颜色的插值, 如果(0, 1)这个点颜色是(1, 0, 0, 1), (1, 1)这个点的颜色是(1, 0, 1, 1), 那么(0.5, 1)这个点的颜色则是(1, 0, 0.5, 1), 因为这边的颜色固定是红色, 再怎么插值也都是红色
如何给Shader传值
首先要获取变量的”指针”, 不同类型通过不同函数获取
attribute
glGetAttribLocation (GLuint program, const GLchar* name);
uniform
glGetUniformLocation (GLuint program, const GLchar* name);
获取到”指针”后, 根据变量的类型, 通过glVertexAttribxxx()方法, 传递对应的参数
比如float+vec4类型的, glVertexAttrib4f (GLuint indx, GLfloat x, GLfloat y, GLfloat z, GLfloat w)
如果是attribute类型的话, 还需要调用glEnableVertexAttribArray来启用这个参数
第三步, 顶点数据
OpenGL绘制图形都是基于点, 线, 三角形, 这些形状都是由顶点Vectex组成的
OpenGL的绘图基本上都是由以下两个函数来完成的
glDrawArrays (GLenum mode, GLint first, GLsizei count);
glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
在使用glDrawArrays或glDrawElements之前, 我们必须先把数据 申请绑定到 OpenGL之中, 然后告诉OpenGL怎么使用这些数据, 最后才是画出来
使用glGenBuffers申请OpenGL的一个Buffer, 然后调用glBindBuffer将这个buffer与对应的类型关联起来, 最后调用glBufferData将数据传入OpenGL
如:
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
先申请一个buffer用_vertexBuffer记住, 然后将_vertexBuffer与GL_ARRAY_BUFFER关联, 最后将数据Vertices传入OpenGL
到这一步为止, 我们只是将数据与GL_ARRAY_BUFFER关联, 并且传入OpenGL.
但是OpenGL并不知道你用不用这些数据, 并不知道怎么使用这些数据
第四步, 告诉OpenGL如何使用这书数据
使用 glEnableVertexAttribArray(GLKVertexAttribPosition);
告诉OpenGL我要启用这个顶点数据
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr);
告诉OpenGL怎么使用这些顶点数据.
index就是数据的类型, 在这里就是GLKVertexAttribPosition顶点数据,
size是数据类型的大小, 默认为RGBA等同于xyzw, 大小为4,
type是数据的类型, 可以是GL_FLOAT, GL_FALSE等数据类型…
normalized是询问是否要将数据归一化, 就是说问你要不要把你传进去的数据范围变成[0, 1], 一般为GL_FALSE
stride是你一个数据的大小, 通常是sizeof(你的数据类型=type) * 数据大小=size
ptr是你的数据的指针偏移量, 一般如果数据是从头开始的, 默认就传入NULL, 有偏移就传入偏移的大小
第五步, 绘制
当你数据绑定完毕之后, 我们就可以调用
glDrawArrays (GLenum mode, GLint first, GLsizei count);
glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
来完成绘制了, 这时候是将数据绘制到FrameBuffer中, 如果要将数据显示出来, 还得在调用一次
[EAGLContext presentRenderbuffer:GL_RENDERBUFFER];
配置OpenGL
- (void) setupOpenGL {
_eaglLayer = (CAEAGLLayer *) self.layer;
_eaglLayer.opaque = YES;
_eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking: @YES,
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
_eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:_eaglContext];
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
[_eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
创建Program和Shader
- (GLuint) compileShaderSource : (NSString *) shaderSource shaderType : (GLenum) shaderType {
GLuint shader = glCreateShader(shaderType);
const char *source = shaderSource.UTF8String;
int length = (int)shaderSource.length;
glShaderSource(shader, 1, &source, &length);
glCompileShader(shader);
return shader;
}
- (void) createProgram {
//编译Vertex Shader和Fragment Shader
_vertexShader = [self compileShaderSource:vertexShaderSource shaderType:GL_VERTEX_SHADER];
_fragmentShader = [self compileShaderSource:fragmentShaderSource shaderType:GL_FRAGMENT_SHADER];
//创建program并与Vertex Shader和Fragment Shader关联
_program = glCreateProgram();
glAttachShader(_program, _vertexShader);
glAttachShader(_program, _fragmentShader);
//链接program
glLinkProgram(_program);
//开始使用program
glUseProgram(_program);
//获取position的"指针"
_positionPtr = glGetAttribLocation(_program, "position");
}
创建顶点数据
- (void) createVertexData {
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
}
告诉OpenGL如何使用
- (void) setupVertexData {
//启动顶点
glEnableVertexAttribArray(_positionPtr);
//告诉OpenGL如何使用这些数据
glVertexAttribPointer(_positionPtr, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, NULL);
}
绘制
- (void) render {
glDrawArrays(GL_TRIANGLES, 0, vertexCount);
[self.eaglContext presentRenderbuffer:GL_RENDERBUFFER];
}
最后在加入
- (void) didMoveToSuperview {
[super didMoveToSuperview];
[self setupOpenGL];
[EAGLContext setCurrentContext:self.eaglContext];
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
glClearColor(0, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
[self createProgram];
[self createVertexData];
[self setupVertexData];
[self render];
}
代码在这