Всем привет! Решил систематизировать свои знания. Просьба относится к тексту ниже, как к вольному сочинению на тему «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); |
// Получаем ссылку на параметр "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
}; |
// Позиция каждой вершины
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); |
// Получаем ссылку на аттрибут "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
}; |
// Данные для вершин
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); |
// Размер аттрибутов для одной вершины в байтах (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
гораздо быстрее, обычной, но мои эксперименты особого выигрыша не выявили.