在上一篇博客 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绘制效率的,不是本文的重点所在,将在下一篇博客中再做介绍。