Урок №8. Системы координат в OpenGL

  Дмитрий Бушуев  | 

  Обновл. 17 Май 2020  | 

 550

В предыдущем уроке мы научились использовать матрицы для создания трансформаций, преобразуя все вершины объекта с помощью матриц преобразования. В этом уроке мы рассмотрим системы координат в OpenGL.

Типы систем координат

OpenGL ожидает, что после каждого запуска вершинного шейдера, все вершины, которые мы хотим сделать видимыми, будут находиться в нормализованных координатах устройства. То есть координаты X, Y и Z каждой вершины должны находиться в интервале от -1.0 до 1.0; координаты вне этого диапазона — видны не будут. То, что мы обычно делаем, — это указываем координаты в диапазоне (или пространстве), который мы сами определяем, и в вершинном шейдере преобразуем эти координаты в нормализованные координаты устройства (NDC). Затем, NDC передаются в растеризатор, чтобы превратить их в 2D координаты/пиксели на экране.

Преобразование координат в NDC обычно выполняется поэтапно в несколько шагов, в процессе которых мы последовательно преобразуем вершины объекта в координаты различных систем координат, прежде чем окончательно получить их в виде NDC. Польза от их преобразования в координаты промежуточных систем состоит в том, что некоторые операции/вычисления в определённых системах координат проще.

Существует в общей сложности 5 различных систем координат, которые имеют для нас большое значение:

   Локальное пространство (или ещё «Пространство объекта» от англ. «Local space»).

   Мировое пространство (англ. «World space»).

   Пространство окна просмотра (или ещё «Пространство наблюдателя/камеры» от англ. «View space»).

   Отсеченное пространство (англ. «Clip space»).

   Экранное пространство (англ. «Screen space»).

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

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

Общая картина


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

Рассмотрим детальнее:

   Шаг №1: Локальные координаты — это координаты вашего объекта относительно его локальной точки отсчёта/начала.

   Шаг №2: Следующим шагом является преобразование локальных координат в координаты мирового пространства, которые являются координатами относительно начальной точки некоторого большого (глобального) мира, содержащего все объекты вместе взятые.

   Шаг №3: Далее мы преобразуем координаты мирового пространства в координаты пространства окна просмотра таким образом, чтобы каждая координата являлась видимой с точки зрения камеры или зрителя.

   Шаг №4: После того, как мы получили координаты пространства окна просмотра, необходимо спроецировать их в отсеченные координаты. Координаты отсеченного пространства обрабатываются в диапазоне от -1.0 до 1.0 и определяют, какие вершины окажутся на экране. Проецирование в координаты отсеченного пространства может добавить перспективу, при условии использования перспективной проекции.

   Шаг №5: И, наконец, происходит преобразование отсеченных координат в экранные координаты в процессе, который мы называем трансформацией окна просмотра, в результате которого координаты от -1.0 и 1.0 трансформируются в диапазон координат, задаваемый через функцию glViewport(). Затем, полученные координаты отправляются в растеризатор, чтобы превратиться во фрагменты.

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

Давайте рассмотрим каждую систему координат более подробно.

Локальное пространство

Локальное пространство — это координатное пространство, которое является локальным для вашего объекта, то есть это то место, где ваш объект берёт своё начало. Представьте себе, что вы создали куб в какой-нибудь программе для моделирования (например, Blender). Начало вашего куба, скорее всего, будет находиться в точке (0,0,0) несмотря на то, что в конце вашей работы куб может оказаться в другом месте. Все последующие созданные вами модели, скорее всего, также будут иметь начальное положение в точке (0,0,0). Таким образом, все вершины вашей модели находятся в локальном пространстве: все они локальны для вашего объекта.

Вершины ящика, который мы использовали в предыдущих уроках, были заданы в виде координат, значения которых лежали на отрезке от -0.5 до 0.5, и при этом точка 0.0 являлась точкой начала отсчёта. Это и есть локальные координаты.

Мировое пространство


