您的位置:首页 > 移动开发 > IOS开发

iOS --- OpenGLES之简单的图形绘制

2016-01-03 19:22 519 查看
在上一篇博客 iOS — OpenGLES之着色器(shader)的编译、链接及使用 中,简要介绍着色器(shader)的编译、链接及使用。本文将在之前一系列OpenGLES相关博客的基础上,使用OpenGLES绘制基本的图形。

以下两个例子中,对于Shader的编译使用等过程基本一致。

绘制三角形

Shader脚本

Vertex Shader如下:

attribute vec4 Position; // variable passed into

void main(void) {
gl_Position = Position; // gl_Position is built-in pass-out variable. Must config for in vertex shader
}


Fragment Shader如下:

precision mediump float;

void main(void) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // must set gl_FragColor for fragment shader
}


在这里,我们要绘制一个红色的三角形,因此只需要传递Position参数即可。

颜色我们在Fragment Shader中指定即可 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

关联Shader参数

关于Shader的编译,链接及使用的更多内容, 请参考之前的博客:

iOS — OpenGLES之着色器[shader]的编译、链接及使用

_glProgram = [ShaderOperations compileShaders:@"DemoShaderVertex" shaderFragment:@"DemoShaderFragment"];

glUseProgram(_glProgram);
_positionSlot = glGetAttribLocation(_glProgram, "Position");

// 设置viewport
glViewport(0, 0, self.view.frame.size.width, self.view.frame.size.height);


以上代码使用已编译好的_glProgram着色器程序, 将Shader脚本的Position参数, 与_positionSlot插槽绑定起来.

则后续赋值给_positionSlot的顶点数据, 会直接传递至Shader中的Position参数.

这样, 就完成了Shader脚本的链接及使用步骤.

而glViewPort方法用于设置OpenGLES显示的区域.

仅使用顶点数据绘制三角形

// 设置顶点数组
const GLfloat vertices[] = {
0.0f,  0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f,  -0.5f, 0.0f };

// 给_positionSlot传递vertices数据
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(_positionSlot);

// Draw triangle
glDrawArrays(GL_TRIANGLES, 0, 3);

[_eaglContext presentRenderbuffer:GL_RENDERBUFFER];


绘制三角形的步骤也很简单:

1. 准备好顶点数据.

2. 将顶点数据传递给_positionSlot插槽, 即传递至Shader脚本中.

3. 使用glDrawArrays绘制图形, 使用presentRenderbuffer:将其呈现到屏幕上.

使用VBO绘制三角形

Vertex Buffer Object(VBO)是GPU存储空间里的一块缓冲区, 可用于存储顶点的所有信息. OpenGL在GPU中记录着VBO与对应的GPU内存地址.

如果不使用VBO, 则每次直接从CPU内存中传递顶点数据到GPU内存中进行运算和渲染.

使用VBO就可将顶点数据存于GPU内存中, 而不需要每次都在CPU和GPU之间进行传递, 效率大大提升.

VBO适用于频繁读取顶点数据的场景, 在这里绘制三角形仅需三个顶点, 且不会重复使用. 因此不是VBO的典型使用场景. 这里的目的仅为了简单介绍VBO.

const GLfloat vertices[] = {
0.0f,  0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f,  -0.5f, 0.0f };

GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
// 绑定vertexBuffer到GL_ARRAY_BUFFER目标
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
// 为VBO申请空间,初始化并传递数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 使用VBO时,最后一个参数0为要获取参数在GL_ARRAY_BUFFER中的偏移量
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(_positionSlot);

glDrawArrays(GL_TRIANGLES, 0, 3);


由上边代码可看出, VBO的使用方式比较固定.

一旦将VBO对象与GL_ARRAY_BUFFER绑定起来, 并将顶点数据传递过去, 则后续的顶点数据默认都从GL_ARRAY_BUFFER中取得.

glVertexAttribPointer方法的最后一个参数要注意:

当不使用VBO时, 要传递顶点数组的指针, 如

glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);


而当使用VBO时, 传递顶点数据在GL_ARRAY_BUFFER中的偏离量, 这里是0.

glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, 0);


结果如图:



绘制矩形

Shader脚本

Vertex Shader如下:

// variable pass into
attribute vec4 Position;    // position of vertex
attribute vec4 SourceColor; // color of vertex

// variable pass out into fragment shader
// varying means that calculate the color of every pixel between two vertex linearly(smoothly) according to the 2 vertex's color
varying vec4 DestinationColor;

