前面几篇文章都只是绘制了平面图形,接下来我们开始绘制一个真正的3D立方体图形。代码在前一篇文章基础上修改。
绘制立方体之前,我们需要知道这个立方体的各个顶点坐标(找不到图,自己画的,请将就将就):
一个立方体有8个顶点,在这里是:
GLfloat vertices[] = {
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
};
它的索引是:
GLubyte indices[] = {
// Front face
0, 3, 2, 0, 2, 1,
// Back face
7, 5, 4, 7, 6, 5,
// Left face
0, 1, 6, 0, 6, 7,
// Right face
3, 4, 5, 3, 5, 2,
// Up face
1, 2, 5, 1, 5, 6,
// Down face
0, 7, 4, 0, 4, 3
};
所以我们修改render函数的代码如下:
-(void)render
{
//设置清屏颜色,默认是黑色,如果你的运行结果是黑色,问题就可能在这儿
glClearColor(0.3, 0.5, 0.8, 1.0);
/*
glClear指定清除的buffer
共可设置三个选项GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT
也可组合如:glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
这里我们只用了color buffer,所以只需清除GL_COLOR_BUFFER_BIT
*/
glClear(GL_COLOR_BUFFER_BIT);
// Setup viewport
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
GLfloat vertices[] = {
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
};
GLubyte indices[] = {
// Front face
0, 3, 2, 0, 2, 1,
// Back face
7, 5, 4, 7, 6, 5,
// Left face
0, 1, 6, 0, 6, 7,
// Right face
3, 4, 5, 3, 5, 2,
// Up face
1, 2, 5, 1, 5, 6,
// Down face
0, 7, 4, 0, 4, 3
};
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(_positionSlot);
glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, indices);
[_context presentRenderbuffer:_renderBuffer];
}
运行结果如下:
运行结果.png诶,我们不是画了个立方体吗?代码画的怎么跟我在纸上画的那个不一样呢?
事实上,我们代码画的确实是一个立方体,只是我们观察的角度是从正方体正面看过去的,立体的部分全被前面的面挡住了,只要我们旋转一下立方体,就能看到立体的部分了,不过这个问题会在下一篇讲到,这里先绘制立方体,只修改它的颜色。
而且,事实上我们画的是个正方体,这里展示是个长方体,这是由于没有进行宽高等比的投影矩阵处理,这里opengl坐标是按着屏幕来的,所以是个长方体。
图中立方体的颜色是我们在片元着色器脚本文件自己定义的固定颜色
precision mediump float;
void main()
{
gl_FragColor = vec4(0.9, 0.5, 0.7, 1.0);
}
如果我们想在外面的代码中动态修改它的颜色,我们需要定义一个变量来接收传入的颜色。
1.修改顶点着色器脚本文件:
attribute vec4 vPosition;
attribute vec4 vSourceColor; //新加
varying vec4 vDestinationColor; //新加
void main(void)
{
gl_Position = vPosition;
vDestinationColor = vSourceColor; //新加
}
2.修改片元着色器脚本文件
precision mediump float;
varying vec4 vDestinationColor; //新加
void main()
{
gl_FragColor = vDestinationColor; //修改
}
以上着色器代码在《OpenGLES-02 绘制基本图元(点、线、三角形)》中有介绍,详细了解glsl请点这里 (也希望你确实做过《拨开迷雾》里的准备工作,如果到这里你还有很多概念不理解,希望你停下来,先去查询资料,理解概念): http://www.cnblogs.com/kex1n/p/3941680.html
好啦,着色器语言已经写好了,接下来我们开始使用
3.代码绘制
我们在MyGLView中新定义一个变量:GLuint _colorSlot; //颜色槽位
@interface MyGLView ()
{
CAEAGLLayer *_eaglLayer; //OpenGL内容只会在此类layer上描绘
EAGLContext *_context; //OpenGL渲染上下文
GLuint _renderBuffer; //
GLuint _frameBuffer; //
GLuint _programHandle;
GLuint _positionSlot; //顶点槽位
GLuint _colorSlot; //颜色槽位
}
然后在setupProgram函数中获取这个_colorSlot:
_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
_colorSlot = glGetAttribLocation(_programHandle, "vSourceColor"); //新加
接下来我们需要构造自己的颜色数据,对于OpenGL,它支持两种着色模式:单调着色(Flat)与平滑着色(smooth,也称Gouraud着色)。单调着色就是整个图元的颜色就是它的任何一个顶点的颜色,比如上面固定颜色的三角形效果;平滑着色下每个顶点都是单独进行的,顶点之间的点是所有顶点颜色的均匀插值计算而得,顶点与顶点颜色是在一起的,如下:
GLfloat vertices[] = {
-0.5f, -0.5f, 0.5f, 1.0, 0.0, 0.0, 1.0, // red
-0.5f, 0.5f, 0.5f, 1.0, 1.0, 0.0, 1.0, // yellow
0.5f, 0.5f, 0.5f, 0.0, 0.0, 1.0, 1.0, // blue
0.5f, -0.5f, 0.5f, 1.0, 1.0, 1.0, 1.0, // white
0.5f, -0.5f, -0.5f, 1.0, 1.0, 0.0, 1.0, // yellow
0.5f, 0.5f, -0.5f, 1.0, 0.0, 0.0, 1.0, // red
-0.5f, 0.5f, -0.5f, 1.0, 1.0, 1.0, 1.0, // white
-0.5f, -0.5f, -0.5f, 0.0, 0.0, 1.0, 1.0, // blue
};
索引数据是不变的,然后我们需要使用颜色槽位数据,修改render函数如下:
-(void)render
{
//设置清屏颜色,默认是黑色,如果你的运行结果是黑色,问题就可能在这儿
glClearColor(0.3, 0.5, 0.8, 1.0);
/*
glClear指定清除的buffer
共可设置三个选项GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT
也可组合如:glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
这里我们只用了color buffer,所以只需清除GL_COLOR_BUFFER_BIT
*/
glClear(GL_COLOR_BUFFER_BIT);
// Setup viewport
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
GLfloat vertices[] = {
-0.5f, -0.5f, 0.5f, 1.0, 0.0, 0.0, 1.0, // red
-0.5f, 0.5f, 0.5f, 1.0, 1.0, 0.0, 1.0, // yellow
0.5f, 0.5f, 0.5f, 0.0, 0.0, 1.0, 1.0, // blue
0.5f, -0.5f, 0.5f, 1.0, 1.0, 1.0, 1.0, // white
0.5f, -0.5f, -0.5f, 1.0, 1.0, 0.0, 1.0, // yellow
0.5f, 0.5f, -0.5f, 1.0, 0.0, 0.0, 1.0, // red
-0.5f, 0.5f, -0.5f, 1.0, 1.0, 1.0, 1.0, // white
-0.5f, -0.5f, -0.5f, 0.0, 0.0, 1.0, 1.0, // blue
};
GLubyte indices[] = {
// Front face
0, 3, 2, 0, 2, 1,
// Back face
7, 5, 4, 7, 6, 5,
// Left face
0, 1, 6, 0, 6, 7,
// Right face
3, 4, 5, 3, 5, 2,
// Up face
1, 2, 5, 1, 5, 6,
// Down face
0, 7, 4, 0, 4, 3
};
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), vertices);
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), vertices+3);
glEnableVertexAttribArray(_positionSlot);
glEnableVertexAttribArray(_colorSlot);
glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, indices);
[_context presentRenderbuffer:_renderBuffer];
}
到这里就得回过来看glVertexAttribPointer()了,
void glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr);
参数 index :为顶点数据(如顶点,颜色,法线,纹理或点精灵大小)在着色器程序中的槽位;
参数 size :指定每一种数据的组成大小,比如顶点由 x, y, z 3个组成部分,纹理由 u, v 2个组成部分;
参数 type :表示每一个组成部分的数据格式;
参数 normalized : 表示当数据为法线数据时,是否需要将法线规范化为单位长度,对于其他顶点数据设置为 GL_FALSE 即可。如果法线向量已经为单位长度设置为 GL_FALSE 即可,这样可免去不必要的计算,提升效率;
stride : 表示上一个数据到下一个数据之间的间隔(同样是以字节为单位),OpenGL ES根据该间隔来从由多个顶点数据混合而成的数据块中跳跃地读取相应的顶点数据;
ptr :值得注意,这个参数是个多面手。这里它指向 CPU 内存中的顶点数据数组;
代码中我们给stride填值,以前都是写的0,现在是写出了具体步长,写0是针对单一数据,如只有顶点数据或颜色数据的时候,系统会自己计算匹配,这时候可以写0,若不是单一数据,如上有顶点有颜色,需要自己计算出具体步长。
再看后面ptr的填值,对颜色数据,我们给出的是vertices+3,表示颜色数据从vertices的第4位开始,往后4位是颜色数据(size为4)。
代码的运行结果如下:
颜色运行结果.png
结果显示了我们立方体正面的颜色,顶点用到了(0,1,2,3),所以颜色是红黄蓝白的线性插值。下一篇文章,我们将对这个立方体进行3D变换以看清它确实是个立方体。
所有教程代码在此 : https://github.com/qingmomo/iOS-OpenGLES-
网友评论