Что такое OpenGL: конвейер, шейдеры, версии и отличия от Vulkan

Что такое OpenGL: конвейер, шейдеры, версии и отличия от Vulkan Полезное

OpenGL (Open Graphics Library) — это открытая кроссплатформенная спецификация программного интерфейса (API), через который программа просит видеокарту нарисовать 2D- и 3D-графику. Сам по себе OpenGL — не библиотека, которую скачивают, а документ-стандарт: его ведет консорциум Khronos Group, а реализуют производители GPU в своих драйверах. Последняя версия — OpenGL 4.6 (2017).

Ниже — конвейер и шейдеры, программа с треугольником, версии и сравнение с Vulkan, DirectX 12 и Metal.

Мини-словарь: что именно называют «OpenGL»

Словом «OpenGL» называют четыре разные вещи:

Термин Что это Пример
Спецификация Документ: какие функции есть и как работают OpenGL 4.6 Core Profile
Реализация Код в драйвере видеокарты, который исполняет вызовы драйвер NVIDIA, AMD, Intel, Mesa в Linux
Контекст Объект со всем состоянием OpenGL, привязанный к окну или буферу создается через SDL, GLFW или системные WGL/GLX/EGL
GLSL Отдельный язык шейдеров — программ, которые исполняет GPU #version 330 core

Поэтому «установить OpenGL» обычно значит обновить драйвер, а версия зависит от драйвера и GPU. Лицензии GNU у OpenGL нет: это открытый стандарт, реализации лицензируются по-разному (драйверы вендоров закрытые, Mesa — MIT-подобная).

Графический конвейер: от вершин к пикселям

Видеокарта рисует треугольники (плюс точки и линии). Путь данных через GPU называют графическим конвейером (pipeline). Упрощенная схема:

  1. Данные вершин. Координаты, цвета, нормали лежат в буфере на GPU (VBO), их формат описывает VAO.
  2. Вершинный шейдер для каждой вершины применяет матрицы модели, камеры и проекции и выдает координаты в пространстве отсечения (clip space). В пиксели их переводит фиксированная стадия после отсечения (деление на w, viewport).
  3. Тесселяция и геометрический шейдер (необязательно) — дробят или порождают примитивы.
  4. Сборка примитивов и отсечение всего, что вне поля зрения.
  5. Растеризация. Треугольник превращается в набор фрагментов — кандидатов в пиксели.
  6. Фрагментный шейдер вычисляет цвет каждого фрагмента: текстуры, освещение.
  7. Тесты и смешивание. Тест глубины отбрасывает скрытые фрагменты, blending дает прозрачность, результат идет в буфер кадра.

Схема упрощена (ранний тест глубины может идти до фрагментного шейдера), но контракт для программиста такой. Стадии 2, 3, 6 программируются шейдерами, остальные настраиваются вызовами вроде glEnable(GL_DEPTH_TEST). Вне конвейера с версии 4.3 есть вычислительные (compute) шейдеры.

Старый и современный OpenGL

Многие уроки до сих пор показывают glBegin/glEnd, glVertex3f и glLight. Это фиксированный конвейер (fixed function) OpenGL 1.x-2.x: программа лишь включала режимы, GPU делал остальное по жестким правилам.

В OpenGL 3.0 (2008) эти функции объявили устаревшими, а с 3.2 (2009) появились два профиля:

  • Core profile — только современный API: буферы, VAO, шейдеры.
  • Compatibility profile — старое и новое вместе; на macOS его нет выше версии 2.1.

Для учебы берите core profile. GLU, GLUT и GLAUX — тоже наследие: сегодня окно создают через GLFW или SDL, функции загружают glad или GLEW, матрицы берут из GLM.

Минимальная программа: треугольник на шейдерах

Полный пример на C++ с SDL2: контекст OpenGL 3.3 core, два шейдера, оранжевый треугольник. Чтобы проверить результат без глаз, программа читает цвет пикселей из буфера кадра.

// triangle.cpp - минимальная программа OpenGL 3.3 core: окно, шейдеры, треугольник
#include <SDL.h>
#ifdef __APPLE__
  #define GL_SILENCE_DEPRECATION
  #include <OpenGL/gl3.h>
#else
  #define GL_GLEXT_PROTOTYPES
  #include <GL/gl.h>
  #include <GL/glext.h>
#endif
#include <cstdio>

// Вершинный шейдер: для каждой вершины
static const char* kVertexSrc = R"(#version 330 core
layout(location = 0) in vec2 aPos;
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
)";

// Фрагментный шейдер: для каждого фрагмента
static const char* kFragmentSrc = R"(#version 330 core
out vec4 FragColor;
void main() { FragColor = vec4(1.0, 0.5, 0.0, 1.0); }
)";

