绘制立方体跟绘制矩形没什么区别,只是创建好6个面的顶点数组,然后遍历创建矩形就可以了。

创建顶点数组

立方体的顶点数组,一共有 6 * 4 = 24 个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const GLfloat cubeVertices[] = {

// Define the front face
-1.0, 1.0, 1.0, // top left
-1.0, -1.0, 1.0, // bottom left
1.0, -1.0, 1.0, // bottom right
1.0, 1.0, 1.0, // top right

// Top face
-1.0, 1.0, -1.0, // top left (at rear)
-1.0, 1.0, 1.0, // bottom left (at front)
1.0, 1.0, 1.0, // bottom right (at front)
1.0, 1.0, -1.0, // top right (at rear)

// Rear face
1.0, 1.0, -1.0, // top right (when viewed from front)
1.0, -1.0, -1.0, // bottom right
-1.0, -1.0, -1.0, // bottom left
-1.0, 1.0, -1.0, // top left

// bottom face
-1.0, -1.0, 1.0,
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,

// left face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
-1.0, -1.0, 1.0,
-1.0, -1.0, -1.0,

// right face
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0
};

至今为止,我还没搞懂顺时针和逆时针的顶点顺序到底区别在哪里。。。

绘制

接下来开始绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

glLoadIdentity();
//让视图可见
glTranslatef(0.0, 0.0, -6.0);

glRotatef(rate, 1.0, 1.0, 1.0);

glVertexPointer(3, GL_FLOAT, 0, cubeVertices);

glEnableClientState(GL_VERTEX_ARRAY);

for (int i = 0; i < 6; i ++) {
glColor4f(cubeColors[i*4], cubeColors[i*4 + 1], cubeColors[i*4 + 2], cubeColors[i*4 + 3]);
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}

glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);

[context presentRenderbuffer:GL_RENDERBUFFER];

可以看到我们使用一个 for 循环绘制矩形。这次我们可以看到 glDrawArrays() 的第二个参数的用法,这个参数代表了每次取 4 个顶点的时候,数据起点偏移量,第一次为 0 ,第二次为 4 ,以此类推。

创建纹理顶点数组

假如我们想再次绘制纹理到每个面上,不光将图片数据加载到 OpenGL ,我们还需要再创建一个符合六个面的纹理数组,绘制的时候,OpenGL 会去这个数组中查找各个面的对应顶点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//textures
const GLshort squareTextureCoords[] = {
// Front face
0, 1, // top left
0, 0, // bottom left
1, 0, // bottom right
1, 1, // top right

// Top face
0, 1, // top left
0, 0, // bottom left
1, 0, // bottom right
1, 1, // top right

// Rear face
0, 1, // top left
0, 0, // bottom left
1, 0, // bottom right
1, 1, // top right

// Bottom face
0, 1, // top left
0, 0, // bottom left
1, 0, // bottom right
1, 1, // top right

// Left face
0, 1, // top left
0, 0, // bottom left
1, 0, // bottom right
1, 1, // top right

// Right face
0, 1, // top left
0, 0, // bottom left
1, 0, // bottom right
1, 1, // top right
};



glTexCoordPointer(2, GL_SHORT, 0, squareTextureCoords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

for (int i = 0; i < 6; i ++) {
glColor4f(cubeColors[i*4], cubeColors[i*4 + 1], cubeColors[i*4 + 2], cubeColors[i*4 + 3]);
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}

cubeColors[] 是我绘制立方体的时候创建的一个颜色数组,可以忽略,也可以自己创建。

每个面使用不同纹理图片

假如我们想立方体的每个面都使用不同的纹理图片,我们肯定需要加载不同的纹理图片,首先,开辟内存的时候就得是 6 个:

1
glGenTextures(6, textures);

然后我们调用 6 次 loadTextures ,分别将图片名和加载位置传入进去,在 glBindTexture(GL_TEXTURE_2D,location) 时,分别传入 textures[0] ,textures[1] 等等。在我们画图的时候,分别再告诉 OpenGL ,我们将使用不同的纹理图片:

1
2
3
4
5
for (int i = 0; i < 6; i ++) {
glBindTexture(GL_TEXTURE_2D, textures[i]);
glColor4f(1.0, 1.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}

不使用 glLoadIdentity()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
glPushMatrix();
{
glTexCoordPointer(2, GL_SHORT, 0, squareTextureCoords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glTranslatef(2.0, 0.0, -8.0);

glRotatef(rate, 1.0, 1.0, 1.0);

glVertexPointer(3, GL_FLOAT, 0, cubeVertices);

glEnableClientState(GL_VERTEX_ARRAY);

for (int i = 0; i < 6; i ++) {
glColor4f(cubeColors[i*4], cubeColors[i*4 + 1], cubeColors[i*4 + 2], cubeColors[i*4 + 3]);
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}

}
glPopMatrix();

glPushMatrix() 与 glPopMatrix() 结对使用,可以让我们丢弃 glLoadIdentity()。
1)OpenGL中的modelview矩阵变换是一个马尔科夫过程:上一次的变换结果对本次变换有影响,上次modelview变换后物体在世界坐标系下的位置是本次modelview变换的起点。默认时本次变换和上次变换不独立。
2)OpenGL物体建模实际上是分两步走的。第一步,在世界坐标系的原点位置绘制出该物体;第二步,通过modelview变换矩阵对世界坐标系原点处的物体进行仿射变换,将该物体移动到世界坐标系的目标位置处。
3)将modelview变换放在glPushMatrix和glPopMatrix之间可以使本次变换和上次变换独立。
4)凡是使用glPushMatrix()和glPopMatrix()的程序一般可以判定是采用世界坐标系建模。既世界坐标系固定,modelview矩阵移动物体。

纹理的使用

我们可以通过将 4 张图片拼接到一起,不用加载多次,只需要加载一次纹理,然后使用的时候,通过纹理坐标来控制将纹理的哪一部分加载到屏幕上。