Skip to content

OpenGL Programming Guide — Modern OpenGL 4.x

DodaTech Updated 2026-06-23 6 min read

In this tutorial, you'll learn about OpenGL Programming Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Modern OpenGL (3.3+) uses a programmable pipeline where the developer writes vertex and fragment shaders in GLSL, manages GPU memory through buffer objects, and controls rendering state through the core profile without deprecated fixed-function APIs.

What You'll Learn & Why It Matters

In this tutorial, you will learn modern OpenGL 4.x programming from the ground up. You will set up a window with GLFW, compile shaders, upload geometry with VAOs and VBOs, apply transformations, and build a textured 3D scene.

Real-world use: OpenGL remains the most portable graphics API, running on Windows, Linux, macOS, and Embedded Systems. Scientific visualization tools, CAD software, and many indie games use OpenGL for cross-platform 3D rendering.

Prerequisites

  • Rasterization Pipeline (previous)
  • C++ programming
  • Linear algebra basics

Learning Path

flowchart LR
  A[Rasterization Pipeline] --> B[OpenGL Programming Guide]
  B --> C[Shader Programming]
  B --> D[Lighting Models]
  B --> E[Texture Mapping]
  C --> F[Compute Shaders]
  B:::current

  classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px

Setting Up OpenGL

Modern OpenGL requires creating a rendering context with a windowing toolkit. GLFW is the most popular choice:

#include <GLFW/glfw3.h>
#include <iostream>

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

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(1024, 768, "OpenGL 4.6 Renderer", nullptr, nullptr);
    if (!window) {
        std::cerr << "Failed to create window" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);
    std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;
    std::cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;

    while (!glfwWindowShouldClose(window)) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearColor(0.1f, 0.1f, 0.2f, 1.0f);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}

Shader Compilation

GLSL shaders must be compiled and linked at runtime. A helper class simplifies this:

#include <string>
#include <fstream>
#include <sstream>

class Shader {
public:
    GLuint program;

    Shader(const char* vertexPath, const char* fragmentPath) {
        std::string vertexCode = readFile(vertexPath);
        std::string fragmentCode = readFile(fragmentPath);

        GLuint vs = compileShader(GL_VERTEX_SHADER, vertexCode);
        GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragmentCode);

        program = glCreateProgram();
        glAttachShader(program, vs);
        glAttachShader(program, fs);
        glLinkProgram(program);

        checkCompileErrors(program, "PROGRAM");