static GLuint compile(GLenum type, const char* src) {
    GLuint s = glCreateShader(type);
    glShaderSource(s, 1, &src, nullptr);
    glCompileShader(s);
    GLint ok = 0;
    glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
    if (!ok) {
        char log[1024];
        glGetShaderInfoLog(s, sizeof log, nullptr, log);
        std::fprintf(stderr, "shader error:\n%s\n", log);
    }
    return s;
}

int main() {
    SDL_Init(SDL_INIT_VIDEO);
    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_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
    SDL_Window* win = SDL_CreateWindow("OpenGL", SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED, 400, 400, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
    SDL_GLContext ctx = win ? SDL_GL_CreateContext(win) : nullptr;
    if (!ctx) { std::fprintf(stderr, "no GL context: %s\n", SDL_GetError()); return 1; }

    std::printf("GL_VERSION:  %s\n", glGetString(GL_VERSION));
    std::printf("GLSL:        %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));

    // 1. Данные: три вершины треугольника в координатах [-1; 1]
    float verts[] = { -0.5f, -0.5f,   0.5f, -0.5f,   0.0f, 0.5f };
    GLuint vao, vbo;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof verts, verts, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
    glEnableVertexAttribArray(0);

    // 2. Программа из двух шейдеров
    GLuint prog = glCreateProgram();
    glAttachShader(prog, compile(GL_VERTEX_SHADER, kVertexSrc));
    glAttachShader(prog, compile(GL_FRAGMENT_SHADER, kFragmentSrc));
    glLinkProgram(prog);
    GLint linked = 0;
    glGetProgramiv(prog, GL_LINK_STATUS, &linked);
    if (!linked) { std::fprintf(stderr, "link failed\n"); return 1; }

    // 3. Кадр: очистить экран и нарисовать
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(prog);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glFinish();

    // Проверка: читаем пиксели из буфера кадра
    int w, h;
    SDL_GL_GetDrawableSize(win, &w, &h);
    unsigned char center[4], corner[4];
    glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, center);
    glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, corner);
    std::printf("center: %d %d %d\n", center[0], center[1], center[2]);
    std::printf("corner: %d %d %d\n", corner[0], corner[1], corner[2]);
    std::printf("glGetError: %u\n", glGetError());

    SDL_GL_SwapWindow(win);
    SDL_GL_DeleteContext(ctx);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}

Сборка на macOS (проверено: macOS 26.4, Apple clang 21, SDL 2.32.10, Apple Silicon):

clang++ -std=c++17 -Wall -Wextra triangle.cpp $(sdl2-config --cflags --libs) -framework OpenGL -o triangle
./triangle

Вывод:

GL_VERSION:  4.1 Metal - 90.5
GLSL:        4.10
center: 255 128 0
corner: 26 26 26
glGetError: 0

Центр — оранжевый треугольник (0.5 от 255 = 128), угол — фон 0.1. Попросили 3.3, драйвер выдал совместимую 4.1, и на Apple Silicon OpenGL работает поверх Metal. Чтение пикселей скрытого окна — наблюдение прогона, не гарантия: по спецификации содержимое невидимого окна не определено, надежнее рисовать в свой framebuffer object. В Linux программа собирается с -lGL вместо -framework OpenGL; в Windows функции новее 1.1 загружают через glad или GLEW, системная opengl32.dll их не экспортирует.

SDL2 — ветка на поддержке, актуальна SDL3 (3.4.x). В ней другой заголовок <SDL3/SDL.h>, SDL_CreateWindow(title, w, h, flags) без позиции, SDL_GL_DestroyContext и SDL_GetWindowSizeInPixels вместо SDL_GL_DeleteContext и SDL_GL_GetDrawableSize; вызовы OpenGL те же.

OpenGL — машина состояний: glBind... и glUse... выбирают «текущий» объект, и следующие вызовы работают с ним.

Типовая ошибка: шейдер из старого урока

Фрагментный шейдер из урока под OpenGL 2.x в core-контексте:

static const char* kFragmentSrc = R"(#version 330 core
void main() { gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0); }
)";

Результат на том же macOS (stderr; строки GL_VERSION и GLSL как раньше):

shader error:
ERROR: 0:2: Use of undeclared identifier 'gl_FragColor'

link failed

Встроенной gl_FragColor в core-профиле нет. Исправление — свой выход out vec4 FragColor;, как в основном примере. Текст ошибки зависит от драйвера (у NVIDIA, AMD и Mesa другой). Всегда проверяйте GL_COMPILE_STATUS и GL_LINK_STATUS: без них программа молча нарисует пустой экран, а glGetError вернет лишь 1282 (GL_INVALID_OPERATION).

Версии OpenGL