void main(void) {
DestinationColor = SourceColor;
// gl_Position is built-in pass-out variable. Must config for in vertex shader
gl_Position = Position;
}


Vertex Shader接收如下两个参数:

Position: 直接赋值给gl_Position变量, 用于指定顶点坐标.

SourceColor: 传递给Fragment Shader中的DestinationColor参数, 指定顶点颜色.

Fragment Shader如下:

varying lowp vec4 DestinationColor;

void main(void) {
// must set gl_FragColor for fragment shader
gl_FragColor = DestinationColor;
}


将DestinationColor传递给gl_FragColor变量, 用于指定顶点的颜色.

我们即将绘制的矩形不再是纯色,而是通过shader传递颜色给每一个像素。所以SourceColor参数至关重要。

关联Shader参数

glUseProgram(_glProgram);
_positionSlot = glGetAttribLocation(_glProgram, "Position");
_colorSlot = glGetAttribLocation(_glProgram, "SourceColor");

glViewport(0, 0, self.view.frame.size.width, self.view.frame.size.height);


仅使用顶点数据绘制矩形

// 顶点数组
const GLfloat Vertices[] = {
-1,-1,0,// 左下,黑色
1,-1,0, // 右下,红色
-1,1,0, // 左上,蓝色

1,-1,0, // 右下,红色
-1,1,0, // 左上,蓝色
1,1,0,  // 右上,绿色
};

// 颜色数组
const GLfloat Colors[] = {
0,0,0,1, // 左下,黑色
1,0,0,1, // 右下,红色
0,0,1,1, // 左上,蓝色

1,0,0,1, // 右下,红色
0,0,1,1, // 左上,蓝色
0,1,0,1, // 右上,绿色
};

// 纯粹使用顶点的方式,颜色与顶点要一一对应。
// 在shader中DestinationColor为最终要传递给OpenGLES的颜色,要使用varying,即两个顶点之间颜色平滑渐变
// 若不使用varying,则完全花掉。

// 取出Vertices数组中的坐标点值,赋给_positionSlot
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, Vertices);
glEnableVertexAttribArray(_positionSlot);

// 取出Colors数组中的每个坐标点的颜色值,赋给_colorSlot
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, 0, Colors);
glEnableVertexAttribArray(_colorSlot);

// 以上两个slot分别于着色器脚本中的Positon,SourceColor两个参数

// 绘制两个三角形,不复用顶点,因此需要6个顶点坐标。
// V0-V1-V2, V3-V4-V5

/**
*  参数1:三角形组合方式
*  参数2:从顶点数组的哪个offset开始
*  参数3:顶点个数6个
*/
glDrawArrays(GL_TRIANGLES, 0, 6);


使用顶点数组绘制矩形时, Colors数组用于指定每个顶点的颜色, 与顶点数组Vertices中的顶点一一对应.

注意, 此时可留意Shader中的DestinationColor之前的varying关键字, 作用是使两个顶点之间的颜色平滑渐变.

关于三角形绘制方式, 还可以使用如下两种方式:

// 绘制两个三角形,复用两个顶点,因此只需要四个顶点坐标
// V0-V1-V2, V1-V2-V3
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// 绘制两个三角形,复用两个顶点,因此只需要四个顶点坐标
// V0-V1-V2, V0-V2-V3
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);


三种方式的绘制效果是一样的.

使用顶点索引数组

// 顶点数组
const GLfloat Vertices[] = {
-1,-1,0,// 左下,黑色
1,-1,0, // 右下,红色
-1,1,0, // 左上,蓝色
1,1,0,  // 右上,绿色
};

// 颜色数组
const GLfloat Colors[] = {
0,0,0,1, // 左下,黑色
1,0,0,1, // 右下,红色
0,0,1,1, // 左上,蓝色
0,1,0,1, // 右上,绿色
};

// 索引数组,指定好了绘制三角形的方式
// 与glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);一样。
const GLubyte Indices[] = {
0,1,2, // 三角形0
1,2,3  // 三角形1
};

// 取出Vertices数组中的坐标点值,赋给_positionSlot
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, Vertices);
glEnableVertexAttribArray(_positionSlot);

// 注意,未使用VBO时,glVertexAttribPointer的最后一个参数是指向对应数组的指针。
// 取出Colors数组中的每个坐标点的颜色值,赋给_colorSlot
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, 0, Colors);
glEnableVertexAttribArray(_colorSlot);