Если бы мы импортировали все наши объекты непосредственно в приложение, то они, вероятно, были бы расположены где-то внутри друг друга в точке отсчёта (0,0,0), а это нас не устраивает. Мы ведь хотим определить местоположение для каждого объекта, чтобы расположить их внутри большего мира. Координаты в таком мировом пространстве — это координаты всех ваших вершин относительно (игрового) мира. Координаты вашего объекта преобразуются из координат локального пространства в координаты мирового пространства с помощью матрицы модели.

Матрица модели — это матрица преобразования, которая транслирует, масштабирует и/или вращает ваш объект, чтобы поместить его в том месте/ориентации окружающего мира, в котором он должен находиться. Представьте себе, что вы трансформируете дом, уменьшая его масштаб (он был слишком большим в локальном пространстве), перемещая его в пригородный город и поворачивая немного влево по оси Y, чтобы он аккуратно вписывался в соседние дома. Матрицу из примера предыдущего урока, в котором мы задавали местоположения контейнера относительно всей сцены, можно воспринимать как своего рода матрицу модели; мы преобразовали локальные координаты контейнера в какое-то другое место в сцене/мире.

Пространство окна просмотра

Пространство окна просмотра — это то, что люди обычно называют камерой OpenGL (иногда также известное как «пространство камеры» или «пространство поля зрения»). Пространство окна просмотра — это результат преобразования ваших координат мирового пространства в координаты, которые находятся перед взором пользователя. Таким образом, это пространство, обозреваемое с точки зрения камеры. Обычно, это достигается с помощью комбинации трансляций и поворотов для перемещения/поворота сцены так, чтобы некоторые элементы трансформировались в позицию перед камерой. Данные комбинированные преобразования чаще всего хранятся в матрице вида, которая преобразует мировые координаты в координаты пространства окна просмотра. В следующем уроке мы подробно обсудим, как создать такую матрицу для имитации камеры.

Отсеченное пространство


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

Поскольку задание всех видимых координат в диапазоне от -1.0 до 1.0 на самом деле не является интуитивно понятным, то для нашей работы мы указываем собственный набор координат и преобразуем их обратно в NDC, как этого ожидает OpenGL.

Для преобразования вершинных координат из координатного пространства окна просмотра в пространство отсеченных координат необходимо определить так называемую матрицу проекции, которая задаёт диапазон координат, например, от -1000 до 1000 в каждом измерении. Затем матрица проекции преобразует координаты из данного диапазона в нормализованные координаты устройства (-1.0, 1.0). Все координаты, лежащие вне диапазона (-1000; 1000), не спроецируются в диапазон (-1.0; 1.0), и поэтому будут обрезаны. В рамках диапазона, указанного в матрице проекции, точка с координатами (1250, 500, 750) не будет видна, так как её X-координата находится вне диапазона и, таким образом, преобразуется в координату со значением выше 1.0 в NDC, а значит — отсекается.

Примечание: Обратите внимание, что, если только часть примитива, например, треугольник, находится вне объёма отсечения, OpenGL восстановит треугольник в виде одного или несколько треугольников, чтобы поместиться внутри диапазона отсечения.

Данное «окно просмотра», созданное матрицей проекции, называется усеченной пирамидой видимости, и каждая координата, которая оказывается внутри этой пирамиды, будет отображаться на экране пользователя. Полный процесс преобразования координат из заданного диапазона в NDC, которые легко могут быть сопоставлены с координатами двумерного видового пространства, называется проекцией, поскольку матрица проекции проецирует 3D-координаты в легко-отображаемые-на-плоскость нормализованные координаты устройства.

Как только все вершины закончат преобразование в координаты отсеченного пространства, начнёт выполняться заключительная операция, называемая перспективным делением, на этапе которой компоненты X, Y и Z соответствующих радиус-векторов мы делим на однородную W-координату; перспективное деление преобразует 4D-координаты отсеченного пространства в 3D-координаты NDC. Данный шаг выполняется автоматически в конце этапа вершинного шейдера. Именно после этого этапа полученные координаты сопоставляются с координатами экрана (используя настройки функции glViewport()) и превращаются во фрагменты.

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

