hellOGL: OpenGL hello world

  • Tutorial
Сегодня я покажу, как открыть окно и создать контекст OpenGL. Это на удивление непростая задача, OpenGL до сих пор не имеет официальных кроссплатформенных средств создания контекста, поэтому будем опираться на сторонние библиотеки (в данном случае GLFW и glad). В интернете уже очень много подобных hello world, но всё, что я видел, мне не нравится: или оно очень навороченное, или картинки в примерах уж очень примитивные (либо и то, и другое!). Большое спасибо всем авторам, но я выкачу очередной туториал :)

Сегодня мы отрисуем вот такое:



Эта модель нарисована художником Сэмюэлем Шаритом (arshlevon), огромное ему спасибо за разрешение её использовать в рамках моего курса лекций по компьютерной графике!

Этап 0: читаем tinyrenderer


Вообще говоря, лучше всего (хотя и не обязательно) эту лекцию читать после прочтения всего моего курса tinyrenderer. Для тех, кто не говорит по английски, этот курс лекций доступен на хабре, хотя русскую версию я больше и не поддерживаю. В рамках этого курса лекций я показал, как всего-навсего в пять сотен строчек кода, да ещё с полным запретом на сторонние библиотеки, можно нарисовать вот такую картинку:



Удивительно, но многие мои студенты не понимают, что этот софтверный растеризатор — не просто игрушка, но самое настоящее введение в то, как работает OpenGL. Поэтому сегодня я покажу, как отрисовать диаблу с хардверным ускорением, причём я во многом воспользуюсь кодом из репозитория софтверного растеризатора.

Внимание, я не ставлю себе задачей объяснить каждую строчку кода, так как полагаюсь на то, что чтение документации — самый хороший способ всё понять. Мой код нужен лишь для того, чтобы знать, что именно в документации читать, и в каком порядке. Более того, я не буду объяснять, что такое шейдеры, и я не буду объяснять, как считать карты нормалей. Я потратил очень много времени на tinyrenderer, где это всё разложено по полочкам.

Этап первый, самый сложный: создание окна


Весь репозиторий живёт здесь; создал по одному коммиту на каждый шаг туториала, так как гитхаб даёт очень удобную просматривалку всех внесённых изменений. Начинаем вот отсюда, наша цель получить вот такое окошко:



Компилируется код при помощи CMake; я проверял под линуксом (g++) и виндой (Visual Studio 2017). Под линуксом последняя версия кода компилируется вот так:

git clone --recurse-submodules https://github.com/ssloy/hellOGL.git
cd hellOGL
mkdir build
cd build
cmake ..
make

Используйте `git checkout`, если хотите компилировать отдельный коммит, а не последнюю версию. Этот код подгружает glad и GLFW, создаёт окошко с необходимым клавиатурным коллбэком, и подгружает с диска пустые вершинный и пиксельный шейдеры.

Этап второй: подгружаем 3Д модель


Изменения в проекте, внесённые на этом этапе, смотреть здесь. На данном этапе наша цель распарсить файл 3Д модели, и нарисовать первые треугольники, не заботясь на данный момент об освещении:



Обратите внимание, что и саму модель, и библиотеку для работы с векторами, да и сам парсер модели я взял целиком из tinyrenderer. Может, не так уж и бесполезен софтверный рендерер?

Основная идея в современном OpenGL очень простая. Мы сначала подгрузили 3Д модель, а затем я создаю массив vertices размером 3 * 3 * (количество треугольников). Каждый треугольник имеет три вершины, так? Каждая вершина описывается тремя числами (x,y,z). Итого для описания всей модели нам достаточно 3*3*model.nfaces():

    std::vector<GLfloat> vertices(3*3*model.nfaces(), 0);
    for (int i=0; i<model.nfaces(); i++) {
        for (int j=0; j<3; j++) {
            for (int k=0; k<3; k++) vertices[(i*3+j)*3 + k] = model.point(model.vert(i, j))[k];
        }
    }

А затем мы скажем OpenGL, что вот массив, рисуй, родной!

    while (!glfwWindowShouldClose(window)) {
[...]
	        glDrawArrays(GL_TRIANGLES, 0, vertices.size());
[...]
}	

Вершинный шейдер ничего интересного не делает, он просто передаёт фрагментному шейдеру данные как есть:

#version 330 core

// Input vertex data, different for all executions of this shader
layout(location = 0) in vec3 vertexPosition_modelspace;

void main() {
    gl_Position = vec4(vertexPosition_modelspace, 1);              // Output position of the vertex, in clip space
}

Ну и фрагментный шейдер тоже незатейлив. Он просто рисует текущий пикесль красным цветом:

#version 330 core

// Output data
out vec3 color;

void main() {
    color = vec3(1,0,0);
}

Самое сложное сделано, теперь дело техники!

Этап третий: диффузное освещение


Изменения в проекте, внесённые на этом этапе, смотреть здесь. Мы должны получить вот такую картинку:



Диффузное освещение в модели Фонга, как известно, это простое скалярное произведение между
нормальным вектором и вектором освещения. Поэтому вдобавок к массиву vertices я добавил ещё один массив normals. Не глядя в код скажите, какого он размера?

Самое интересное происходит во фрагментном шейдере, в основном .cpp файле происходит лишь подгрузка данных:

#version 330 core

// Output data
out vec3 color;

// Interpolated values from the vertex shaders
in vec3 Normal_cameraspace;
in vec3 LightDirection_cameraspace;

void main() {
    vec3 n = normalize(Normal_cameraspace);  // Normal of the computed fragment, in camera space
    vec3 l = normalize(LightDirection_cameraspace); // Direction of the light (from the fragment to the light)
    float cosTheta = clamp(dot(n,l), 0, 1);         // Cosine of the angle between the normal and the light direction, 

    color = vec3(1,0,0)*(0.1 +           //  ambient lighting
                         1.3*cosTheta);  //  diffuse lighting
}

Этап четвёртый: матрицы преобразований


Изменения в проекте, внесённые на этом этапе, смотреть здесь. На этом этапе я закодил Model, View и Projection матрицы. В самом начале они просто единичные, но если вы нажмёте пробел, то модель начнёт вращаться: при каждой отрисовке картинки я поворачиваю матрицу Model вокруг оси z на 0.01 радиана:

            { // rotate the model around the z axis with each frame
                Matrix R = rot_z(0.01);
                if (animate) M = R*M;
            }

Здесь функция rot_z() возвращает матрицу вращения вокруг оси z на заданный угол. Поскольку OpenGL про мой класс матриц ничего не знает, пришлось добавить экспорт матриц void export_row_major() в простой указатель на float.



Этап пятый: карты нормалей


Изменения в проекте, внесённые на этом этапе, смотреть здесь. На этом этапе мы научимся накладывать текстуры. Поскольку обычная диффузная текстура — это скучно, то я сразу применю карту нормалей, да причём в касательном пространстве. Карты нормалей выглядят примерно вот так:



Соответствующие вычисления, мягко скажем, неочевидны, поэтому опять-таки, читайте объяснения в tinyrenderer. С точки зрения данных нужно добавить несколько буферов: координаты uv, и массивы касательных и бикасательных векторов.



Этап пятый: диффузная текстура


Ну, если мы уже умеем считать карты нормалей, то обычную диффузную текстуру наложить просто тривиально. Изменения в проекте, внесённые на этом этапе, смотреть здесь.



Этап шестой: блики


Изменения в проекте, внесённые на этом этапе, смотреть здесь. Заключительный этап, доабавляем ещё одну текстуру, которая нам позволит симулировать блики освещения от блестящих поверхностей:



Заключение


В этом коде много что можно улучшить, да и визуальные эффекты можно наворачивать бесконечно. Но моя цель не в этом, моя цель показать, что абсолютно все техники, которые я затрагивал в софтверном рендеринге, применимы и в настоящем контексте OpenGL. И лично я по-прежнему считаю, что начинать знакомство с 3Д графикой нужно, рисуя картинки без использования магии графических библиотек.

В качестве продлжения попробуйте, например, добавить тени, или посчитать глобальное освещение, ну или, наконец, сделать glow map: ведь глаза и кристалл во лбу Диабло должны светиться!
Поделиться публикацией

Комментарии 54

    0
    Если рассматривать статью как ознакомление с OpenGL, то фактически нет объяснений что, как, зачем и почему. За проект на гите спасибо, конечно, но вот в самой статье крайне мало информации полезной.
      +4
      У меня десять статей ознакомления с OpenGL. Там очень подробно о том, что, как, зачем и почему. И об этом напрямую сказано, причём неоднократно.
        +3

        Все видел, на все подписан давно и за все больше спасибо. Просто в этой статье кроме ссылок на гитхаб ничего нет, что не особо на страстью похоже.

          0
          Ну так а репозиторий на гитхабе не абы какой, а имеет чёткую структуру повествования. Смысла повторять документацию на OpenGL я не вижу ни малейшего, я лишь даю код, который соответствует предыдущим статьям, и благодаря которому желающий поймёт, что именно ему нужно читать в официальной документации, которая весьма объёмна, и с первого подхода к ней можно вполне себе опухнуть. Кроме того, интернет полон протухшими примерами легаси OpenGL, и данный проект должен быть ценнен не столько текстом вокруг, сколько тем, что написан на достаточно современном GL, и при этом должен собираться и работать из коробки.
            +1

            За проект на гите тоже спасибо. Просто это мое мнение, что статья в формате: пункт 1 посмотрите этот код, пункт 2 посмотрите вот этот код. Немного странно воспринимается для меня, как лекция (как указано в самой статье). В совокупности с предыдущими уроками и с постоянным обращениям к докам по OpenGL кончено все отлично.

              0

              Возможно, мне просто было непривычно видеть статью подобного формата у Вас после статей про OpenGL своими руками, которые до сих пор одни из моих любимых на хабре.

                +1
                На самом деле, все учебные материалы по OpenGL упираются в то, что нужно и API показывать, и про математику рассказывать, а и то, и другое плохо уживаются вместе. Именно поэтому я подробно расписывал софтверный рендеринг, поскольку он даёт полную свободу действий, не заставляя работать в жёстких рамках API, документацию на который трудно понять.

                А этот проект — да, это не статья как таковая. Можно рассматривать это как филиал stackoverflow, с которого можно копипейстить нужные куски. Это удивительно, но в 2019м году быстро найти минималистичный пример OpenGL, который не использует legacy кода, и который реально компилируется — это непросто. Отсюда и мой репозиторий. Можете его считать просто как рекомендацию использовать для мелких приложений GLFW и glad вместо GLEW и glut, что были стандартом десять лет назад.

                А про сам текст статьи, то, грубо говоря, если вы уже написали сортировку пузырьком на си, то переписать её на жаве — дело чисто синтаксиса, и сильно растекаться в описании, по моему скромному мнению, ни к чему. Этот репозиторий хоть и hello world, но предполагает некоторую подготовку читателя.
                  +1

                  Вы совершенно правы, все что меня смутило — в статье говорится что это лекция, хотя больше похоже на комментарии к коммитам. Что вовсе не преуменьшает проделанную Вами работу, а ссылку на гит я уже схоронил. В любом случае больше спасибо за труды и жду новых интересных статей.

                    0
                    Всё, что я выкладывал по компьютерной графике, и этот текст+репозиторий в том числе — это краткие тезисы лекций, которые я читаю вживую. Отсюда и выбор слова. К сожалению, уложить сотню часов болтовни, жестикулирования, чтения мыслей и дебагинга чужого кода в довольно жёсткий формат статей на хабре далеко не всегда удаётся :(
        0
        Какая связь между отображением модели и инициализацией контекста?
          +2
          Чтобы отобразить модель, нужно инициализировать контекст?
            +1
            Вот только статья якобы как раз про контекст. А по содерждимому — про модель и контексту вообще минимум текста соответствует.
              0
              Я не понял, вы что, жалуетесь, что я не остановился на вот этом этапе?
              интересная картинка



              Статья называется «hello world», а это не обязательно только открытие пустых окон.
                –1
                Статья называется hello world, являсь тизером к туторам на левом сайте.
                +3
                Как ни странно, но именно на этом месте много моментов. Хотелось бы увидеть ПРАВИЛЬНОЕ создание контекста. Открыть окно не сложно, а вот открыть так, чтоб потом не было вопросов наподобие: «А почему два не работают? а если четыре?».
                Брать гибкий GLFW и гвоздём прибитый GLAD для сохранения кросплатформенности? Немного странно, мягко скажем. Да и зачем при таком подходе GLAD. Кстати зашедший наверняка хотел бы узнать про моменты связанные с ними.

                Замечания выше справедливы. Если эта статья направлена на обучение, то скорее всего формат подачи должен быть более полным.
                  0
                  Давайте для начала договоримся о том, что «правильно» сильно зависит от решаемой задачи, и универсального решения не существует. А если вы видите способ улучшить код, присылайте пуллреквест, я их принимаю с удовольствием.
                    +2
                    Мой маленький и «пушистый протест» вызван только тем, что я вижу у вас действительно высокий уровень знаний. Фундаментальных и важных. Вы делитесь ими с нами и это достойно уважения. Именно поэтому хотелось бы больше услышать и почитать. Воспримите моё сообщение как «Ну пожалуйста еще! Ещё больше».
                    Спасибо.
          0
          А есть какой-то простой инструмент, чтобы для моделей генерировал все что нужно чтобы их отрисовать? Например координаты вершин, текстуры, карты нормалей и т.д.
          Каждый раз, когда я начинал заниматься OpenGL останавливался именно на этом моменте. Кубик крутить уже было скучно, а как сделать свою модель было не понятно. Пробовал разобраться с Blender но как-то не пошло.
            0
            Ох, простых нет. Художники не зря свой хлеб едят. Кстати, а вы просто карандашом на бумаге хорошо рисуете? Без этого никак, похоже.
              0
              Нет, я вообще никак не рисую. Но мне и не хочется самому рисовать. Для старта хочется найти какие-то уже готовые модели со всеми текстурами и картами нормалей, чтобы поэкспериментировать.
                0
                Так я для этого с художниками и договаривался. Ровно потому, что у меня мороз по коже от разных кубиков и
                блендеровской сюзанны.

                  0
                  Да миллиард их.
                  turbosquid в помощь.
                    +1
                    Найти хорошую модель не является непосильной задачей, но про миллиард вы погорячились. В бесплатном доступе лежат в основном модели посредственного качества, и далеко не всегда с полным набором текстур, а уж если мы захотим скелеты и анимации — вообще тяжело. Так что вопрос вполне законный.
                      0
                      В бесплатном доступе лежат в основном модели посредственного качества

                      Самого разного качества модели лежат. И не особо глубоко.

                      захотим скелеты и анимации — вообще тяжело

                      Это вообще не тяжело. Mixamo в помощь.
                        +1
                        Миксамо в помощь — это почти так же, как и «майя в помощь». Я думаю, вы прекрасно знаете, что создание модели такой сложности, как показана у меня в статье, это неделя работы хорошего художника. А ведь в ней нет анимации, это ещё больше. И художники хотят есть и бесплатно работают, но редко. В свободном доступе в основном работы учеников, что тоже хорошо, но не всегда достаточно.
                          0
                          О чем вы вообще говорите…
                          Mixamo аниматор берет любую модель, в два клика её ригит автоматом(даже ребенок справится) и позволяет на неё вешать любую анимацию(тоже в пару кликов). Их сотни бесплатных и тысячи платных.
                          Никакой майей здесь и не пахнет.
                            0
                            Возможно, я отстал от жизни, но если всё так просто, можно вас попросить сделать тридцатисекундный ролик с боксерским поединком двух Диабло? Модель уже есть.
                              0
                              дайте мне ссыль на модель в fbx
                                –2
                                У меня только .obj. Вы же сказали любую? .obj де факто стандарт обмена
                                  +1
                                  Из 2000 чтоли пишите? С стандартом в виде obj. :) Ну не коллада и то хорошо.
                                  Уж лет так пять fbx стандарт во всех движках и пакетах. Ок, давайте архив с obj.Миксамо поддерживает obj. Так что obj в т-позе вполне подойдет.

                                  Вы же сказали любую?

                                  Ага. Любую, геометрически. А не в плане формата. Очевидно что все 100500 миллионов форматов никто не поддерживает.
                                  Ну и даже в плане геометрии чуда нет, любую с двумя руками, двумя ногами, ровно стоящий.
              0
              статья отличная, но обсуждается по факту далеко не hello world
                0
                А нет ли у кого чего-нибудь работающего с инициализацией контекста в серверном контейнере с mesa для off-screen рендеринга (c osmesa или без)? Единственное до чего додумался методом научного тыка — инициализировать контекст через glut и виртуальный x11 буфер xvfb, что как бы не блещет прямизной.
                  0
                  русскую версию я больше и не поддерживаю

                  То есть те статьи на хабре уже не актуальны и многое изменилось?
                  Или вопросы в комментариях под ними больше не будут получать ответов автора?
                  А то я наконец-то плотно взялся за реализацию всего, что хотел попробовать, но долго откладывал, и скоро до того курса дойду как раз.
                    0
                    На самом деле, это означает, что мне лень в двух десятках статей править оглавление каждый раз, как я добавляю новую. Так-то текст не устарел, и на вопросы я отвечаю.
                    0
                    Очень странно считать создание окна и контекста в OpenGL непростой задачей потому что в интернете слишком много примеров как это сделать.
                      +1
                      Вы очень правильно использовали слово слишком. Примеров много, но очень большая их часть уже безнадежно устарела. А другая, тоже большая, часть предполагает, что у вас уже установлены нужные библиотеки, при этом не всегда говорится, какие. В итоге разобраться в этом начинающему непросто.
                        0
                        Есть же learnopengl.com. Ни в подробности ни в актуальности вашей статье его уроки не уступают, разве что актуальной русскоязычной версии нет.
                          0
                          — Что подарить нашему товарищу на юбилей?
                          — Давайте подарим ему книгу!
                          — Зачем ему книга? У него уже одна есть.


                          Я думаю, что существование learnopengl не является препятствием для написания других учебных материалов.
                      0
                      Я не совсем понял, с OpenGL почти не знаком, гораздо больше знаком с DirectX и вот отсюда вопрос, в OpenGL нет вершинных и прочих буферов?
                      Спрашиваю потому что я значительное время потратил чтоб понять как и зачем нужны эти буферы (и честно говоря у меня все еще есть ощущение что я не до конца их понимаю в масштабах ААА игр).
                        0
                        Всё есть, эти буферы идут из железа, поэтому директыкс и опенжл суть есть одно и то же, просто по разному записано.
                          0
                          и честно говоря у меня все еще есть ощущение что я не до конца их понимаю в масштабах ААА игр
                          А что конкретней непонятно? Весь смысл всех буферов и тому подобных объектов сводится к тому чтобы гонять как можно меньше данных по шине и как можно более крупными порциями. Просто оптимизация.
                            0
                            Ну вот я не пойму, в сцене присутсвуют тысячи 3д объектов, нужно ли объекты по типу шейдера (вроде technique называется) распределять по буфферам, или все в один буффер толкать, например.
                              0
                              инстансы вам в помощь, если объекты с одинаковой геометрией.
                              статику надо по максимуму пихать в один буффер, но с оговорочкой — чтобы лишнего не рендерилось.
                              то есть взять и весь мир запихать в один буффер, при том что в фрустум попадает 1% — плохо.
                              юить надо так, чтобы минимально необходимое количество геометрии рендерилось, за минимально возможное количество дипов.
                                0
                                А если, предположим, у нас игра-стратегия, где игрок по ходу игры строит новые здания, для этого использовать дополнительные динамичные буфферы? Тот же вопрос ко всяким временным VFX объектам типа тумана и огня.
                                И еще, если реализован picking, с точки зрения буфферов и вызовов отрисовки ничего меняться не должно, верно?
                                Сорян за много вопросов, тема интересная, опыта мало
                                  0
                                  В современных GAPI вообще нет другого способа, кроме использования буфферов.
                                  Ну и если говорить конкретно о зданиях, то да. Использовать буфферы.
                                  Всё что вы рисуете более одного раза надо пихать в буфферы даже на старых GAPI.
                                  В целом, если вы хотите делать игру, а не движок — возьмите Unity или UE. Влезать в дебри движкостроения, чтобы делать игру — бессмысленно. Опыт подсказывает, что пока делается движок, игра не делается. А движок делается бесконечно.
                                    0
                                    Мне наоборот как раз больше нравится в дебрях копаться :)
                                    А движкам немного не доверяю в том плане, что они на общий лад скорее делаются, особенно юнити.
                                      0
                                      UE опенсорсный, можно хреначить свои велосипеды на основе уже готового крутого инструмента.
                                      Впрочем если нравится делать движки, то почему бы и нет.
                                      Буферы — это безвариантный инструмент в современном мире. Ну и в целом покопайте в сторону оптимизаций рендера, там столько жести вылазит… :(
                                        0
                                        Спасибо за советы!)
                          0
                          А для чего нужен этот курс, если уже есть Перевод learnopengl на хабре, причем достаточно качественный, с объяснением принципов работы всех нужных буферов, разными эффектами, шейдерами на GLSL и продвинутым освещением?
                            0
                            Я не знаю, как ответить на вопрос: «А зачем вы написали учебник?»

                            Но давайте посмотрим на простую вещь: сегодня, 13го марта 2019го года, репозиторий learnopengl имеет 2622 звезды на гитхабе, а мой курс лекций 6506 звёзд. Наверное, кто-то находит его полезным.
                              0
                              То, что тут написано одной статьей, в хабровском переводе learnopengl развернуто расписано больше чем на 15 статей, с хорошим объяснением всех аспектов. Если Вы сможете расписать настолько же подробно, с удовольствием почитаю на досуге. Но на данный момент эта статья не дает хорошего представления о том, как работать с OpenGL. И даже как вводная она на мой взгляд не очень хороша, потому что слишком много этапов проскакивается сразу, не глядя. Hello Window или Hello Triangle для вводной статьи было бы в самый раз, тем более что у вас glad, а там glew.
                                0
                                Ещё раз повторю: у меня статей уже полтора десятка на эту тему. Конкретно эта статья не первая в цикле.
                            +1
                            Спасибо! Очень нехватало такого простого но не примитивного примера.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое