3D Voxel Game Using Allegro/OpenGL; GL_QUADS is slow

Hey! I'm working on a 3D Voxel Game using Allegro 5 and OpenGL. I've run into a problem... As you may have guessed, I'm going to be drawing a lot of cubes. GL_QUADS, however, seems to not be able to get the job done without lagging awfully. When I try to draw more than 128x128x64 cubes, the lag starts picking up. It is definitely not my hardware. I've heard that drawing cubes with GL_QUADS is not the best route. However, even drawing one face as a triangle still results in awful lag.

The 3D array is stored like this:
 
int voxelMap[256][256][256][4];

The map is 256x256x256, and I have 4 separate values for R, G, B(all blocks are color based, not texture based) and an int that is 1 or 0 determining if the block is to be collided with/drawn or not.

I draw each cube like this in a for loop:

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
void drawCube(int x, int y, int z, float r, float g, float b) {
    glBegin(GL_QUADS);
        glColor3f(r, g, b);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE);

        glColor3f(r / 2, g / 2, b / 2);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE);

        glColor3f(r * .75, g * .75, b * .75);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE);

        glColor3f(r * .75, g * .75, b * .75);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE);

        glColor3f(r * .75, g * .75, b * .75);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);

        glColor3f(r * .75, g * .75, b * .75);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
        glVertex3f(x * CUBE_SIZE + CUBE_SIZE, y * CUBE_SIZE + CUBE_SIZE, z * CUBE_SIZE + CUBE_SIZE);
    glEnd();     
}


I've heard about some OpenGL functions that draw arrays and such, but I'm not sure how to get them working with a situation like this where each block must have an R, G, B, and Collision integer value.

Would someone be willing to help me out with optimizing this? I am a noob at OpenGL, for the record.

Thanks in advance!
-Exiled

http://i.imgur.com/NtefJj4.png
Here is a picture of what I'm trying to do. This is drawing 64x64x32 cubes, which is lag free. However, at any more than 128x128x128, it would be considered unplayable.
Last edited on
Anyone? :|
Look into Opengl VBOs (Vertex Buffer Objects) or Opengl Vertex Arrays.
Actually, Vertex Arrays are deprecated IIRC. Your best bet is to go with a VBO or multiple VBO depending on your judgement.

I will post some base code which you can implement later on.
I'll also look for some resources/tutorials.
(will edit this I guess)
Thanks man! :) I'll check it out once you post it. Thanks in advance for the code base, it will definitely help!
I like what you're going for.. I have some OGL experience, nothing big or worth bragging about. But yeah your main/only bet will be vertex buffer objects.

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
// Copy vertices and normals to the memory of the GPU
void Model::CreateVBO()
{
	assert(m_model != NULL);
	
	// Calculate the number of faces we have in total
	GetFaces();
	
	// Allocate memory for our vertices and normals
	vertices = new Lib3dsVector[m_TotalFaces * 3];
	normals = new Lib3dsVector[m_TotalFaces * 3];
	
	// Loop through all the meshes
	for(mesh = m_model->meshes;mesh != NULL;mesh = mesh->next)
	{
		lib3ds_mesh_calculate_normals(mesh, &normals[FinishedFaces*3]);
		// Loop through every face
		for(unsigned int cur_face = 0; cur_face < mesh->faces;cur_face++)
		{
			face = &mesh->faceL[cur_face];
			for(unsigned int i = 0;i < 3;i++)
			{
				memcpy(&vertices[FinishedFaces*3 + i], mesh->pointL[face->points[i]].pos, sizeof(Lib3dsVector));
			}
			FinishedFaces++;
		}
	}
	
	// Generate a Vertex Buffer Object and store it with our vertices
	glGenBuffers(1, &m_VertexVBO);
	glBindBuffer(GL_ARRAY_BUFFER, m_VertexVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Lib3dsVector) * 3 * m_TotalFaces, vertices, GL_STATIC_DRAW);
	
	// Generate another Vertex Buffer Object and store the normals in it
	glGenBuffers(1, &m_NormalVBO);
	glBindBuffer(GL_ARRAY_BUFFER, m_NormalVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Lib3dsVector) * 3 * m_TotalFaces, normals, GL_STATIC_DRAW);
	
	// Clean up our allocated memory
	delete vertices;
	delete normals;
	
	// We no longer need lib3ds
	lib3ds_file_free(m_model);
	m_model = NULL;

	std::cout << "Successfully created VBO!" << std::endl;
}