Ортографическая проекция

Матрица ортографической проекции определяет кубо-образную усеченную пирамиду видимости, задающую пространство отсечения, в котором отсекается каждая вершина, лежащая вне этого прямоугольника. При создании матрицы ортографической проекции мы задаём ширину, высоту и длину данной пирамиды. Все координаты внутри этой пирамиды после преобразования соответствующей матрицей окажутся в пределах диапазона NDC, а значит — не будут отсечены. Рассматриваемая кубо-образная усеченная пирамида чем-то похожа на обычный ящик или контейнер:

Пирамида определяет видимые координаты и имеет такие условные составляющие, как: ширина, высота, ближняя и дальняя плоскости. Любая координата перед ближней плоскостью отсекается, и то же самое относится к координатам за дальней плоскостью. Ортографическая пирамида напрямую отображает все координаты внутри себя на нормализованные координаты устройства без каких-либо особых побочных эффектов, так как она не затрагивает W-компоненту преобразованного вектора; если W-компонента остаётся равной 1.0, то перспективное деление не изменит координаты.

Для создания матрицы ортографической проекции мы используем встроенную GLM-функцию glm::ortho():

Первые два параметра задают левую и правую координаты пирамиды, а третий и четвертый — нижнюю и верхнюю её части. С помощью описанных 4-х точек мы определили размер ближней и дальней плоскостей. Далее, 5-й и 6-й параметры определяют расстояния между ближней и дальней плоскостями. Указанная матрица проекции преобразует все координаты (X, Y и Z) заданного диапазона в нормализованные координаты устройства.

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

Перспективная проекция


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

Как вы можете видеть, из-за перспективы линии на достаточно большом удалении от наблюдателя кажутся сходящимися. Это именно тот эффект, который пытается имитировать перспективная проекция, используя матрицу перспективной проекции. Матрица проекции отображает диапазон, задаваемый усеченной пирамидой, в отсеченное пространство, и также манипулирует w-значением каждой координаты вершины таким образом, что, чем дальше координата вершины находится от зрителя, тем больше становится w-компонента. Как только координаты преобразуются в координаты отсеченного пространства, то их значения будут лежать в диапазоне от -w до w (всё, что выходит за пределы этого диапазона, — отсекается). OpenGL требует, чтобы в выходном результате вершинного шейдера все видимые координаты находились в диапазоне от -1.0 до 1.0, поэтому, как только координаты попадают в усеченное пространство, к ним применяется операция деления перспективы:

Каждая компонента координаты вершины делится на w-компоненту, значение которой тем меньше, чем дальше вершина находится от зрителя. Это ещё одна причина, по которой w-компонента очень важна, так как она помогает нам с перспективной проекцией. После этого, полученные координаты находятся в нормализованном пространстве устройства. Если вам интересно выяснить, как на самом деле вычисляются матрицы ортографической и перспективной проекции (и вы не слишком боитесь математики), я могу порекомендовать вам эту превосходную статью от Songho.

Матрица перспективной проекции может быть создана в GLM следующим образом:

Функция glm::perspective() снова создаёт большую усеченную пирамиду, которая определяет видимое пространство, а всё, что находится вне её, не попадет в объём отсеченного пространства и, таким образом, отсекается. Перспективная пирамида может быть визуализирована как прямоугольник неравномерной формы, каждая координата внутри этого прямоугольника будет отображена в точку в отсеченном пространстве. Ниже представлена картинка с изображением перспективной пирамиды:

Первый параметр определяет значение fov (от англ. «field of view»), которое обозначает поле зрения и задаёт размер видового пространства. Для реалистичного вида он обычно устанавливается равным 45 градусов, но, если вы хотите получить что-то вроде doom-стиля, то можете установить более высокое значение. Второй параметр задает соотношение сторон, которое вычисляется путем деления ширины видового экрана на его высоту. Третий и четвертый параметры задают ближнюю и дальнюю плоскости пирамиды. Обычно мы устанавливаем ближнее расстояние равным значению 0.1, а дальнее — 100.0. Все вершины, лежащие между ближней и дальней плоскостями и внутри отсеченного пространства, будут визуализированы.

Примечание: Всякий раз, когда ближнее значение вашей матрицы перспективы устанавливается слишком большим (например, 10.0), OpenGL будет обрезать все близлежащие к камере координаты (между 0.0 и 10.0), что может дать визуальный результат, который вы, возможно, видели раньше в видеоиграх, где вы могли видеть сквозь определённые объекты, двигаясь очень близко к ним.

При использовании ортографической проекции каждая из координат вершины напрямую сопоставляется с координатой отсеченного пространства без какого-либо причудливого деления перспективы (на самом деле, оно всё ещё выполняется, но без использования w-компоненты (её значение остаётся равным 1) и, следовательно, не имеет никакого эффекта). Поскольку ортографическая проекция не использует перспективную проекцию, объекты, находящиеся дальше, не кажутся меньше, что создаёт странный визуальный эффект. По этой причине ортографическая проекция в основном используется для 2D-визуализации и некоторых архитектурных или инженерных приложений, где мы предпочли бы не иметь вершин, искаженных перспективой. Такие приложения для 3D-моделирования, как Blender, иногда используют ортографическую проекцию, потому что она более точно отображает размеры каждого объекта. Ниже вы увидите сравнение обоих методов проекции в Blender:

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

Складываем всё вместе

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

Vclip = Mprojection × Mview × Mmodel × Vlocal

Обратите внимание, что порядок умножения матрицы — обратный (помните, что нам нужно читать умножение матрицы справа налево). Затем полученная вершина должна быть назначена в gl_Position в вершинном шейдере, и OpenGL автоматически выполнит деление перспективы и отсечение.

Примечание: Вы можете задаться вопросом: «А что потом?». Выходные данные вершинного шейдера требуют, чтобы координаты находились в отсеченном пространстве, что мы только что и сделали с помощью матриц преобразования. Затем OpenGL выполняет деление перспективы на координатах отсеченного пространства, чтобы преобразовать их в координаты нормализованного устройства. После этого OpenGL использует параметры из функции glViewPort() для сопоставления нормализованных координат устройства с экранными координатами, где каждая координата соответствует точке на вашем экране (в нашем случае экран имеет расширение 800×600). Этот процесс называется трансформацией окна просмотра.

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

Переходим в 3D

Теперь, когда мы знаем, как преобразовать 3D-координаты в 2D-координаты, мы можем начать визуализировать реальные 3D-объекты вместо убогой 2D-плоскости, которую мы показывали до сих пор.

Чтобы начать рисовать в 3D, сначала создадим матрицу модели. Матрица модели состоит из трансляций, масштабирования и/или поворотов, которые мы хотели бы применить для преобразования всех вершин объекта в мировое пространство. Давайте немного изменим нашу плоскость, повернув её по оси X так, чтобы она выглядела, будто лежит на полу. Тогда матрица модели будет выглядеть следующим образом:

Умножая координаты вершин с помощью вышеописанной матрицы модели, мы преобразуем их в мировые координаты. Наш плоскость, которая находится на полу, таким образом, представляет собой плоскость в глобальном мире.

Далее нам нужно создать матрицу вида. Мы хотим немного сдвинуться назад в сцене, чтобы объект стал видимым (когда в мировом пространстве мы находимся в начале координат (0,0,0)). Чтобы передвигаться по сцене, необходимо представить следующее:

   Перемещение камеры назад — это то же самое, что перемещение всей сцены вперёд.

Это именно то, что делает матрица вида, — мы перемещаем всю сцену в направлении, обратном направлению перемещения камеры.

Поскольку мы хотим двигаться назад, а OpenGL — это правосторонняя система, то мы должны двигаться вдоль положительного направления оси Z. А выполнить данное передвижение можно переводя сцену в сторону отрицательного направления оси Z. Это создаёт впечатление, что мы движемся назад.

Правосторонняя система (Правило правой руки)

Условно говоря, OpenGL — это правосторонняя система. Данный факт говорит нам о том, что положительная ось X находится справа от вас, положительная ось Y — направлена вверх, а положительная ось Z — назад. Представьте, что ваш экран является центром 3-х осей и положительной оси Z, проходящей через ваш экран к вам. Оси рисуются следующим образом:

Чтобы понять, почему правило называется «правилом правой руки», сделайте следующее:

   Вытяните правую руку вдоль положительной оси Y.

   Пусть ваш большой палец указывает вправо.

   Пусть ваш указательный палец указывает вверх.

   Теперь согните средний палец на 90 градусов относительно вашей ладони.

Если вы всё сделали правильно, то ваш большой палец должен быть направлен в сторону положительной оси X, указательный палец — в сторону положительной оси Y, а средний палец — в сторону положительной оси Z. Если бы вы сделали это левой рукой, то увидели бы, что ось Z перевёрнута. Это называется левосторонней системой и она, обычно, используется в DirectX. Заметим, что в нормализованных координатах устройства OpenGL фактически использует левостороннюю систему (матрица проекции выступает в роли переключателя стрелки).

Переходим в 3D (продолжение)

Более подробно о том, как передвигаться по сцене, мы поговорим в следующем уроке. На данный момент матрица вида имеет следующее представление:

Последнее, что нам нужно определить, — это матрица проекции. Мы будем использовать перспективную проекцию для нашей сцены, поэтому объявим матрицу проекции следующим образом:

Теперь, когда мы создали матрицы преобразований, необходимо передать их нашим шейдерам. Сначала объявим матрицы преобразований uniform-переменными в вершинном шейдере и умножим их на координаты вершин:

Мы также должны отправить матрицы в шейдер (это обычно делается каждый кадр, так как матрицы преобразования имеют тенденцию сильно меняться):

Теперь, когда наши координаты вершин преобразуются через матрицы модели, вида и проекции, конечный объект должен быть:

   Лежащим на полу и наклоненным в сторону от нас.

   Немного дальше относительно нас.

   Иметь перспективу (он должен становиться меньше, чем дальше находятся его вершины).

Давайте проверим, действительно ли результат соответствует этим требованиям:

Это действительно выглядит так, как будто наша плоскость — это трёхмерная плоскость, которая лежит на каком-то воображаемом полу. Если вы не получили результат выше, то сравните свой код с полным исходным кодом ниже.

Исходный код №1 — Урок №8. Системы координат в OpenGL

Ещё больше 3D

До сих пор, даже в 3D-пространстве, мы работали с 2D-плоскостью, поэтому давайте расширим нашу 2D-плоскость до 3D-куба. Для визуализации куба нам нужно в общей сложности 36 вершин (6 граней * 2 треугольника * 3 вершины каждый). 36 вершин — это очень много, чтобы суммировать их, так что вы можете получить их отсюда:

Для развлечения мы позволим кубу вращаться с течением времени:

А затем мы отобразим куб с помощью функции glDrawArrays() (так как мы не указывали индексы), но на этот раз с количеством вершин равным 36:

Вы должны получить что-то похожее на следующее:

Он действительно немного напоминает куб, но что-то не так. Некоторые стороны куба рисуются поверх других сторон. Это происходит потому, что когда OpenGL рисует ваш куб треугольник за треугольником, фрагмент за фрагментом, он будет перезаписывать любой цвет пикселя, который, возможно, уже был нарисован там раньше. Поскольку OpenGL не даёт никаких гарантий относительно порядка отображения треугольников (в рамках одного и того же вызова функции отрисовки), некоторые треугольники рисуются поверх друг друга, даже если один из них явно должен быть перед другим.

К счастью, OpenGL хранит информацию о глубине в буфере, называемом Z-буфером, который позволяет OpenGL решать, когда рисовать поверх пикселя, а когда — нет. Используя Z-буфер, мы можем настроить OpenGL для выполнения тестирования глубины.

