Drawing a Cube

Moving on from 2D to 3D in our drawing process, we arrive at the easiest geometric form one could draw with an extra dimension added: the humble cube. Because we are moving step by step in our learning process, we will be drawing a cube in the most inconvenient way possible: by creating the vertex array, triangle by triangle and passing it to the shader.

DrawCube from c2_1_DrawCube project

Our beautiful cube

Keeping the tone of the previous drawing examples we’ve presented in this series of tutorials, we will use a Cube class. It derives from the Model class and its primary functions are creation and drawing of the cube. The header and cpp file in their entirety are as follow:

//Cube.h
#pragma once

#include "Model.h"
#include<time.h>
#include<stdarg.h>
namespace Rendering
{
    namespace Models
    {
        class Cube : public Model
        {
        public:
            Cube();
            ~Cube();

            void Create();
            virtual void Draw(const glm::mat4& projection_matrix,
                              const glm::mat4& view_matrix)
                              override final;
            virtual void Update() override final;

        private:
            glm::vec3 rotation, rotation_speed;
            time_t timer;
        };
    }
}
//Cube.cpp
#include "Cube.h"
using namespace Rendering;
using namespace Models;

#define PI 3.14159265

Cube::Cube()
{
}

Cube::~Cube()
{
}

void Cube::Create()
{
    GLuint vao;
    GLuint vbo;

    time(&timer);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    std::vector<VertexFormat> vertices;
    //vertices for the front face of the cube
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, 1.0),
                                    glm::vec4( 0.0,  0.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, 1.0),
                                    glm::vec4(1.0,  0.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, 1.0),
                                    glm::vec4(1.0, 1.0, 1.0, 1.0)));

    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, 1.0),
                                    glm::vec4( 0.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, 1.0),
                                    glm::vec4(1.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, 1.0),
                                    glm::vec4( 0.0,  0.0, 1.0, 1.0)));

    //vertices for the right face of the cube
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, 1.0),
                                    glm::vec4(1.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, -1.0),
                                    glm::vec4(1.0, 1.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, -1.0),
                                    glm::vec4(1.0,  0.0 , 0.0, 1.0)));

    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, 1.0),
                                    glm::vec4(1.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, -1.0),
                                    glm::vec4(1.0,  0.0, 0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, 1.0),
                                    glm::vec4(1.0,  0.0, 1.0, 1.0)));

    //vertices for the back face of the cube
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, -1.0),
                                    glm::vec4( 0.0,  0.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, -1.0),
                                    glm::vec4(1.0,  0.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, -1.0),
                                    glm::vec4(1.0, 1.0,  0.0, 1.0)));

    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, -1.0),
                                    glm::vec4( 0.0,  0.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, -1.0),
                                    glm::vec4(1.0, 1.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, -1.0),
                                    glm::vec4( 0.0, 1.0,  0.0, 1.0)));

   //vertices for the left face of the cube
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, -1.0),
                                    glm::vec4( 0.0, 0.0, 0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, 1.0),
                                    glm::vec4( 0.0,  0.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, 1.0),
                                    glm::vec4( 0.0, 1.0, 1.0, 1.0)));

    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, -1.0),
                                    glm::vec4( 0.0,  0.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, 1.0),
                                    glm::vec4( 0.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, -1.0),
                                    glm::vec4(0.0, 1.0, 0.0, 1.0)));

   //vertices for the upper face of the cube
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, 1.0),
                                    glm::vec4(1.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, 1.0),
                                    glm::vec4( 0.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, -1.0),
                                    glm::vec4(1.0, 1.0,  0.0, 1.0)));

    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, 1.0),
                                    glm::vec4( 0.0, 1.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, 1.0, -1.0),
                                    glm::vec4(1.0, 1.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, 1.0, -1.0),
                                    glm::vec4( 0.0, 1.0, 0.0, 1.0)));

//vertices for the bottom face of the cube
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, -1.0),
                                    glm::vec4( 0.0,  0.0, 0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, -1.0),
                                    glm::vec4(1.0,  0.0,  0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, 1.0),
                                    glm::vec4( 0.0,  0.0, 1.0, 1.0)));

    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, -1.0),
                                    glm::vec4(1.0,  0.0, 0.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(-1.0, -1.0, 1.0),
                                    glm::vec4( 0.0,  0.0, 1.0, 1.0)));
    vertices.push_back(VertexFormat(glm::vec3(1.0, -1.0, 1.0),
                                    glm::vec4(1.0,  0.0, 1.0, 1.0)));

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
    glBufferData(GL_ARRAY_BUFFER, sizeof(VertexFormat) * 36, &vertices[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(VertexFormat), (void*)(offsetof(VertexFormat, VertexFormat::color)));
    glBindVertexArray(0);
    this->vao = vao;
    this->vbos.push_back(vbo);

    rotation_speed = glm::vec3(90.0, 90.0, 90.0);
    rotation = glm::vec3(0.0, 0.0, 0.0);

}

void Cube::Update()
{

}

