Computer Graphics
  • 5CM507 Graphics 25-26
  • Module
  • Schedule
    • Term 1 Schedule
    • Term 2 Schedule
  • Assessments
    • Coursework 1 (CW1)
    • Coursework 2 (CW2)
  • Lectures A
    • L01 Overview of Computer Graphics
    • L02 Mathematics
  • Labs A
    • LabA0 Environment Setup
      • Using a Git Repository
      • Checking or Installing Software
      • Creating a Project Framework
    • LabA01 Drawing 2D Triangles
      • Early OpenGL
      • Modern OpenGL
    • LabA02 Load Meshes and Calculate Normals
      • How to Represent a Mesh
        • Mesh Data Structure in C++
        • Mesh Data Structure in WebGL
        • The OBJ Mesh Format
      • Normal Calculation
      • Calculating Normals in C++
    • LabA03 Transformations
    • LabA04 Scene Graph
    • LabA05 Projections
    • LabA06 Lighting
    • LabA07 Shaders
    • LabA08 Texture Mapping
  • Lectures B
  • Labs B
  • Resources
  • People
    • Module Leader
    • The Games Group@Derby
    • Senior External Consultants
Powered by GitBook
On this page
  • Objective
  • Prerequisites
  • OpenGL Evolution
  • Step 1: Setting Up the Environment
  • Step 2: Drawing a Single Triangle
  • Step 3: Drawing Multiple Raw Triangles
  • Step 4: Drawing a Triangle Mesh with Indices
  • Drawing Indexed Triangles with Vertex Colours
  • Adding Colour Attribute to Vertices
  • Setting Data Buffer Layout
  • Conclusion
  1. Labs A
  2. LabA01 Drawing 2D Triangles

Modern OpenGL

Drawing Triangles with OpenGL, GLEW, and GLFW

PreviousEarly OpenGLNextLabA02 Load Meshes and Calculate Normals

Last updated 3 months ago

Objective

This tutorial guides you through rendering triangles using OpenGL with GLEW and GLFW. We start by drawing a single triangle, then expand to a triangle soup, and finally construct a small indexed triangle mesh.

Prerequisites

  • C++ development environment (e.g., Visual Studio, Code::Blocks, or GCC)

  • OpenGL, GLEW, and GLFW installed

OpenGL Evolution

(1997) : addition of vertex arrays, which allows for the number of GL calls to specify a point, line or polygon, which could easily get into double figures, to drop to one (not including some setup calls).

(2003) : addition of vertex buffer objects, which builts on the vertex array specification and which allowed for data that didn't need to change to be kept in GPU memory.


Step 1: Setting Up the Environment

  1. Install GLFW and GLEW if not already installed.

  2. Create a new C++ project and set up OpenGL.

  3. Include necessary headers:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
  1. Initialize GLFW and create a window:

if (!glfwInit()) {
    std::cerr << "Failed to initialize GLFW" << std::endl;
    return -1;
}
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Triangle", nullptr, nullptr);
if (!window) {
    std::cerr << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);
  1. Initialize GLEW:

glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
    std::cerr << "Failed to initialize GLEW" << std::endl;
    return -1;
}

Step 2: Drawing a Single Triangle

  1. Define a triangle:

float vertices[] = {
    0.0f,  0.5f, 0.0f,
   -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f
};
  1. Create and bind a Vertex Buffer Object (VBO) and Vertex Array Object (VAO):

GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
  1. Create a simple inlined vertex and fragment shader.

const char* vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos, 1.0);\n"
    "}\0";

const char* fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "}\n\0";
  1. Inside the render loop:

glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);

Step 3: Drawing Multiple Raw Triangles

  1. Define multiple triangles:

float vertices[] = {
    -0.5f,  0.5f, 0.0f,
     0.0f, -0.5f, 0.0f,
     0.5f,  0.5f, 0.0f,

    -0.75f, -0.5f, 0.0f,
    -0.25f, -0.5f, 0.0f,
    -0.5f,   0.0f, 0.0f
};
  1. Adjust glDrawArrays to render multiple triangles:

glDrawArrays(GL_TRIANGLES, 0, 6);

Step 4: Drawing a Triangle Mesh with Indices

  1. Define vertices and indices:

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.5f,  0.5f, 0.0f,
    -0.5f,  0.5f, 0.0f
};

unsigned int indices[] = {
    0, 1, 2,
    2, 3, 0
};
  1. Create and bind an Element Buffer Object (EBO):

GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
  1. Modify the draw call:

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

glDrawArrays: raw triangles with a fixed indexing pattern, one of GL_TRIANGLES, GL_TRIANGLE_STRIP or GL_TRIANGLE_FAN.

glDrawElements: using index buffer. With glDrawElements you can specify a mixed indexing pattern within the single draw call. However, you have to generate, store and load that index buffer onto the GPU, which adds extra time and takes extra space.

Drawing Indexed Triangles with Vertex Colours

Adding Colour Attribute to Vertices

// defining vertices
float vertices[] = {
    // (x, y, z)   (R, G, B)
    0.0,  0.0, 0.0, 1.0, 0.0, 0.0,
    1.0,  0.0, 0.0, 0.0, 1.0, 0.0,
    1.0,  1.0, 0.0, 0.0, 0.0, 1.0,
    0.0,  1.0, 0.0, 1.0, 1.0, 0.0
};

Setting Data Buffer Layout

Now each vertex has its own colour attribute. We use glVertexAttribPointer to tell the shader program this data layout.

For glVertexAttribPointer, its second parameter specifies the size of the attribute, its 5th parameter specifies the stride (number of floats for each vertex), and its last parameter specifies the pointer to the first element.

void glVertexAttribPointer(GLuint index, GLint size, GLenum type, 
           GLboolean normalized, GLsizei stride, const void * offset);
/*          
index: attribute index, corresponds to location in the vertex shader, 
       e.g. vertex location normally comes with 0
size: number of components, e.g. 3 for a 3d vertex
type: data type, e.g. GL_FLOAT  
normalized: false
stride: size of each vertex data, in byte.
        e.g. for a 3d vertex with (r, g, b) value, is 6 * sizeof(float) 
offset: start offset of the attribute in each row, in bytes. 
        This is sometimes confusing.
*/

For our data, the stride is 6 * sizeof(float). The offset is 0 for vertex position attribute, 3 * sizeof(float) for the colour attribute . The following code sets the vertex location and vertex colour attributes using index retrieved from the vertex shader.

int posLoc = glGetAttribLocation(shaderProgram, "aPos");
glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(posLoc);
int posCol = glGetAttribLocation(shaderProgram, "aColour");
glVertexAttribPointer(posCol, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*) (3 * sizeof(float)));
glEnableVertexAttribArray(posCol);

The drawing code in the loop needs no change.

Conclusion

This tutorial demonstrated how to render a single triangle, multiple triangles, and an indexed triangle mesh using OpenGL with GLEW and GLFW. Next steps could involve adding colors, textures, or interactivity.

OpenGL 1.1
OpenGL 1.5