Исходный код №2 — Урок №8. Системы координат в OpenGL

Z-буфер

OpenGL хранит всю свою информацию о глубине в Z-буфере, также известном как буфер глубины. GLFW автоматически создаёт такой буфер для вас (точно так же, как это было и с буфером цвета, который хранит цвета выходного изображения). Информация о глубине хранится в рамках каждого фрагмента (как Z-значение фрагмента), и всякий раз, когда фрагмент хочет вывести свой цвет, OpenGL сравнивает его значения глубины с Z-буфером. Если текущий фрагмент находится позади другого фрагмента, то он отбрасывается, а в противном случае, — перезаписывается. Этот процесс называется тестированием глубины и выполняется автоматически средствами OpenGL.

Однако, если мы хотим убедиться, что OpenGL действительно выполняет тестирование глубины, нам сначала нужно сказать OpenGL, что мы хотим включить глубинное тестирование; ведь по умолчанию оно отключено. Мы можем включить глубинное тестирование с помощью функции glEnable(). Функции glEnable() и glDisable() позволяют нам включать/отключать определённый функционал в OpenGL до тех пор, пока не будет сделан другой вызов для его включения/отключения. Прямо сейчас мы хотим включить глубинное тестирование, задействовав GL_DEPTH_TEST:

Поскольку мы используем буфер глубины, то нам также нужно его очищать перед каждой итерацией рендеринга (в противном случае информация о глубине предыдущего кадра останется в буфере). Точно так же, как и с очисткой цветового буфера, мы можем очистить буфер глубины, указав бит DEPTH_BUFFER_BIT в функции glClear():

Давайте снова запустим нашу программу и посмотрим, выполняет ли теперь OpenGL глубинное тестирование:

Ну вот и всё! Полностью текстурированный куб с правильным тестированием глубины, который вращается с течением времени.

Исходный код №3 — Урок №8. Системы координат в OpenGL

Больше кубиков!

Давайте отобразим 10 кубиков на экране. Каждый куб будет выглядеть одинаково, но отличаться вращением и местоположением. Графическая расстановка куба уже определена, поэтому нам не нужно менять наши буферы или массивы атрибутов при рендеринге большего количества объектов. Единственное, что мы должны изменить для каждого объекта, — это его матрицу модели, в которой мы преобразуем кубики в мировое пространство.

Во-первых, давайте определим вектор трансляции для каждого куба, который определяет его положение в мировом пространстве. Зададим 10 позиций куба в массиве glm::vec3:

Теперь в цикле рендеринга нам нужно вызвать функцию glDrawArrays() 10 раз, но на этот раз каждый раз посылаем другую матрицу модели в вершинный шейдер, прежде чем отправлять вызов на отрисовку. Создадим небольшой цикл внутри цикла рендеринга, который будет рендерить наш объект 10 раз с другой матрицей модели каждый раз. Обратите внимание, что мы также добавляем небольшое уникальное вращение к каждому контейнеру:

Этот фрагмент кода будет обновлять матрицу модели каждый раз, когда рисуется новый куб, и делать это он будет в общей сложности 10 раз. Прямо сейчас мы должны увидеть мир, наполненный 10 странно вращающимися кубиками:

Прекрасно! Похоже, наш контейнер нашел себе единомышленников. Если вы застряли, смотрите исходный код ниже.

Исходный код №4 — Урок №8. Системы координат в OpenGL

Упражнения

Задание №1

Попробуйте поэкспериментировать с параметрами FoV и соотношением сторон GLM-функции проекции. Посмотрите, сможете ли вы понять, как они влияют на пирамиду перспективы.

Задание №2

Поиграйтесь с матрицей вида, выполняя трансляцию в нескольких направлениях, и посмотрите, как меняется сцена. Подумайте о матрице вида как об объекте камеры.

Задание №3

Попробуйте заставить каждый 3-й контейнер (включая 1-й) вращаться с течением времени, оставляя другие контейнеры статичными, используя только матрицу модели.

Ответ №3

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (11 оценок, среднее: 5,00 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *