- - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
- ...
- // 使用fragment.glsl 和 vertex.glsl中的shader
- glUseProgram(self.shaderProgram);
- [self drawTriangle];
- }
- - (void)drawTriangle {
- static GLfloat triangleData[18] = {
- 0, 0.5f, 0, 1, 0, 0, // x, y, z, r, g, b,每一行存储一个点的信息,位置和颜色
- -0.5f, -0.5f, 0, 0, 1, 0,
- 0.5f, -0.5f, 0, 0, 0, 1,
- };
- // 启用Shader中的两个属性
- // attribute vec4 position;
- // attribute vec4 color;
- GLuint positionAttribLocation = glGetAttribLocation(self.shaderProgram, "position");
- glEnableVertexAttribArray(positionAttribLocation);
- GLuint colorAttribLocation = glGetAttribLocation(self.shaderProgram, "color");
- glEnableVertexAttribArray(colorAttribLocation);
- // 为shader中的position和color赋值
- // glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
- // indx: 上面Get到的Location
- // size: 有几个类型为type的数据,比如位置有x,y,z三个GLfloat元素,值就为3
- // type: 一般就是数组里元素数据的类型
- // normalized: 暂时用不上
- // stride: 每一个点包含几个byte,本例中就是6个GLfloat,x,y,z,r,g,b
- // ptr: 数据开始的指针,位置就是从头开始,颜色则跳过3个GLFloat的大小
- glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData);
- glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData + 3 * sizeof(GLfloat));
- glDrawArrays(GL_TRIANGLES, 0, 3);
- }
在上篇文章的基础上我们增加了
。效果如下
- - (void)drawTriangle
在解释代码之前我们先来了解一下 OpenGL 渲染的基本流程。
从图中可以看出,最开始的输入是顶点数据。比如三角形,就是三个点。每个顶点数据可以包含任意数量的信息,最基本的有位置,颜色。后面介绍贴图时还会包含 UV 信息。经过各种处理,最终放入 FrameBuffer,就是上一篇文章说的缓冲区。接下来我们按照这个流程解释绘制的代码。
- static GLfloat triangleData[18] = {
- 0, 0.5f, 0, 1, 0, 0, // x, y, z, r, g, b,每一行存储一个点的信息,位置和颜色
- -0.5f, -0.5f, 0, 0, 1, 0,
- 0.5f, -0.5f, 0, 0, 0, 1,
- };
我们要绘制的是一个三角形,三角形有 3 个点,每个点我希望包含位置信息和颜色信息,至于两点之间的颜色如何,我们不关心,OpenGL ES 会处理。综上,我们为每一个点分配 6 个 GLfloat 大小的空间,前三个存储位置 x,y,z,后三个存储颜色 r,g,b。三个点就是 18 个 GLfloat 的数组。
使用 GLfloat 而不是 float 是为了跨平台,保证不同平台的 GLfloat 占用的字节数都是一致的。从而规范化了传递给 Shader 的数据的格式和大小。
- attribute vec4 position;
- attribute vec4 color;
- varying vec4 fragColor;
- void
- main
- (void)
- {
- fragColor = color;
- gl_Position = position;
- }
这是本例使用的 Vertex Shader,他只做了两件事,将顶点数据里的颜色传递给了 Fragment Shader,将位置传递给了 OpenGL ES。更详细的解释会再下一篇介绍。
- glDrawArrays(GL_TRIANGLES, 0, 3);
这一步,以形状为单位汇总渲染指令,为下一步栅格化颜色插值做准备。本例中只绘制了三角形,还可以通过 glDrawArrays 绘制直线,点等。
这一步会栅格化绘制的形状。第一步我们说过只需传递顶点的颜色,两点中间的颜色 OpenGL 会帮我们处理。OpenGL 将会计算出每一个像素对应的属性,比如颜色,这些值都是根据顶点的属性值以及形状计算而来的。
这是例子渲染出来的三角形,由图可以看出,三角形内部的每个像素的颜色都是根据像素点与三个点的距离计算出来的。离红色点越近像素的红色成分越多。
经过栅格化之后,每一个像素都要经过 Fragment Shader 处理一遍。下面就是本例使用的 Fragment Shader。
- varying lowp vec4 fragColor;
- void
- main
- (void)
- {
- gl_FragColor = fragColor;
- }
本例使用的 Fragment Shader 什么也没做,就是把 OpenGL 计算出来的颜色直接递交回给 OpenGL。
这里主要处理 OpenGL 对像素的一些固定操作。比如深度测试,剪裁测试等。通过 OpenGL 的 API 进行配置。
最终写入 Framebuffer,交换缓冲区后显示在窗口上。
最后再介绍两个重要的点。
- static GLfloat triangleData[18] = {
- 0, 0.5f, 0, 1, 0, 0, // x, y, z, r, g, b,每一行存储一个点的信息,位置和颜色
- -0.5f, -0.5f, 0, 0, 1, 0,
- 0.5f, -0.5f, 0, 0, 0, 1,
- };
中间点是 0,0 点。x,y,z 的生长方向如图,Z 轴是由内向外。屏幕长宽都是 2。
上面解释步骤时,从第一步到第二步如何传递顶点数据没有介绍。它的代码实现如下。
- // 启用Shader中的两个属性
- // attribute vec4 position;
- // attribute vec4 color;
- GLuint positionAttribLocation = glGetAttribLocation(self.shaderProgram, "position");
- glEnableVertexAttribArray(positionAttribLocation);
- GLuint colorAttribLocation = glGetAttribLocation(self.shaderProgram, "color");
- glEnableVertexAttribArray(colorAttribLocation);
- // 为shader中的position和color赋值
- // glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
- // indx: 上面Get到的Location
- // size: 有几个类型为type的数据,比如位置有x,y,z三个GLfloat元素,值就为3
- // type: 一般就是数组里元素数据的类型
- // normalized: 暂时用不上
- // stride: 每一个点包含几个byte,本例中就是6个GLfloat,x,y,z,r,g,b
- // ptr: 数据开始的指针,位置就是从头开始,颜色则跳过3个GLFloat的大小
- glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char * ) triangleData);
- glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char * ) triangleData + 3 * sizeof(GLfloat));
首先通过
激活 shader 中的两个属性。
- glEnableVertexAttribArray
是为了获取 shader 中某个属性的位置。这是 shader 与 OpenGL 约定的数据互通方式
- glGetAttribLocation
- GLuint positionAttribLocation = glGetAttribLocation(self.shaderProgram, "position");
- glEnableVertexAttribArray(positionAttribLocation);
- GLuint colorAttribLocation = glGetAttribLocation(self.shaderProgram, "color");
- glEnableVertexAttribArray(colorAttribLocation);
Shader 中的属性
- attribute vec4 position;
- attribute vec4 color;
顶点数据只会传递给 Vertex Shader,所以不能把
写到 Fragment Shader 里,从上面的流程图也可以看出来。激活 Vertex Shader 中的属性后就可以传值给它了,下面是传值代码。
- attribute vec4 position;
- glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char * ) triangleData);
- glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char * ) triangleData + 3 * sizeof(GLfloat));
上面第一行代码就是告诉 Vertex Shader,向位置属性传递的数据大小是 3 个 GLfloat,每个顶点数据有 6 个 GLfloat,位置数据起始的指针是 (char *)triangleData。OpenGL 读取完第一个位置数据后,就会将指针增加 6 个 GLfloat 的大小,访问下一个顶点位置。颜色也是相同的道理。
本篇主要介绍了绘制三角形需要用到哪些 API 以及绘制流程。下一篇会重点介绍 Shader 的相关知识。
来源: https://juejin.im/post/5a314883f265da432a7b96d2