Drawing a Rotating 3D Cube with Delta Time using SDL3, GLM, GLAD, and OpenGL 3.3 Отрисовка вращающегося 3D-куба с дельта-таймом с помощью SDL3, GLM, GLAD и OpenGL 3.3
A step-by-step guide to rendering a colored 3D rotating cube with frame-rate independent animation using SDL3, GLM 3D matrix transformations, depth testing, and the GLAD loader library on Windows and Ubuntu. Пошаговое руководство по отрисовке цветного вращающегося 3D-куба с анимацией, независимой от частоты кадров, с использованием SDL3, 3D-трансформаций GLM, теста глубины и библиотеки-загрузчика 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 3D cube project: Создайте рабочую папку для проектов и новую директорию для вашего проекта 3D-куба:
Windows (Command Prompt / PowerShell)
mkdir C:\projects\draw-cube-sdl3-glm-glad-opengl-desktop
cd C:\projects\draw-cube-sdl3-glm-glad-opengl-desktop
Ubuntu (Terminal)
mkdir -p ~/projects/draw-cube-sdl3-glm-glad-opengl-desktop
cd ~/projects/draw-cube-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
1. Depth Buffer: Enabled via
glEnable(GL_DEPTH_TEST) and cleared with GL_DEPTH_BUFFER_BIT so back faces don't overlap front faces.2. Delta Time: Uses
SDL_GetTicksNS() to compute smooth frame delta in seconds, making rotation speed consistent regardless of GPU framerate.
1. Буфер глубины: Включается с помощью glEnable(GL_DEPTH_TEST) и очищается вместе с GL_DEPTH_BUFFER_BIT, чтобы задние грани не перекрывали передние.2. Дельта-тайм: Использует
SDL_GetTicksNS() для расчёта плавного шага времени в секундах, делая скорость вращения постоянной независимо от FPS.
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_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_Window* window = SDL_CreateWindow("SDL3 GLM GLAD OpenGL 3.3 Rotating Cube (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
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
SDL_Log("Failed to initialize GLAD");
SDL_GL_DestroyContext(glContext);
SDL_DestroyWindow(window);
SDL_Quit();
return -1;
}
// Enable OpenGL Depth Testing
glEnable(GL_DEPTH_TEST);
GLuint shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);
// Cube Vertex Data (Positions + RGB Colors for 6 faces, 36 vertices)
float vertices[] = {
// Positions // Colors
// Front face (Reddish)
-0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
0.5f, 0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
0.5f, 0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
// Back face (Greenish)
-0.5f, -0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
-0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
0.5f, -0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
-0.5f, -0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
// Left face (Blueish)
-0.5f, 0.5f, 0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.2f, 0.2f, 1.0f,
// Right face (Yellowish)
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.2f,
0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.2f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.2f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.2f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.2f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.2f,
// Top face (Cyanish)
-0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.2f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.2f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.2f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 1.0f,
// Bottom face (Magentaish)
-0.5f, -0.5f, -0.5f, 1.0f, 0.2f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.2f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 1.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 1.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.2f, 1.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);
// 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);
GLint mvpLoc = glGetUniformLocation(shaderProgram, "uMVP");
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, 1.5f, 3.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
float rotationAngle = 0.0f;
Uint64 lastTicks = SDL_GetTicksNS();
bool running = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
running = false;
}
}
// Calculate Delta Time in seconds
Uint64 currentTicks = SDL_GetTicksNS();
float deltaTime = static_cast<float>(currentTicks - lastTicks) / 1e9f;
lastTicks = currentTicks;
// Rotate 60 degrees per second
rotationAngle += 60.0f * deltaTime;
if (rotationAngle >= 360.0f) {
rotationAngle -= 360.0f;
}
// Model matrix transformation
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(rotationAngle), glm::vec3(0.5f, 1.0f, 0.0f));
glm::mat4 mvp = projection * view * model;
// Clear Color and Depth Buffers
glClearColor(clearColorVec.r, clearColorVec.g, clearColorVec.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw Cube
glUseProgram(shaderProgram);
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvp));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
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 projection = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
float rotationAngle = 0.0f;
Uint64 lastTicks = 0;
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;
}
// Set callback rate hint before window creation
SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "60");
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable MULTISAMPLE
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); // Can be 2, 4, 8 or 16
// 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_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
AppState* state = new AppState();
*appstate = state;
state->window = SDL_CreateWindow("SDL3 GLM GLAD OpenGL 3.3 Rotating Cube (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);
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
SDL_Log("Failed to initialize GLAD");
return SDL_APP_FAILURE;
}
glEnable(GL_DEPTH_TEST);
state->shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);
float vertices[] = {
// Front face (Reddish)
-0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
0.5f, 0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
0.5f, 0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 0.2f,
// Back face (Greenish)
-0.5f, -0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
-0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
0.5f, -0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
-0.5f, -0.5f, -0.5f, 0.2f, 1.0f, 0.2f,
// Left face (Blueish)
-0.5f, 0.5f, 0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.2f, 0.2f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.2f, 0.2f, 1.0f,
// Right face (Yellowish)
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.2f,
0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.2f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.2f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.2f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.2f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.2f,
// Top face (Cyanish)
-0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.2f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.2f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.2f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.2f, 1.0f, 1.0f,
// Bottom face (Magentaish)
-0.5f, -0.5f, -0.5f, 1.0f, 0.2f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.2f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 1.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.2f, 1.0f,
-0.5f, -0.5f, -0.5f, 1.0f, 0.2f, 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);
state->projection = glm::perspective(glm::radians(45.0f), 800.0f / 450.0f, 0.1f, 100.0f);
state->view = glm::lookAt(glm::vec3(0.0f, 1.5f, 3.5f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
state->mvpLoc = glGetUniformLocation(state->shaderProgram, "uMVP");
state->lastTicks = SDL_GetTicksNS();
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);
Uint64 currentTicks = SDL_GetTicksNS();
float deltaTime = static_cast<float>(currentTicks - state->lastTicks) / 1e9f;
state->lastTicks = currentTicks;
state->rotationAngle += 60.0f * deltaTime;
if (state->rotationAngle >= 360.0f) {
state->rotationAngle -= 360.0f;
}
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(state->rotationAngle), glm::vec3(0.5f, 1.0f, 0.0f));
glm::mat4 mvp = state->projection * state->view * model;
glClearColor(state->clearColorVec.r, state->clearColorVec.g, state->clearColorVec.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(state->shaderProgram);
glUniformMatrix4fv(state->mvpLoc, 1, GL_FALSE, glm::value_ptr(mvp));
glBindVertexArray(state->VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
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-cube-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, a window will open displaying a smoothly rotating colored 3D cube rendered with OpenGL 3.3 depth testing and GLM perspective projection. После запуска приложения откроется окно с плавно вращающимся цветным 3D-кубом, отрисованным с помощью теста глубины OpenGL 3.3 и перспективной проекции GLM.
SDL3 GLM GLAD OpenGL 3.3 Rotating Cube (Classic)
or (if using the Callbacks API version): или (если используется версия с Callbacks API):
SDL3 GLM GLAD OpenGL 3.3 Rotating Cube (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 Поддержка криптовалютой