/**
*  参数1:三角形组合方式
*  参数2:索引数组中的元素个数,即6个元素,才能绘制矩形
*  参数3:索引数组中的元素类型
*  参数4:索引数组
*/
// 注意,未使用VBO时,glDrawElements的最后一个参数是指向对应索引数组的指针。
glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, Indices);

/**
*  结论:
*  不管使用哪种方式,顶点和颜色两个数组一定要一一对应。
*  glDrawArrays:
*  glDrawElements: 引入了索引,则很方便地实现顶点的复用。
*
*  在每个vertex上调用我们的vertex shader,以及每个像素调用fragment shader
*  相比glDrawArray, 使用顶点索引数组可减少存储和绘制重复顶点的资源消耗
*/
```
indices是顶点索引数组, 指定绘制图形时的顶点顺序.
使用glDrawElements即可按照顶点索引数组指定的顺序进行绘制.

### 使用VBO


// 定义一个Vertex结构, 其中包含了坐标和颜色

typedef struct {

float Position[3];

float Color[4];

} Vertex;

// 顶点数组

const Vertex Vertices[] = {

{{-1,-1,0}, {0,0,0,1}},// 左下,黑色

{{1,-1,0}, {1,0,0,1}}, // 右下,红色

{{-1,1,0}, {0,0,1,1}}, // 左上,蓝色

{{1,1,0}, {0,1,0,1}}, // 右上,绿色

};

// 索引数组

const GLubyte Indices[] = {

0,1,2, // 三角形0

1,2,3 // 三角形1

};

// setup VBOs

// GL_ARRAY_BUFFER用于顶点数组

GLuint vertexBuffer;

glGenBuffers(1, &vertexBuffer);

// 绑定vertexBuffer到GL_ARRAY_BUFFER,

glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);

// 给VBO传递数据

glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);

// GL_ELEMENT_ARRAY_BUFFER用于顶点数组对应的Indices,即索引数组

GLuint indexBuffer;

glGenBuffers(1, &indexBuffer);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);

// 注意,未使用VBO时,glVertexAttribPointer的最后一个参数是指向对应数组的指针。

// 但是,当使用VBO时,glVertexAttribPointer的最后一个参数是要获取的参数在GL_ARRAY_BUFFER(每一个Vertex)的偏移量

// 取出Vertex结构体的Position,赋给_positionSlot

glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);

glEnableVertexAttribArray(_positionSlot);

// 注意,未使用VBO时,glVertexAttribPointer的最后一个参数是指向对应数组的指针。

// 但是,当使用VBO时,glVertexAttribPointer的最后一个参数是要获取的参数在GL_ARRAY_BUFFER(每一个Vertex)的偏移量

// Vertex结构体,偏移3个float的位置,即是Color值

glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid )(sizeof(float) 3));

glEnableVertexAttribArray(_colorSlot);

// 使用glDrawArrays也可绘制,此时仅从GL_ARRAY_BUFFER中取出顶点数据,

// 而索引数组就可以不要了,即GL_ELEMENT_ARRAY_BUFFER实际上没有用到。

// glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// 而使用glDrawElements的方式:本身就用到了索引,即GL_ELEMENT_ARRAY_BUFFER。

// 所以,GL_ARRAY_BUFFER和GL_ELEMENT_ARRAY_BUFFER两个都需要。

/**

* 参数1:三角形组合方式

* 参数2:索引数组中的元素个数,即6个元素,才能绘制矩形

* 参数3:索引数组中的元素类型

* 参数4:索引数组在GL_ELEMENT_ARRAY_BUFFER(索引数组)中的偏移量

*/

// 注意,未使用VBO时,glDrawElements的最后一个参数是指向对应索引数组的指针。

// 但是,当使用VBO时,参数4表示索引数据在VBO(GL_ELEMENT_ARRAY_BUFFER)中的偏移量

glDrawElements(GL_TRIANGLE_STRIP, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0);

“`

其中,Vertices数组中包含了每个顶点的位置信息及颜色。Indices数组是绘制矩形过程中使用的顶点索引数组。

使用顶点索引数组结合glDrawElements(),可减少存储和绘制重复顶点的资源消耗。

使用VBO可大大提高顶点数据在内存中的传递效率.

结果如图:



Demo

请参考: Demo

参考资料

OpenGL Tutorial for iOS: OpenGL ES 2.0

iOS — OpenGLES之着色器(shader)的编译、链接及使用

OpenGL ES渲染管线与着色器

另外,绘制该矩形的时候,用到了Vertex Buffer Object(VBO),是用来提升OpenGLES绘制效率的,不是本文的重点所在,将在下一篇博客中再做介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息