This is the code I use to work with 3DS models (not advised). Specifically about the part of the 'glGenBuffers()' statements is what you want to look at.
@Kronus:
line 40 and 41 should be both be using delete []
closed account (o1vk4iN6)
 
int voxelMap[256][256][256][4];


Every dimension you add to an array is another multiplication you have to do to access the actual value.

Not to mention the fixed pipeline for opengl has been removed since OpenGL 3.0 i think and OpenGL ES 2.0. If you don't know what the fixed pipeline is, it is basically exactly what you are using now.
Thanks for the replies. I've looked into the VBO's... Any ideas on how I should connect it up with my existing setup if possible?
@cire Correct =)

@Exiled Here's my other model loader.

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include "Model.h"

bool Mesh::LoadMesh(const std::string& Filename) {
    // Release the previously loaded mesh (if it exists)
    Clear();

    bool Ret = false;
    Assimp::Importer Importer;

    const aiScene* pScene = Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);

    if (pScene) {
        Ret = InitFromScene(pScene, Filename);
    }
    else {
        std::cerr<< "Error parsing '" << Filename.c_str() <<"': '" << Importer.GetErrorString() <<"'\n");
    }

    return Ret;
}

bool Mesh::InitFromScene(const aiScene* pScene, const std::string& Filename) {
    m_Entries.resize(pScene->mNumMeshes);
    m_Textures.resize(pScene->mNumMaterials);

    // Initialize the meshes in the scene one by one
    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        const aiMesh* paiMesh = pScene->mMeshes[i];
        InitMesh(i, paiMesh);
    }

    return InitMaterials(pScene, Filename);
}

void Mesh::InitMesh(unsigned int Index, const aiMesh* paiMesh) {
    m_Entries[Index].MaterialIndex = paiMesh->mMaterialIndex;

    std::vector Vertices;
    std::vector Indices;

	const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

    for (unsigned int i = 0 ; i < paiMesh->mNumVertices ; i++) {
        const aiVector3D* pPos = &(paiMesh->mVertices[i]);
        const aiVector3D* pNormal = &(paiMesh->mNormals[i]) : &Zero3D;
        const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;

        Vertex v(Vector3f(pPos->x, pPos->y, pPos->z),
                Vector2f(pTexCoord->x, pTexCoord->y),
                Vector3f(pNormal->x, pNormal->y, pNormal->z));

        Vertices.push_back(v);
    }

	for (unsigned int i = 0 ; i < paiMesh->mNumFaces ; i++) {
        const aiFace& Face = paiMesh->mFaces[i];
        assert(Face.mNumIndices == 3);
        Indices.push_back(Face.mIndices[0]);
        Indices.push_back(Face.mIndices[1]);
        Indices.push_back(Face.mIndices[2]);
    }

	m_Entries[Index].Init(Vertices, Indices);
}

bool Mesh::InitMaterials(const aiScene* pScene, const std::string& Filename) {
    for (unsigned int i = 0 ; i < pScene->mNumMaterials ; i++) {
        const aiMaterial* pMaterial = pScene->mMaterials[i];
		
		m_Textures[i] = NULL;
        if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
            aiString Path;

            if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
                std::string FullPath = Dir + "/" + Path.data;
                m_Textures[i] = new Texture(GL_TEXTURE_2D, FullPath.c_str());

                if (!m_Textures[i]->Load()) {
                    std::cerr<< "Error loading texture '" << FullPath.c_str()<< "'\n"<< std::endl;
                    delete m_Textures[i];
                    m_Textures[i] = NULL;
                    Ret = false;
                }
            }
        }

		if (!m_Textures[i]) {
          m_Textures[i] = new Texture(GL_TEXTURE_2D, "./white.png");
          Ret = m_Textures[i]->Load();
       }
    }

    return Ret;
}

void Mesh::Render() {
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        glBindBuffer(GL_ARRAY_BUFFER, m_Entries[i].VB);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Entries[i].IB);

        const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;

        if (MaterialIndex < m_Textures.size() && m_Textures[MaterialIndex]) {
            m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
        }

        glDrawElements(GL_TRIANGLES, m_Entries[i].NumIndices, GL_UNSIGNED_INT, 0);
    }

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
}
I'm a bit confused on how these work...

Would I need to change my system of blocks? How does OpenGL know which values X, Y, and Z are(and other values like color)? This is all very new to me, sorry I'm being a noob haha :S
Hate to be that one guy, but bump...

:/
Sorry to be a noob, but I really need some help understanding this stuff guys. :/
Topic archived. No new replies allowed.