Всем привет! Решил систематизировать свои знания. Просьба относится к тексту ниже, как к вольному сочинению на тему «OpenGLES». Спасибо за понимание!
Вершинные координаты
В OpenGL используется правосторонняя система координат. Обычно направления координат X, Y и Z проверяются на пальцах правой руки. Большой палец — это X, указательный — Y, а средний — Z. По умолчанию X направлен вправо, Y вверх, а Z смотрит на нас.
В двумерном пространстве координата X обычно направлена вправо, Y — вниз, а Z игнорируют. Центр системы координат слева сверху, но иногда центр совпадает с центром экрана.
Экранные координаты
Координаты экрана представляют собой две оси: горизонтальная и вертикальная (X и Y соответственно).
Горизонтальная ось направлена вправо, вертикальная — вверх. Центр экрана — это начало координат.
Координаты углов экрана: левый верхний (-1.0, 1.0), левый нижний (-1.0, -1.0), правый нижний (1.0, -1.0), правый верхний (1.0, 1.0).
Текстурные координаты
Текстурные координаты задаются от левого нижнего угла. Оси текстурных координат называются по-разному: (U, V) или (S, T).
Горизонтальная ось U или S направлена слева направо. Вертикальная ось V или T направлена вверх.
Для наложения текстуры один в один, координаты должны быть в пределах от нуля до единицы. Если задать координаты больше единицы, то в OpenGL можно задать несколько вариантов обработать такую ситуацию: повторение текстуры, зеркалирование, повторение крайних пикселей.
Немного о треугольниках
По умолчанию рисование треугольников идет против часовой стрелки. Существуют три способа задать вершины:
GL_TRIANGLES
— по три вершины на треугольникGL_TRIANGLE_STRIP
— три вершины на треугольник, затем каждая следующая с двумя предыдущимиGL_TRIANGLE_FAN
— представляет собой веер, где первая вершина является центральной точкой веера, а остальные добавляют треугольники к вееру, подробнее в википедии
Для создания прямоугольника с помощью GL_TRIANGLE_STRIP
используется такой порядок вершин (при обходе против часовой стрелки): левый нижний, правый нижний, левый верхний, правый верхний.
Шейдеры
Шейдер состоит из двух частей: вершинный
шейдер и пиксельный
шейдер.
На входе шейдер получает данные текущей вершины (например, позиция точки, цвет пикселя или текстурные координаты) и параметры общие для всего шейдера (матрица транформации, общая прозрачность объекта и так далее).
На выходе вершинный шейдер выдает координаты точки (с помощью gl_Position
), а пиксельный — цвет точки (gl_FragColor
).
источник картинки
Данные текущей вершины называются аттрибутами и задаются с помощью ключевого слова "attribute"
в самом шейдере, а передаются в него процедурой glVertexAttribPointer()
.
Общие параметры для всего шейдера задаются с помощью ключевого слова "uniform"
и передача их идет процедурой glUniform()
.
Также есть ключевое слово "varying"
, им обозначаются параметры, которые передаются от вершинного шейдера к пиксельному. К этим параметрам применяется интерполяция от вершине к вершине, потому что вершин мало, а пикселей много. Поэтому вершинный шейдер вызывается реже, а пиксельный чаще.
Для установки параметров типа uniform
используется такой код:
// Получаем ссылку на параметр "uniformName" в шейдере GLuint uniformId = glGetUniformLocation(shader, "uniformName"); // Устанавливаем значение параметра glUniform(uniformId, uniformValue); |
Поскольку параметр типа uniform
может представлять собой матрицу или массив 2-4 элементов, функция glUniform()
имеет множество модификаций (glUniform1i
, glUniform4f
и так далее).
Параметры типа attribute
задаются для каждой вершины. У каждого аттрибута необходимо знать две вещи: количество элементов в аттрибуте и тип элемента. Например, позиция вершины в 3D координатах XYZ имеет три элемента тип GL_FLOAT
, а текстурные координаты UV имеют соответственно два элемента и тип GL_FLOAT
.
Аттрибуты для вершин можно представить двумя способами.
В первом случае это просто массив для каждого типа аттрибутов:
// Позиция каждой вершины float [] position = { x0, y0, z0, x1, y1, z1, ..., xN, yN, zN }; // Текстурные координаты каждый вершины float [] texCoord = { u0, v0, u1, v1, ..., uN, vN }; |
Передача аттрибутов в шейдерв таком виде элементарна и не представляет сложности:
// Получаем ссылку на аттрибут "position" в шейдере positionId = glGetAttribLocation(shader, "position"); // Передаем массив аттрибутов с тремя элементами типа float для каждой вершины glVertexAttribPointer(positionId, 3, GL_FLOAT, GL_FALSE, 0, position); // Включаем аттрибут glEnableVertexAttribArray(positionId); // Получаем ссылку на аттрибут "texCoord" в шейдере texCoordId = glGetAttribLocation(shader, "texCoord"); // Передаем массив аттрибутов с двумя элементами типа float для каждой вершины glVertexAttribPointer(texCoordId, 2, GL_FLOAT, GL_FALSE, 0, texCoord); // Включаем аттрибут glEnableVertexAttribArray(texCoordId); |
Во втором случае все аттрибуты задаются в одном массиве и называется такой буфер interleaved
:
// Данные для вершин float [] vertices = { x0, y0, z0, u0, v0, x1, y1, z1, u1, v1, ..., xN, yN, zN, uN, vN }; |
Код для передачи в шейдер похож на первый случай:
// Размер аттрибутов для одной вершины в байтах (5 элементов типа float) #define VERTEX_SIZE (5 * sizeof(float)) // Начало 3D координат в буфере (координаты идут с самого начала буфера) #define POS_OFFSET 0 // Начало текстурных координат в буфере (текстурные // координаты начинаются сразу после трех 3D координат #define TEX_OFFSET 3 // Получаем ссылку на аттрибут "position" в шейдере positionId = glGetAttribLocation(shader, "position"); // Передаем массив аттрибутов с тремя элементами типа float для каждой вершины glVertexAttribPointer( positionId, 3, GL_FLOAT, GL_FALSE, VERTEX_SIZE, vertices + POS_OFFSET); // Включаем аттрибут glEnableVertexAttribArray(positionId); // Получаем ссылку на аттрибут "texCoord" в шейдере texCoordId = glGetAttribLocation(shader, "texCoord"); // Передаем массив аттрибутов с двумя элементами типа float для каждой вершины glVertexAttribPointer( texCoordId, 2, GL_FLOAT, GL_FALSE, VERTEX_SIZE, vertices + UV_OFFSET); // Включаем аттрибут glEnableVertexAttribArray(texCoordId); |
Как нетрудно заметить, во втором случае ссылка на данные фактически передается два раза. Умные люди придумали выход из этой ситуации и называется он VertexBufferObject. Вкратце идея такая: создаем один раз буфер, а затем в glVertexAttribPointer()
передаем только смещения от начала.
Ходят слухи, что отрисовка во втором случае с использованием VertexBufferObject
гораздо быстрее, обычной, но мои эксперименты особого выигрыша не выявили.