Drawing a Triangle with SDL3, GLM, GLAD, and OpenGL 3.3 Отрисовка треугольника с помощью SDL3, GLM, GLAD и OpenGL 3.3
A step-by-step guide to rendering a colored 2D triangle using SDL3, GLM matrix transformations, and OpenGL 3.3 Core profile with the GLAD loader library on Windows and Ubuntu. Пошаговое руководство по отрисовке цветного 2D-треугольника с использованием SDL3, матричных трансформаций GLM и профиля OpenGL 3.3 Core с библиотекой-загрузчиком GLAD под Windows и Ubuntu.
- Installing GLAD OpenGL Loader Library Установка библиотеки-загрузчика OpenGL GLAD
- Installing GLM Library Установка библиотеки GLM
- Building SDL3 Static Library for Windows (MinGW & CMake) Сборка статической библиотеки SDL3 для Windows (MinGW и CMake)
- Building SDL3 Static Library for Ubuntu (GCC, CMake & Ninja) Сборка статической библиотеки SDL3 для Ubuntu (GCC, CMake и Ninja)
Step 1: Create Project Directory Шаг 1: Создание директории проекта
Create a workspace folder and a new directory for your GLAD-enabled triangle project: Создайте рабочую папку для проектов и новую директорию для вашего проекта треугольника с GLAD:
Windows (Command Prompt / PowerShell)
mkdir C:\projects\draw-triangle-sdl3-glm-glad-opengl-desktop
cd C:\projects\draw-triangle-sdl3-glm-glad-opengl-desktop
Ubuntu (Terminal)
mkdir -p ~/projects/draw-triangle-sdl3-glm-glad-opengl-desktop
cd ~/projects/draw-triangle-sdl3-glm-glad-opengl-desktop
Step 2: Create Source Files (main.cpp & CMakeLists.txt) Шаг 2: Создание исходных файлов (main.cpp и CMakeLists.txt)
Create these files using Notepad / Sublime Text on Windows, or nano in Ubuntu terminal:
Создайте эти файлы через Блокнот / Sublime Text в Windows или с помощью nano в терминале Ubuntu:
Windows (Terminal / Notepad)
notepad main.cpp
notepad CMakeLists.txt
Ubuntu (Terminal / Nano)
nano main.cpp
nano CMakeLists.txt
By incorporating GLAD, we no longer need manually written function pointers or macros. Simply include
<glad/glad.h> before SDL headers, and call gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) right after context creation.
Благодаря интеграции GLAD нам больше не требуются ручные объявление указателей на функции или макросы. Достаточно подключить <glad/glad.h> до заголовочных файлов SDL и вызвать gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) сразу после создания контекста.
1A. File Contents for main.cpp (Classic Loop) 1A. Содержимое файла main.cpp (Классический цикл)
#include <glad/glad.h> // Must be included before SDL headers
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Vertex Shader source code
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
uniform mat4 uMVP;
void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
ourColor = aColor;
}
)";
// Fragment Shader source code
const char* fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main() {
FragColor = vec4(ourColor, 1.0);
}
)";
GLuint createShaderProgram(const char* vertexSrc, const char* fragmentSrc) {
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexSrc, nullptr);
glCompileShader(vertexShader);
GLint success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
SDL_Log("Vertex Shader Compilation Failed: %s", infoLog);
}
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentSrc, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
SDL_Log("Fragment Shader Compilation Failed: %s", infoLog);
}
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
SDL_Log("Shader Program Link Failed: %s", infoLog);
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
int main(int argc, char* argv[]) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_Log("SDL Initialization failed: %s", SDL_GetError());
return -1;
}
// Configure OpenGL 3.3 Core Profile attributes
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_Window* window = SDL_CreateWindow("SDL3 GLM GLAD OpenGL 3.3 Triangle (Classic)", 800, 450, SDL_WINDOW_OPENGL);
if (!window) {
SDL_Log("Window creation failed: %s", SDL_GetError());
SDL_Quit();
return -1;
}
SDL_GLContext glContext = SDL_GL_CreateContext(window);
if (!glContext) {
SDL_Log("OpenGL context creation failed: %s", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return -1;
}
SDL_GL_MakeCurrent(window, glContext);
SDL_GL_SetSwapInterval(1); // Enable VSync
// Initialize GLAD function pointers using SDL_GL_GetProcAddress
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
SDL_Log("Failed to initialize GLAD");
SDL_GL_DestroyContext(glContext);
SDL_DestroyWindow(window);
SDL_Quit();
return -1;
}
GLuint shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);
// Vertex data: Position (X, Y, Z) and Color (R, G, B)
float vertices[] = {
// Positions // Colors
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // Top (Red)
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // Bottom Left (Green)
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f // Bottom Right (Blue)
};
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);
// Position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// Setup GLM Matrices
glm::vec3 clearColorVec(0.1f, 0.12f, 0.15f);
glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f / 450.0f, 0.1f, 100.0f);
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 mvp = projection * view * model;
GLint mvpLoc = glGetUniformLocation(shaderProgram, "uMVP");
bool running = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
running = false;
}
}
// Clear screen
glClearColor(clearColorVec.r, clearColorVec.g, clearColorVec.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Draw Triangle
glUseProgram(shaderProgram);
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvp));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
SDL_GL_SwapWindow(window);
}
// Cleanup resources
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
SDL_GL_DestroyContext(glContext);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
1B. File Contents for main.cpp (SDL3 Callbacks API) 1B. Содержимое файла main.cpp (SDL3 Callbacks API)
#define SDL_MAIN_USE_CALLBACKS 1
#include <glad/glad.h> // Must be included before SDL headers
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Vertex Shader source code
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
uniform mat4 uMVP;
void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
ourColor = aColor;
}
)";
// Fragment Shader source code
const char* fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main() {
FragColor = vec4(ourColor, 1.0);
}
)";
GLuint createShaderProgram(const char* vertexSrc, const char* fragmentSrc) {
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexSrc, nullptr);
glCompileShader(vertexShader);
GLint success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
SDL_Log("Vertex Shader Compilation Failed: %s", infoLog);
}
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentSrc, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
SDL_Log("Fragment Shader Compilation Failed: %s", infoLog);
}
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
SDL_Log("Shader Program Link Failed: %s", infoLog);
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
struct AppState {
SDL_Window* window = nullptr;
SDL_GLContext glContext = nullptr;
GLuint shaderProgram = 0;
GLuint VAO = 0;
GLuint VBO = 0;
GLint mvpLoc = -1;
glm::mat4 mvp = glm::mat4(1.0f);
glm::vec3 clearColorVec = glm::vec3(0.1f, 0.12f, 0.15f);
};
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_Log("SDL Initialization failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
AppState* state = new AppState();
*appstate = state;
state->window = SDL_CreateWindow("SDL3 GLM GLAD OpenGL 3.3 Triangle (Callbacks)", 800, 450, SDL_WINDOW_OPENGL);
if (!state->window) {
SDL_Log("Window creation failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
state->glContext = SDL_GL_CreateContext(state->window);
if (!state->glContext) {
SDL_Log("OpenGL context creation failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_GL_MakeCurrent(state->window, state->glContext);
SDL_GL_SetSwapInterval(1);
// Initialize GLAD
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
SDL_Log("Failed to initialize GLAD");
return SDL_APP_FAILURE;
}
state->shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);
float vertices[] = {
// Positions // Colors
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f
};
glGenVertexArrays(1, &state->VAO);
glGenBuffers(1, &state->VBO);
glBindVertexArray(state->VAO);
glBindBuffer(GL_ARRAY_BUFFER, state->VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f / 450.0f, 0.1f, 100.0f);
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 model = glm::mat4(1.0f);
state->mvp = projection * view * model;
state->mvpLoc = glGetUniformLocation(state->shaderProgram, "uMVP");
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS;
}
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppIterate(void* appstate) {
auto* state = static_cast<AppState*>(appstate);
glClearColor(state->clearColorVec.r, state->clearColorVec.g, state->clearColorVec.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(state->shaderProgram);
glUniformMatrix4fv(state->mvpLoc, 1, GL_FALSE, glm::value_ptr(state->mvp));
glBindVertexArray(state->VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
SDL_GL_SwapWindow(state->window);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
if (appstate) {
auto* state = static_cast<AppState*>(appstate);
if (state->VAO) glDeleteVertexArrays(1, &state->VAO);
if (state->VBO) glDeleteBuffers(1, &state->VBO);
if (state->shaderProgram) glDeleteProgram(state->shaderProgram);
if (state->glContext) {
SDL_GL_DestroyContext(state->glContext);
}
if (state->window) {
SDL_DestroyWindow(state->window);
}
delete state;
}
SDL_Quit();
}
2. File Contents for CMakeLists.txt 2. Содержимое файла CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
# Default to Debug if CMAKE_BUILD_TYPE is not set via command line
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
project(draw-triangle-sdl3-glm-glad-opengl-desktop CXX C)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# -----------------------------------------------------------------------------
# Locate System OpenGL Package
# -----------------------------------------------------------------------------
find_package(OpenGL REQUIRED)
# -----------------------------------------------------------------------------
# Locate SDL3 Package via Custom Paths
# -----------------------------------------------------------------------------
if(WIN32)
set(SDL3_DIR "C:/libs/sdl3-3.4.16-mingw/lib/cmake/SDL3")
else()
set(SDL3_DIR "$ENV{HOME}/libs/sdl3-3.4.16-ubuntu/lib/cmake/SDL3")
endif()
find_package(SDL3 REQUIRED CONFIG)
# -----------------------------------------------------------------------------
# Set Include Path for Header-Only GLM Library
# -----------------------------------------------------------------------------
if(WIN32)
set(GLM_INCLUDE_DIR "C:/libs/glm-1.0.3/glm")
else()
set(GLM_INCLUDE_DIR "$ENV{HOME}/libs/glm-1.0.3")
endif()
# -----------------------------------------------------------------------------
# Set Path and Source for GLAD Loader
# -----------------------------------------------------------------------------
if(WIN32)
set(GLAD_DIR "C:/libs/glad-0.1.36/opengl-3.3")
else()
set(GLAD_DIR "$ENV{HOME}/libs/glad-0.1.36/opengl-3.3")
endif()
set(GLAD_INCLUDE_DIR "${GLAD_DIR}/include")
set(GLAD_SOURCE "${GLAD_DIR}/src/glad.c")
# -----------------------------------------------------------------------------
# Target Definition
# -----------------------------------------------------------------------------
set(PROJECT_SOURCES
main.cpp
${GLAD_SOURCE}
)
add_executable(app ${PROJECT_SOURCES})
# Include GLM and GLAD headers
target_include_directories(app PRIVATE
${GLM_INCLUDE_DIR}
${GLAD_INCLUDE_DIR}
)
# Link SDL3 statically and system OpenGL library
target_link_libraries(app PRIVATE SDL3::SDL3-static OpenGL::GL)
# -----------------------------------------------------------------------------
# Platform-Specific Compiler & Linker Flags
# -----------------------------------------------------------------------------
if(WIN32)
target_compile_options(app PRIVATE -Os)
target_link_options(app PRIVATE -static -s -static-libgcc)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_link_options(app PRIVATE -mconsole)
else()
target_link_options(app PRIVATE -mwindows)
endif()
endif()
Step 3: Create Build & Execution Scripts Шаг 3: Создание скриптов сборки и запуска
Windows (MinGW / Batch Scripts)
config-exe.bat
cmake -G Ninja -S . -B dist/win
build-exe.bat
cmake --build dist/win
run-exe.bat
@echo off
.\dist\win\app.exe
Run the scripts sequentially in Command Prompt or PowerShell: Последовательно запустите скрипты в командной строке или PowerShell:
config-exe.bat
build-exe.bat
run-exe.bat
Ubuntu (Bash Scripts)
config-bin.sh
#!/usr/bin/env bash
set -e
cmake -G Ninja -S . -B dist/linux
build-bin.sh
#!/usr/bin/env bash
set -e
cmake --build dist/linux
run-bin.sh
#!/usr/bin/env bash
set -e
./dist/linux/app
Grant execute permissions on Ubuntu before running: Предоставьте права на выполнение в Ubuntu перед запуском:
chmod +x config-bin.sh build-bin.sh run-bin.sh
./config-bin.sh
./build-bin.sh
./run-bin.sh
Step 4: Expected Output Шаг 4: Ожидаемый результат
Upon running the application, GLAD initializes OpenGL 3.3 Core procedures and draws a smooth RGB-interpolated triangle: После запуска приложения GLAD инициализирует процедуры OpenGL 3.3 Core и отрисует треугольник с плавным RGB-градиентом:
SDL3 GLM GLAD OpenGL 3.3 Triangle (Classic)
or (if using the Callbacks API version): или (если используется версия с Callbacks API):
SDL3 GLM GLAD OpenGL 3.3 Triangle (Callbacks)
Support My Work Поддержать проект
If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.
Sberbank (Russia only) Сбербанк (только для РФ)
Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)
USDT TRC20
Support via Cryptocurrency Поддержка криптовалютой