Предыдущий шейдер рисовал зелёный треугольник, не обращая внимания на цвет. В этом выпуске мы научимся передавать в шейдер цвета вершин.
Предыдущие части: Наш первый зелёный шейдер, Рисуем треугольник, Приступим, помолясь, OpenGL на пальцах
Слегка подытожим пройденное. Мы сделали список вершин треугольника, передали его в буфер на стороне OpenGL, и сказали вершинному шейдеру, что он должен в качестве входных данных использовать этот буфер.
Всё, что нужно доделать, это
- Сделать ещё один буфер с цветами вершин
- Сказать вершинному шейдеру, что нужно использовать два входных буфера, а не один
- Сказать пиксельному шейдеру, чтобы брал те данные цвета, которые передал ему вершинный шейдер.
Основная работа заключается во втором пункте. Нужно связать входные параметры шейдера с разными буферами.
Добавляем буфер
По полной аналогии с вершинами создаём массив цветов:
Каждый цвет это три RGB-компоненты. Первый будет чистый R, второй будет чистый G, и третий будет чистый B.
Затем точно так же по аналогии с вершинами создаём буфер на стороне OpenGL и перекачиваем в него наши данные о цветах:
Назначение входных параметров
Все входные параметры шейдеры берут из VAO – Vertex Array Object, или вершинного массива. Код для создания VAO мы уже писали:
Сюда сейчас добавилась лишь одна строчка. glEnableVertexAttribArray(0) разрешал использование входного параметра с индексом 0. А мы, добавив glEnableVertexAttribArray(1), также разрешили использование входного параметра с индексом 1. Таким образом, теперь входных параметров будет два.
Теперь самая магия, где всё происходит.
Первую часть мы уже писали, но давайте повторим. glBindBuffer() связывает номерок буфера вершин vertex_buffer_handle с целью GL_ARRAY_BUFFER, делая его текущим для дальнейших действий.
glVertexAttribPointer() настраивает для текущего буфера индекс параметра (0) и его формат (длина элемента в байтах, тип элемента и т.д.)
Что мы добавили: то же самое, только для буфера цветов color_buffer_handle. И назначили ему индекс не 0, а 1.
Наконец-то OpenGL имеет два указателя на буферы, проиндексированные как 0 и 1, и знает их формат. То есть может скармливать эти данные шейдерам.
Переделываем шейдеры
Вершинный шейдер:
Теперь он принимает на вход два параметра: координаты вершины из буфера вершин (VertexPosition) и цвет из буфера цветов (VertexColor). Цвет просто передаётся дальше в выходной параметр Color для пиксельного шейдера.
Пиксельный шейдер элементарно получает этот цвет и красит им пиксел:
Попробуем запустить программу:
Вот даже что-то получилось, но треугольник неправильный (вершины не там, где надо) и цвета вершин тоже неправильные (есть красный и зелёный, но нет синего).
Очевидно, в параметрах вершинного шейдера что-то напутано и он берёт данные не оттуда, откуда надо.
Ранее мы обозначили вершинные параметры индексом 0, а цветовые параметры индексом 1. С помощью следующих инструкций мы сообщаем шейдеру, какой из них какой:
Перед in-параметром VertexPosition мы пишем layout (location = 0).
layout это значит "расклад", location – "позиция". То есть мы пишем что-то вроде "расклад такой: VertexPosition находится на позиции 0".
Подобный синтаксис кажется мне довольно нелепым, так как проще было бы написать, допустим,
in(0) vec3 VertexPosition
Но вероятно, я о чём-то просто не в курсе. В общем, мы сообщили шейдеру, что параметр с индексом 0 – это VertexPosition.
Аналогично мы поступаем с параметром VertexColor с индексом 1.
Теперь шейдер будет брать данные из правильных источников, и мы можем посмотреть на результат:
Работает!
Как меняется цвет?
Мы задали только три цвета, а треугольник состоит из 100500 цветов, плавно переходящих один в другой. Кто создал все эти переходы?
Как мы могли заметить, OpenGL достаточно замкнутая в себе вещь, с которой трудно общаться извне, поэтому многие операции там сделаны по умолчанию. Например, когда рисуется треугольник, то координаты каждого пиксела интерполируются между координатами вершин. Точно так же интерполируются и цвета между вершинами.
Поэтому, когда пиксельный шейдер получает цвет, этот цвет уже автоматически интерполирован в соответствии с координатами пиксела.
Такое поведение не всегда требуется, и его можно поменять. Например, заставить OpenGL не интерполировать цвет, а отдавать его фиксированным от какой-то вершины. Эта вершина называется "провоцирующей" (provoking).
Но эти детали пока не нужны, займёмся ими когда-нибудь потом.
Вступительную часть работы с OpenGL можно считать завершённой. Постепенно разберёмся с текстурами, освещением, преобразованиями, и 3-мерной графикой (надеюсь).
Текущая версия кода лежит на github в ветке layouts.
Читайте дальше: Оптимизации, VAO и uniform