Introduction to OpenGL

Basic Steps to Render with OpenGL

  1. Create and set up a window
  1. Store data for primitives in GPU memory
  1. Create a shader program to tell OpenGL how to render our primitives
  1. In a while loop, draw each frame

Create and set up a window

GLFWwindow* window;
int  initWindow() {

	if(!glfwInit()) {
		std::cerr << "Failed to initialize GLFW\n";
		return -1;
	}

	glfwWindowHint(GLFW_SAMPLES, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // FOR MAC

	window = glfwCreateWindow(800, 400, "Title", NULL ,NULL);

	if(window == NULL) {
		fprintf(stderr, "Failed to open GLFW window.\n");
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	glewExperimental = true; // Needed for core profile

	if (glewInit() != GLEW_OK) {
		fprintf(stderr, "Failed to initialize GLEW\n");
		return -1;
	}

	glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);
	glEnable(GL_CULL_FACE);

	return 0;
}

Store data for primitives in GPU memory

We will draw a square using two triangles.

float vertices[] = {
	// First triangle 
	-0.5f, -0.5f, 0.0f, 
	0.5f, -0.5f, 0.0f, 
	0.5f, 0.5f, 0.0f, 

	// Second triangle 
	-0.5f, -0.5f, 0.0f, 
	0.5f, 0.5f, 0.0f, 
	-0.5f, 0.5f, 0.0f 
};

To store data in the GPU memory, we need:-

Vertex Buffer Object (VBO)

Vertex Array Object (VAO)

Create identifier for our VAO and VBO

GLuint VAO, VBO;

Create VAO and VBO

glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);

Send our data to the buffer

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

Tell openGL how to how to interpret our data

glVertexAttribPointer( 
	0, // Attribute index 
	3, // Number of components (x,y,z) 
	GL_FLOAT, // Type of data 
	GL_FALSE, // Not normalized 
	3 * sizeof(float), // Stride (distance between consecutive vertex positions) 
	(void*)0 // Offset (start at beginning of the array) 
); 
glEnableVertexAttribArray(0);

Unbind to avoid unintentional changes

glBindBuffer(GL_ARRAY_BUFFER, 0); 
glBindVertexArray(0);

Create a shader program to tell OpenGL how to render our primitives

We will use the given function that loads and compiles shader.

GLuint shaderProgram = LoadShaders( "vertexShader.glsl", "fragmentShader.glsl" );

Vertex shader

#version 330 core
layout(location = 0) in vec3 aPos;

void main() {
    gl_Position = vec4(aPos, 1.0);
}

Fragment shader

#version 330 core
out vec4 FragColor;

void main() {
    // White color for all fragments
    FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

In a while loop, draw each frame

while (!glfwWindowShouldClose(window)) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
    glfwSwapBuffers(window);
    
    glfwPollEvents();
}

glfwTerminate();


Multiple Buffers

Let’s add some color

float colors[] = {
    // First triangle (R, G, B)
    1.0f, 0.0f, 0.0f,  // Red
    0.0f, 1.0f, 0.0f,  // Green
    0.0f, 0.0f, 1.0f,  // Blue

    // Second triangle
    1.0f, 1.0f, 0.0f,  // Yellow
    0.0f, 1.0f, 1.0f,  // Cyan
    1.0f, 0.0f, 1.0f   // Magenta
};

We need to create multiple buffers for the VAO

We have an additional VBO

GLuint VAO, VBO_verts, VBO_colors;

Create our buffers

glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO_verts);
glGenBuffers(1, &VBO_colors);

Send data to our buffers

glBindVertexArray(VAO);

glBindBuffer(GL_ARRAY_BUFFER, VBO_verts);
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, VBO_colors);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),(void*)0); 
glEnableVertexAttribArray(1);

Use color in shader

Vertex shader

#version 330 core

layout(location = 0) in vec3 aPos;   // position
layout(location = 1) in vec3 aColor; // color

out vec3 ourColor; // pass to fragment shader

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
}

Fragment Shader

#version 330 core

in vec3 ourColor; // from vertex shader
out vec4 FragColor;

void main()
{
    FragColor = vec4(ourColor, 1.0);
}

Indexed Drawing

We are repeating vertices. For a large mesh, it would result in a lot of wasted memory.

float vertices[] = {
    // Bottom-left
    -0.5f, -0.5f, 0.0f,
    // Bottom-right
     0.5f, -0.5f, 0.0f,
    // Top-right
     0.5f,  0.5f, 0.0f,
    // Top-left
    -0.5f,  0.5f, 0.0f
};

float colors[] = {
    // Bottom-left (red)
    1.0f, 0.0f, 0.0f,
    // Bottom-right (green)
    0.0f, 1.0f, 0.0f,
    // Top-right (blue)
    0.0f, 0.0f, 1.0f,
    // Top-left (yellow)
    1.0f, 1.0f, 0.0f
};

unsigned int indices[] = {
    0, 1, 2,   // first triangle
    2, 3, 0    // second triangle
};

We create an additional buffer for indexes.

GLuint VAO, VBO_verts, VBO_colors, EBO;

glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO_verts);
glGenBuffers(1, &VBO_colors);
glGenBuffers(1, &EBO);

Send index data to our buffer.

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

Now to draw indexed data we use:-

 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);