        glDeleteShader(vs);
        glDeleteShader(fs);
    }

    void use() { glUseProgram(program); }

    void setMat4(const std::string& name, const glm::mat4& mat) {
        glUniformMatrix4fv(glGetUniformLocation(program, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

private:
    std::string readFile(const char* path) {
        std::ifstream file(path);
        std::stringstream stream;
        stream << file.rdbuf();
        return stream.str();
    }

    GLuint compileShader(GLenum type, const std::string& source) {
        GLuint shader = glCreateShader(type);
        const char* src = source.c_str();
        glShaderSource(shader, 1, &src, nullptr);
        glCompileShader(shader);
        checkCompileErrors(shader, type == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT");
        return shader;
    }

    void checkCompileErrors(GLuint shader, const std::string& type) {
        GLint success;
        GLchar infoLog[1024];
        if (type != "PROGRAM") {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success) {
                glGetShaderInfoLog(shader, 1024, nullptr, infoLog);
                std::cerr << "Shader compile error (" << type << "): " << infoLog << std::endl;
            }
        } else {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success) {
                glGetProgramInfoLog(shader, 1024, nullptr, infoLog);
                std::cerr << "Program link error: " << infoLog << std::endl;
            }
        }
    }
};

Vertex Specification with VAOs

Vertex Array Objects (VAOs) store all vertex attribute configuration:

// vertex.glsl
#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 FragColor;
out vec2 TexCoord;

void main()
{
    FragColor = aColor;
    TexCoord = aTexCoord;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}
// fragment.glsl
#version 460 core
in vec3 FragColor;
in vec2 TexCoord;
out vec4 FinalColor;

uniform sampler2D tex;

void main()
{
    FinalColor = texture(tex, TexCoord) * vec4(FragColor, 1.0);
}
// Setting up VAO and VBO
float vertices[] = {
    // positions          // colors           // texcoords
    -0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f, 0.0f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  0.0f, 0.0f, 1.0f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  0.0f, 0.0f, 1.0f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f, 0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  1.0f, 0.0f, 0.0f,  0.0f, 0.0f
};

GLuint VAO, VBO;
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, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
flowchart TD
  A[Vertex Data] -->|glBufferData| B[VBO on GPU]
  B -->|glVertexAttribPointer| C[VAO State]
  C -->|glBindVertexArray| D[Rendering Loop]
  D -->|glUseProgram| E[Shader Program]
  E -->|glDrawArrays| F[Triangle on Screen]

Rendering a Cube

glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::lookAt(glm::vec3(3.0f, 2.0f, 3.0f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 proj = glm::perspective(glm::radians(45.0f), 1024.0f / 768.0f, 0.1f, 100.0f);

shader.use();
shader.setMat4("model", model);
shader.setMat4("view", view);
shader.setMat4("projection", proj);

glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);

Expected output: A rotating cube with per-vertex colors and a texture, rendered with proper perspective projection.

Common Errors & Mistakes

1. Invalid OpenGL Context

Mistake: Calling OpenGL functions before creating a context or after destroying the window.

Fix: Always ensure a current context exists: glfwMakeContextCurrent(window) before any GL call.

2. Core Profile Deprecated Functions

Mistake: Using glBegin/glEnd, glMatrixMode, or other deprecated immediate mode functions.

Fix: Use the core profile exclusively with VAOs, VBOs, and shaders. Immediate mode was removed in OpenGL 3.3+.

3. Forgetting to Bind VAO

Mistake: Calling glVertexAttribPointer without a bound VAO, causing the state to be lost.

Fix: Always bind a VAO before specifying vertex attributes. The VAO stores all vertex binding state.

4. Shader Compilation Errors

Mistake: Not checking shader compilation or program linking status, causing a black screen.

Fix: Always check GL_COMPILE_STATUS and GL_LINK_STATUS. Print the info log for debugging.

Practice Questions

Question 1

What is the purpose of a VAO in OpenGL?

Show answer A VAO (Vertex Array Object) stores all vertex attribute configurations (attribute location, format, stride, offset) in a single object. Switching VAOs instantly switches the entire vertex layout, reducing API calls.

Question 2

Why does modern OpenGL require shaders?

Show answer Modern OpenGL removed the fixed-function pipeline (built-in lighting, transformation). Shaders give developers full control over vertex processing and fragment coloring, enabling custom effects like PBR, tessellation, and compute-based rendering.

Question 3

What is the difference between GL_STATIC_DRAW and GL_DYNAMIC_DRAW?

Show answer GL_STATIC_DRAW tells the GPU the buffer data will not change, allowing placement in fast, read-only memory. GL_DYNAMIC_DRAW indicates frequent updates, keeping data in writable memory but potentially sacrificing some read performance.

Challenge

Extend the renderer to load and display a 3D model in OBJ format with textures. Implement a simple arcball camera controller for orbit interaction.

FAQ

Is OpenGL still relevant in 2026?

Yes, OpenGL remains widely used for CAD, scientific visualization, and cross-platform applications. While Vulkan and DirectX 12 dominate AAA games, OpenGL's simplicity makes it ideal for educational tools and Embedded Systems.

What is the difference between OpenGL and Vulkan?

OpenGL is a high-level state machine API with implicit synchronization. Vulkan is a low-level explicit API that gives the developer direct control over memory, synchronization, and command buffers, enabling higher performance at the cost of more code.

Can I use OpenGL on macOS?

Apple deprecated OpenGL in macOS 10.14 and Metal is preferred. However, OpenGL 4.1 is still available. For new cross-platform projects, consider Vulkan with MoltenVK or WebGPU.


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Author: DodaTech | Last updated: June 23, 2026

DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro