Hands-On Graphical Programming Tutorial in C++ From OpenGL to DirectX 12

Hands-On Graphical Programming Tutorial in C++ From OpenGL to DirectX 12


This tutorial walks you through modern graphical programming in C++, starting with a simple OpenGL triangle and progressing to advanced DirectX 12 Ultimate ray tracing. You’ll learn to set up tools, write shaders, and optimize rendering workflows. All code examples are designed for Windows 11 with up-to-date SDKs and frameworks.


Setting Up Your Development Environment

Tools Required:

 Visual Studio 2025 Community Edition (with C++ and Game Development workloads).          
 vcpkg (C++ package manager).          
 CMake (for cross-platform builds).          
 RenderDoc (GPU debugger).          

DirectX 12 SDK and Windows 11 SDK (included with Visual Studio).

Installation Steps:

1. Install Visual Studio 2025 and select "Desktop Development with C++" and "Game Development with C++" workloads.

2. Install vcpkg (follow [vcpkg.io](https://vcpkg.io)).

3. Install CMake from [cmake.org](https://cmake.org).

4. Download RenderDoc from [renderdoc.org](https://renderdoc.org).


Example 1: Your First OpenGL Triangle

Objective: Render a triangle using OpenGL 4.6.

Step 1: Set Up Dependencies

vcpkg install glfw3 glew glm  # Install GLFW, GLEW, and GLM via vcpkg          

Step 2: Create a CMake Project

- CMakeLists.txt:

cmake_minimum_required(VERSION 3.25)
project(Triangle)
find_package(glfw3 CONFIG REQUIRED)
find_package(GLEW REQUIRED)  # Use "GLEW"
add_executable(Triangle main.cpp)
target_link_libraries(Triangle PRIVATE glfw GLEW::GLEW)         

Step 3: Write the Code

- main.cpp:

#include <GL/glew.h>  

#include <GLFW/glfw3.h>  

int main() {  

  glfwInit();  

  GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Triangle", nullptr, nullptr);  

  glfwMakeContextCurrent(window);  

  glewInit();  

  while (!glfwWindowShouldClose(window)) {  

    glClear(GL_COLOR_BUFFER_BIT);  

    glBegin(GL_TRIANGLES);  

      glColor3f(1, 0, 0); glVertex2f(-0.5, -0.5);  

      glColor3f(0, 1, 0); glVertex2f(0.5, -0.5);  

      glColor3f(0, 0, 1); glVertex2f(0.0, 0.5);  

    glEnd();  

    glfwSwapBuffers(window);  

    glfwPollEvents();  

  }  

  glfwTerminate();  

  return 0;  

}          


Step 4: Build and Run

mkdir build && cd build  

cmake .. -DCMAKE_TOOLCHAIN_FILE=[path/to/vcpkg/scripts/buildsystems/vcpkg.cmake]  

cmake --build .  

./Triangle          



Debugging: Use RenderDoc to capture the frame and inspect the triangle.


Modern OpenGL with Shaders

Objective: Render a rotating 3D cube using vertex/fragment shaders.

Project Structure:

3dcube/
├── CMakeLists.txt
├── main.cpp
├── shaders/
│   ├── vertex.glsl
│   └── fragment.glsl        

1. CMakeLists.txt

cmake_minimum_required(VERSION 3.25)
project(RotatingCube)

# Find required packages
find_package(glfw3 CONFIG REQUIRED)
find_package(GLEW REQUIRED)
find_package(GLM REQUIRED)
find_package(glad CONFIG REQUIRED)

# Configure shaders to be copied to build directory
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vertex.glsl ${CMAKE_CURRENT_BINARY_DIR}/shaders/vertex.glsl COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fragment.glsl ${CMAKE_CURRENT_BINARY_DIR}/shaders/fragment.glsl COPYONLY)

# Create executable
add_executable(RotatingCube main.cpp)


# Link libraries
target_link_libraries(RotatingCube PRIVATE 
    glfw
    GLEW::GLEW
	glm::glm
	glad::glad
    )
	

# Copy shaders to build directory on Windows
if(WIN32)
    add_custom_command(TARGET RotatingCube POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_directory
        ${CMAKE_CURRENT_SOURCE_DIR}/shaders/
        <TARGET_FILE_DIR:RotatingCube>/3dcube/shaders/
    )
endif()        

NB : Change <TARGET_FILE_DIR:RotatingCube> with the path containing the project 3dcube

2. shaders/vertex.glsl

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

uniform mat4 modelViewProjection;

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

3. shaders/fragment.glsl

#version 460 core
out vec4 FragColor;

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

4. main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <fstream>
#include <sstream>

// Cube vertices
float vertices[] = {
    // Positions        
    -0.5f, -0.5f, -0.5f,
     0.5f, -0.5f, -0.5f,
     0.5f,  0.5f, -0.5f,
    -0.5f,  0.5f, -0.5f,
    
    -0.5f, -0.5f,  0.5f,
     0.5f, -0.5f,  0.5f,
     0.5f,  0.5f,  0.5f,
    -0.5f,  0.5f,  0.5f,
};

unsigned int indices[] = {
    0, 1, 2, 2, 3, 0,
    4, 5, 6, 6, 7, 4,
    0, 4, 7, 7, 3, 0,
    1, 5, 6, 6, 2, 1,
    3, 2, 6, 6, 7, 3,
    0, 1, 5, 5, 4, 0
};

GLuint loadShader(GLenum type, const char* path) {
    // Load shader code from file
    std::string code;
    std::ifstream file;
    file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    
    try {
        file.open(path);
        std::stringstream stream;
        stream << file.rdbuf();
        file.close();
        code = stream.str();
    } catch (...) {
        std::cerr << "Error reading shader file: " << path << std::endl;
        return 0;
    }

    // Compile shader
    const char* codePtr = code.c_str();
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &codePtr, NULL);
    glCompileShader(shader);

    // Check errors
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char infoLog[512];
        glGetShaderInfoLog(shader, 512, NULL, infoLog);
        std::cerr << "Shader compilation error (" << path << "):\n" << infoLog << std::endl;
        return 0;
    }
    return shader;
}

GLuint createShaderProgram(const char* vertexPath, const char* fragmentPath) {
    GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexPath);
    GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentPath);

    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);

    // Check linking errors
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        char infoLog[512];
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        std::cerr << "Shader program linking error:\n" << infoLog << std::endl;
        return 0;
    }

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    return program;
}

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

    // Configure GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Create window
    GLFWwindow* window = glfwCreateWindow(800, 600, "Rotating Cube", NULL, NULL);
    if (!window) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    // Initialize GLAD
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cerr << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // Create shader program
    GLuint shaderProgram = createShaderProgram("shaders/vertex.glsl", "shaders/fragment.glsl");
    if (!shaderProgram) return -1;

    // Set up buffers
    GLuint VAO, VBO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

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

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

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

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

    // Enable depth testing
    glEnable(GL_DEPTH_TEST);

    // Render loop
    while (!glfwWindowShouldClose(window)) {
        // Clear screen
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Create transformations
        glm::mat4 model = glm::rotate(glm::mat4(1.0f), (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));
        glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
        glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f/600.0f, 0.1f, 100.0f);
        glm::mat4 mvp = projection * view * model;

        // Use shader program
        glUseProgram(shaderProgram);
        GLint mvpLoc = glGetUniformLocation(shaderProgram, "modelViewProjection");
        glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvp));

        // Draw cube
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(indices[0]), GL_UNSIGNED_INT, 0);

        // Swap buffers and poll events
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // Cleanup
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glDeleteProgram(shaderProgram);

    glfwTerminate();
    return 0;
}        

To Build and Run:

Install dependencies using vcpkg:

vcpkg install glfw3:x64-windows glad:x64-windows glm:x64-windows
        

Configure with CMake:

cd 3dcube
mkdir build 
cd build
cmake -B build -DCMAKE_TOOLCHAIN_FILE=[path/to/vcpkg]/scripts/buildsystems/vcpkg.cmake
        

Build and run:

cd  build
cmake --build .  --config Release
xcopy ..\shaders   Release /s /e /h (copy project shaders to RotatingCube.exe folder)
.\build\Release\RotatingCube.exe        


RotatingCube

DirectX 12 Triangle

Objective: Render a triangle using DirectX 12 Ultimate.

Step 1: Create a DirectX 12 Project

- In Visual Studio, create a new "Windows Desktop Wizard" project with C++.

Step 2: Initialize DirectX 12

- Use D3D12CreateDevice, create a command queue, and set up a swap chain.

Sample Code Snippet:

  // Simplified setup (full example requires 200+ lines)  

ComPtr<ID3D12Device> device;  

D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_2, IID_PPV_ARGS(&device));  

// Create command queue, swap chain, and pipeline state...         

Step 3: Write HLSL Shaders

- shader.hlsl:

struct PSInput {  

  float4 position : SV_POSITION;  

  float4 color : COLOR;  

};  

PSInput VSMain(float3 position : POSITION) {  

  PSInput result;  

  result.position = float4(position, 1.0f);  

  result.color = float4(1.0f, 0.0f, 0.0f, 1.0f);  

  return result;  

}  

float4 PSMain(PSInput input) : SV_TARGET {  

  return input.color;  

}          

Debugging: Use PIX for Windows (included with the Windows SDK) to profile GPU workloads.

Advanced Example: Cross-Platform 3D with bgfx

Objective: Render a 3D model using bgfx, a cross-platform graphics library.

Step 1: Install bgfx

vcpkg install bgfx  # Installs bgfx and dependencies          

Step 2: Minimal bgfx Application

#include <bgfx/bgfx.h>  

#include <bx/bx.h>  

int main() {  

  bgfx::Init init;  

  init.platformData.nwh = glfwGetWin32Window(window); // Requires GLFW window  

  bgfx::init(init);  

  bgfx::setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x303030ff);  

  while (!glfwWindowShouldClose(window)) {  

    bgfx::frame();  

  }  

  bgfx::shutdown();  

  return 0;  

}          

Advanced Topic: Compute Shaders and Ray Tracing

Objective: Simulate particles using a compute shader and render with DirectX Raytracing (DXR).

DirectX 12 Ultimate Setup:

1. Enable DXR in your project settings.

2. Use DirectML for AI denoising (optional).

Sample Workflow:

- Write a compute shader to update particle positions.

- Use DXR to ray trace reflections.

Full Tutorial: Follow Microsoft’s [DirectX-Graphics-Samples](https://github.com/microsoft/DirectX-Graphics-Samples).

Tools to Test and Debug

1. RenderDoc: Inspect OpenGL/DirectX frames.

2. PIX for Windows: Profile DirectX 12 workloads.

3. NSight Graphics (NVIDIA) or Radeon GPU Profiler (AMD): Analyze GPU performance.

4. Visual Studio Graphics Debugger: Step through shaders.

Conclusion

By progressing from OpenGL basics to DirectX 12 ray tracing, you’ve gained hands-on experience with modern graphical programming. Experiment with cross-platform frameworks like bgfx, and explore AI-driven rendering techniques to stay ahead.

Next Steps:

- Explore Vulkan tutorials at [vulkan-tutorial.com].

- Clone full examples from [Microsoft’s Samples](https://github.com/microsoft/DirectX-Graphics-Samples).


Compile, tweak, and innovate—your journey into graphical engineering starts here!


#OpenGL #DirectX12

要查看或添加评论,请登录

Hani Fahmi的更多文章