Learn OpenGL Getting Started - jgoffeney/Cesium4Unreal GitHub Wiki

Back

OpenGL

The site is developed for the 3.3 version of OpenGL which represented a change in how OpenGL was used.

Core-profile vs. Immediate Mode

Immediate mode was the classic version of OpenGL in which important parts of the functionality / calculations were hidden in the library. After version 3.3 the API was changed to be more flexible and give developers more control at the cost of ease of use.

Hello Window

OpenGL requires a window to function. The GLFW library can be used for device independent window generation. The code below is to instantiate a basic window with an event loop. The glfwWindowHint function is used to pass parameters to GLFW and in the example tells GLFW to expect OpenGL 3.3 and to use the core profile which disables the deprecated immediate mode functions.

#include <GLFW/glfw3.h>

int main()
{
    glfwInit();
    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);  //Required for Mac

    GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
  
    // Run until a callback function tell it to quit
    while(!glfwWindowShouldClose(window))
    {
        glfwSwapBuffers(window);
        glfwPollEvents();    
    }

    glfwTerminate();
    return 0;
}

Viewport

The glViewport function is used to set the OpenGL rendering window (which can be smaller then the GLFW window). The parameters are used to map the device coordinates from (-1 to 1) to the window region.

xw = (xnd + 1)(width/2) + xleft

yw = (ynd + 1)(height/2) + ybottom

  • glViewport(int left, int bottom, int width, int height);
    • Parameters
      • left: the x coordinate for the lower left corner
      • bottom: the y coordinate for the lower left corner
      • width: the window width in pixels
      • height: the window height in pixels

Hello Triangle

This section describes the graphics pipeline which takes a set of 3D coordinates and transforms them into 2D colored pixels on the screen.

Vertex Input

The vertex inputs are defined as an array with each vertex being three consecutive elements (a triangle with three vertices would be represented by an array of 9 elements). The host array can be copied to the GPU via vertex buffer objects.

// Define the vertex array in world space coordinates
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};  

// Get a valid buffer name
unsigned int VBO;
glGenBuffers(1, &VBO);  

// Bind the buffer to the GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO); 

// Copy the vertex array to the GL_ARRAY_BUFFER as unchanging data
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 

Normalized Device Coordinates (NDC)

After vertex coordinates have been processed in the vertex shader the x, y and z coordinates should vary from -1.0 to 1.0. Any coordinates outside this range are clipped. The NDC coordinate origin is in the center of the region with y positive up.

The viewport transform then transforms the NDC coordinates to screen space coordinates / fragments as inputs to the fragment shader.

Vertex Shader

The vertex shader is the first programmable shader in the pipeline. The vertex shader is run on each input vertex. The example below is a passthrough shader as it does not alter the vertex data from input to output. The in keyword specifies an input while the layout(location = 0) indicates in the host code the input vertices should be set to the shader's first parameter location. The output is written to gl_Position which is a predefined variable of type vec4.

// User OpenGL version 3.3 core profile
#version 330 core

// Pass in the vertex 
layout (location = 0) in vec3 aPos;

void main()
{
    // Just set the output vertex value as the same as the input
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

The following code shows how to compile a shader on the host.

// The vertex shader source code
const char *vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";

// Create the shader id for the vertex shader
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

// Copy the shader source code to the GPU and then compile
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

Fragment Shader

The fragment shader provides color to the pixels and is run on each fragment generated after the vertex shader. A fragment shader is required to have a single output (as defined by the out keyword) of type vec4 as the RGBA color channels. The allowed color values are from 0.0 to 1.0.

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

The method for loading and compiling the fragment source is similar to the vertex shader source.

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

Shader Program

Shaders have to be linked into a shader program to be run. The following takes the vertex and fragment shaders defined above and links then within a program. Any draw functions called after the program is activated will be processed though the pipeline using the shaders.

// Creates the program
unsigned int shaderProgram;
shaderProgram = glCreateProgram();

// Attach and link the programs
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

//Activate the program
glUseProgram(shaderProgram);

// Do some rendering

// Clean up
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);  

Linking Vertex Attributes

As mentioned previously the vertex shader needs an array of vertices as an input.

  • void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)
    • Description
      • Tells OpenGL how to interpret the vertex buffer when a draw call is made. Notice that it allows partitioning of the vertices as either packed or with a stride but not referencing individual vertices which is covered by the Element Buffer Objects.
    • Parameters
      • index: the vertex shader location to assign the data
      • size: the number of components per vertex (1, 2, 3, or 4)
      • type: the enum of the component data type such as GL_BYTE, GL_UNSIGNED_BYTE, GL_FLOAT, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, and GL_UNSIGNED_INT.
      • normalized: use GL_TRUE to normalize the vertex data should be normalized
      • stride: the byte offset between vertices with 0 indicating tightly packed. An example of use would be when vertex coordinated and colors are stored in the same array.
      • pointer: the offset to the first vertex in the array
  • void glEnableVertexAttribArray(GLuint index)
    • Parameters
      • index: enables the vertex shader index
// Loads the vertex array 'vertices' to the GPU 
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Associate the currently bound VBO to vertex shader attribute 0 and tell OpenGL how to use it  
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);  
// Activate the shader program
glUseProgram(shaderProgram);
// Call a drawing function
someOpenGLFunctionThatDrawsOurTriangle();   

Vertex Array Object (VAO)

A VAO is used with VBO to contain pointers to vertices. The general idea is to have a single VBO to hold all the vertices for a scene but then use a VAO for each object in the scene. Without a VAO then you would have to call the above code for each object every time you want to draw it. NOTE: core OpenGL requires the use of VAOs.

The VAO name/id creation uses glGenVertexArrays and binding uses glBindVertexArray. Once a VAO is bound then the subsequent VBO operations are stored to it. So you can use glVertexAttribPointer to indicate a specific subset of the VBO which contains one object.

// Assumes a previously bounds VBO
// Create a new VAO during initialization
unsigned int VAO;
glGenVertexArrays(1, &VAO);  

// Bind the VAO for the subsequent VBO operations
glBindVertexArray(VAO);

// Bind the VBO and copy the vertex data to the GPU
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// Set the OpenGL configuration for reading/drawing the vertices 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);  

// Activate the shader program and the VAO for the object to draw
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();   

Element Buffer Objects (EBO)

An EBO is an array that contains the indices to vertices within a VBO via a VAO. This prevents the need to store redundant vertices within the VBO when triangles share common ones. The code snippet below shows an array of 4 vertices used to generate a square. As two triangles they would share two vertices along the diagonal. Rather than repeating these vertices the EBO contains the index values for each vertex needed for the triangles. In this example we save two vectors of float vertex coordinates but gain two vectors of integer vertex indices. More complex models will have more vertex sharing.

float vertices[] = {
     0.5f,  0.5f, 0.0f,  // top right
     0.5f, -0.5f, 0.0f,  // bottom right
    -0.5f, -0.5f, 0.0f,  // bottom left
    -0.5f,  0.5f, 0.0f   // top left 
};
unsigned int indices[] = {  // note that we start from 0!
    0, 1, 3,   // first triangle
    1, 2, 3    // second triangle
};  

Setting up an EBO is similar to other buffer objects in OpenGL but using the GL_ELEMENT_ARRAY_BUFFER binding. The code shows how to set up the EBO and use to draw.

// Create a new EBO 
unsigned int EBO;
glGenBuffers(1, &EBO);

// Bind and copy the indices to the GPU
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 

// Later when ready to draw bind the EBO and draw the triangles
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
  • void glDrawElements(GLenum mode, GLsizei count, GLenum type, const void * indices);
    • Description
      • Draws geometry primitives from a buffer
    • Parameters
      • mode: enum indicating the type of primitives to draw (GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_LINE_STRIP_ADJACENCY, GL_LINES_ADJACENCY, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_TRIANGLE_STRIP_ADJACENCY, GL_TRIANGLES_ADJACENCY or GL_PATCHES)
      • count: the total number of vertices to draw (number of triangles * 3)
      • type: the data type of the stored indices (GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT)
      • indices: the pointer to the offset of the vertex indices

The VAO automatically stores EBO information as well as VBO and the code below demonstrates the usage.

// Initialization
// Bind the VAO to manage all the subsequent calls
glBindVertexArray(VAO);

// Bind and copy the vertices to the VBO 
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// Bind and copy the indices to the EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// Set the attributes for the vertices
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);  

//Rendering 

// Enable the program and the vertex array object
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);
⚠️ **GitHub.com Fallback** ⚠️