void Cube::Draw(const glm::mat4& projection_matrix,
                const glm::mat4& view_matrix)
{
    rotation = 0.01f * rotation_speed + rotation;

    glm::vec3 rotation_sin = glm::vec3(rotation.x * PI / 180, rotation.y * PI / 180, rotation.z * PI / 180);

    glUseProgram(program);
    glUniform3f(glGetUniformLocation(program, "rotation"),
                rotation_sin.x,
                rotation_sin.y,
                rotation_sin.z);
    glUniformMatrix4fv(glGetUniformLocation(program, "view_matrix"), 1,
                     false, &view_matrix[0][0]);
    glUniformMatrix4fv(glGetUniformLocation(program, "projection_matrix"),                      1, false, &projection_matrix[0][0]);
    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, 0, 36);
}

In the header, we use the rotation vector to store the degrees by which the object is rotated on each axis. Rotation_speed is used to tweak the rotation speed on each axis.

In the cpp file, we bind the vertex buffer and fill it with all the triangles that a 2x2x2 cube is comprised of, as well as give each vertex its own unique color, depending on its position within the cube. In the Draw method, we modify the rotation array and pass it along with the view and projection matrices to the shader. I took the liberty of adding rotation on 2 axes and color each vertex in a different color so that we could see more of it, from all sides.

Regarding the values transmitted as uniform, these are values which will remain constant (or uniform) and can be accessed from any shader which makes up the program that these values are associated with. These values are usually constants (gravity, coefficients, etc.) and are extremely useful to operations within shaders.

Next up, we need to modify the vertex shader:

//It's actually glsl
#version 450 core
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec4 in_color;

uniform mat4 projection_matrix, view_matrix;
uniform vec3 rotation;

out vec4 color;

void main()
{

    color = in_color;
    mat4 rotate_x, rotate_y, rotate_z;
    
    rotate_x = mat4(1.0, 0.0, 0.0, 0.0,
                    0.0, cos(rotation.x), sin(rotation.x), 0.0,
                    0.0, -sin(rotation.x), cos(rotation.x), 0.0,
                    0.0, 0.0, 0.0, 1.0);

    rotate_y = mat4(cos(rotation.y), 0.0, -sin(rotation.y), 0.0,
                    0.0, 1.0, 0.0, 0.0,
                    sin(rotation.y), 0.0, cos(rotation.y), 0.0,
                    0.0, 0.0, 0.0, 1.0);

    rotate_z = mat4(cos(rotation.z), -sin(rotation.z), 0.0, 0.0,
                    sin(rotation.z), cos(rotation.z), 0.0, 0.0,
                    0.0, 0.0, 1.0, 0.0,
                    0.0, 0.0, 0.0, 1.0);

    gl_Position = projection_matrix * view_matrix *
                  rotate_y * rotate_x *rotate_z * vec4(in_position, 1);
    
}

In the shader, we calculate the rotation matrices around each axis, according to the angles received via the rotation attribute, after which we simply multiply all of our matrices to get the correct position of each vertex.

The next big change to the existing files is in the Scene_manager, where we calculate the view and projection matrices, to be passed on to the model.

#include "Scene_Manager.h"
using namespace Managers;

Scene_Manager::Scene_Manager()
{

    glEnable(GL_DEPTH_TEST);

    shader_manager = new Shader_Manager();
    shader_manager->CreateProgram("colorShader",
                                  "Shaders\\Vertex_Shader.glsl",
                                  "Shaders\\Fragment_Shader.glsl");

    view_matrix = glm::mat4(1.0f, 0.0f, 0.0f, 0.0f,
                              0.0f, 1.0f, 0.0f, 0.0f,
                            0.0f, 0.0f, -1.0f, 0.0f,
                            0.0f, 0.0f, 10.0f, 1.0f);

    models_manager = new Models_Manager();
}

Scene_Manager::~Scene_Manager()
{
    
    delete shader_manager;
    delete models_manager;
}

void Scene_Manager::NotifyBeginFrame()
{
    models_manager->Update();
}

void Scene_Manager::NotifyDisplayFrame()
{

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(0.0, 0.0, 0.0, 1.0);

    models_manager->Draw();
    models_manager->Draw(projection_matrix, view_matrix);
}

void Scene_Manager::NotifyEndFrame()
{

}

void Scene_Manager::NotifyReshape(int width, int height,
                                int previos_width, int previous_height)
{
    float ar = (float)glutGet(GLUT_WINDOW_WIDTH) /
               (float)glutGet(GLUT_WINDOW_HEIGHT);
    float angle = 45.0f, near1 = 0.1f, far1 = 2000.0f;
    
    projection_matrix[0][0] = 1.0f / (ar * tan(angle / 2.0f));
    projection_matrix[1][1] = 1.0f / tan(angle / 2.0f);
    projection_matrix[2][2] = (-near1 - far1) / (near1 - far1);
    projection_matrix[2][3] = 1.0f;
    projection_matrix[3][2] = 2.0f * near1 * far1 / (near1 - far1);
}

Other less significant changes have been made to the Models_manager class (include the Cube class in the header, small changes in a few methods) and to the classes that Cube derives from, by adding a new Draw method with the view and projection matrices as parameters. All of these can be downloaded from our Git repository. Next time we will see how to improve just a little bit our engine.

 


I'm an engineer, currently employed at a financial software company. My interests include gaming, LPing and, of course, reviewing, but also game dev and graphics. Also, in the past I've dabbled in amateur photography, reviewing movies and writing short stories and blog posts. I am also a huge Song of Ice and Fire fan, but that's beside the point. Youtube Channel, Deviantart , Google + , Twitter

blog comments powered by Disqus