Версия Год Что появилось
1.0 1992 Первая спецификация (Silicon Graphics)
1.5 2003 Буферы вершин (VBO)
2.0 2004 Язык шейдеров GLSL
3.0 / 3.2 2008 / 2009 Устаревание, профили core/compatibility, геометрические шейдеры
3.3 2010 База большинства учебников
4.0 2010 Тесселяция
4.3 2012 Compute-шейдеры, отладочный вывод
4.5 2014 Direct State Access — работа с объектами без привязки
4.6 2017 Прием шейдеров в формате SPIR-V (как в Vulkan)

С 2017 года новых версий OpenGL нет (на сентябрь 2026): Khronos развивает Vulkan, OpenGL живет расширениями. Для мобильных есть ветка OpenGL ES (последняя 3.2), на ES 3.0 основан WebGL 2. Свою версию покажет строка GL_VERSION из примера, в Linux — glxinfo -B (mesa-utils), в Windows — GPU-Z.

OpenGL на macOS

Apple объявила OpenGL устаревшим в macOS 10.14 Mojave (2018) и перешла на Metal. OpenGL в системе остался (проверено на macOS 26.4), но замер на 4.1: compute-шейдеров, DSA и других возможностей 4.2-4.6 нет. Без GL_SILENCE_DEPRECATION компилятор засыпает предупреждениями, а программу под OpenGL 4.3+ на Mac придется переписывать.

OpenGL vs Vulkan, DirectX 12 и Metal

OpenGL Vulkan Direct3D 12 Metal
Кто ведет Khronos Group Khronos Group Microsoft Apple
Платформы Windows, Linux, macOS (до 4.1), ES — мобильные Windows, Linux, Android; на Apple — через MoltenVK Windows, Xbox macOS, iOS, iPadOS
Уровень Высокий: память и синхронизация на драйвере Явный: память, синхронизация, буферы команд на программисте Явный Явный, компактнее Vulkan
Шейдеры GLSL (SPIR-V с 4.6) SPIR-V (пишут на GLSL, HLSL и др.) HLSL Metal Shading Language
Многопоточность Контекст активен в одном потоке Запись команд из многих потоков Да Да
Порог входа Треугольник — около 100 строк Треугольник — порядка 1000 строк Высокий Средний

Как выбирать: учиться графике — OpenGL 3.3 core (понятия переносятся в любой API); производительность на Linux и Android — Vulkan; только Windows и Xbox — Direct3D 12; только Apple — Metal; игра, а не движок — Unity, Unreal Engine или Godot, они выбирают API сами. Цена высокого уровня OpenGL: драйвер скрыто проверяет состояние, и при тысячах вызовов отрисовки на кадр он обычно раньше Vulkan упирается в процессор (насколько — зависит от сцены и драйвера).

Где OpenGL используют в 2026 году

В CAD, научной визуализации и учебных курсах. Жизнь ему продлевают трансляторы: Zink в Mesa исполняет OpenGL поверх Vulkan, ANGLE — OpenGL ES поверх Direct3D, Vulkan или Metal (так WebGL работает в Chrome на Windows). Новые нагруженные проекты чаще берут явные API, в вебе — WebGPU.

Выводы

  • OpenGL — открытая спецификация Khronos Group; реализует ее драйвер, версия зависит от GPU и драйвера.
  • Современный OpenGL — core profile, буферы и шейдеры GLSL; glBegin/glEnd, GLU и GLUT — наследие фиксированного конвейера.
  • Последняя версия — 4.6 (2017); на macOS OpenGL устарел и ограничен 4.1.
  • Vulkan, Direct3D 12 и Metal — явные API с меньшими накладными расходами и большим объемом кода; OpenGL — самый короткий путь к пониманию конвейера.
  • Проверяйте компиляцию и линковку шейдеров, иначе ошибка выглядит как пустой экран.

Где применяется / связь с практикой

Освойте тему на практике

Графику на OpenGL и Vulkan чаще всего пишут на C++: движки, CAD, симуляторы. Помимо API нужен уверенный современный C++: RAII-обертки над объектами OpenGL, время жизни ресурсов, многопоточность, CMake. Их системно разбирают на курсе «C++-разработчик. Продвинутый уровень». Послушать преподавателей и попробовать формат можно на бесплатных открытых уроках Otus.

FAQ

Нужно ли скачивать OpenGL отдельно?
Нет, он входит в драйвер видеокарты; для новой версии обычно достаточно обновить драйвер. Для разработки ставят библиотеку окна (GLFW или SDL) и загрузчик функций (glad или GLEW).

Можно ли писать под OpenGL не на C++?
Да: есть привязки для C, Python (PyOpenGL, moderngl), Java (LWJGL), C# (OpenTK), Rust; вызовы и шейдеры те же.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)