Размещение обектов сцены в OpenGL

Проект
Исходники проекта [5]Описание
Предположим, что у нас есть некоторая 3D модель. Те, кто работал во всевозможных 3D редакторах, знают что модели проектируются таким образом, чтобы начало координат находилось в центре обекта. Во всяком случае, если это не так, то начало координат расположено близко к набору вершин, составляющих модель.
Если отобразить несколько таких обектов в рамках одной сцены, то их начала координат совпадут с началом координат сцены. Все объекты будут расположены в одном месте. В случае системы NDC (Native Device Coordinates) — в центре сцены.
Кроме того, при загрузке модели в память и формировании объекта VAO (Vertex Array Object), учитываются именно локальные координаты вершин без привязки к какому-либо месту на отображаемой сцене. Вместе с тем флаг GL_STATIC_DRAW говорит о том, что координаты и другие параметры вершин объекта не будут менятся в рамках данного массива. Но никто не проектирует статичные модели с учетом их положения на сцене.
Для того чтобы разместить объект в нужном месте сцены, используют соответствующие преобразования координат вершин в коде vertex шейдера. В случае трехмерного пространства все преобразования осуществляются путем умножения вектора с координатами вершины на матрицу размера 4х4 слева.
Вектор, содержащий координаты вершины, имеет вид: v = \begin{pmatrix} x \\ y \\ z \\ 1.0 \end{pmatrix}. Последний компонент существует для корректности операции умножения на матрицу и, кроме того, участвует в работе z-буфера.
Масштабирование
Пусть вектор v соответствует одной из вершин объекта.
Масштабирование осуществляется путем регулирования расстояния от вершины до начала координат: v_{1} = \begin{pmatrix} dx \ dy \ dz \ 1.0 \end{pmatrix} , где d — показатель, определяющий во сколько раз результирующий обект больше (меньше) оригинального.
Получить такой вектор можно путем произведения исходного вектора на матрицу:
v_{1} = \begin{pmatrix} d & 0 & 0 & 0 \\ 0 & d & 0 & 0 \\ 0 & 0 & d & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \times \begin{pmatrix} x \\ y \\ z \\ 1.0 \end{pmatrix} = \begin{pmatrix} dx \\ dy \\ dz \\ 1.0 \end{pmatrix}Перемещение
Чтобы переместить объект в пространстве, достаточно к векторам вершин объекта прибавить вектор перемещения. Пусть вектор t соответствует перемещению, а вектор v соответствует вершине, тогда результирующий вектор можно получить следующим образом:
v_{1} = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{pmatrix} \times \begin{pmatrix} x \\ y \\ z \\ 1.0 \end{pmatrix} = \begin{pmatrix} x + t_x \\ y + t_y \\ z + t_z \\ 1.0 \end{pmatrix},где вектор перемещения:
t = \begin{pmatrix} t_x \\ t_y \\ t_z \end{pmatrix} \\Следует отметить, что все преоброзования можно производить последовательно, умножая результат предыдущего преобразования на матрицу (слева), соответствующую текущему преобразованию:
v_{1} = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{pmatrix} \times \begin{pmatrix} d & 0 & 0 & 0 \\ 0 & d & 0 & 0 \\ 0 & 0 & d & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \times \begin{pmatrix} x \\ y \\ z \\ 1.0 \end{pmatrix} = \begin{pmatrix} dx + t_x \\ dy + t_y \\ dz + t_z \\ 1.0 \end{pmatrix},В силу ассоциативности операций матричного умножения, выражение для v_1 можно представить так:
v_{1} = \begin{pmatrix} d & 0 & 0 & t_x \\ 0 & d & 0 & t_y \\ 0 & 0 & d & t_z \\ 0 & 0 & 0 & 1 \end{pmatrix} \times \begin{pmatrix} x \\ y \\ z \\ 1.0 \end{pmatrix} = \begin{pmatrix} dx + t_x \\ dy + t_y \\ dz + t_z \\ 1.0 \end{pmatrix},Вращение
Рассмотрим случай поворота объекта вокруг оси z. В системе NDC (Normalized Device Coordinates) ось z соответствует направлению «взгляда» камеры.
Матрица поворота вектора на угол \alpha будет иметь следующий вид:
M_{rot_z} = \begin{pmatrix} \cos \alpha & -\sin \alpha & 0 & 0 \\ \sin \alpha & \cos \alpha & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}Так как поворот объекта осуществляется вокруг оси z в системе координат сцены, целесообразно произвести поворот до параллельного переноса. В таком случае объект повренется вокруг своей оси. В противном случае поворот будет осуществлятся вокруг оси, проходящей через центр сцены.
Матрицы поворота вокруг других осей будут выглядеть аналогично:
Объединение всех преобразований в одном выражении
Так как масштабирование и поворот следует выполнять первыми, получаем следующую формулу для преобразования вершин объекта:
v_1 = M_{trans} \times M_{rot} \times M_{scale} \times v, где \\ \\ M_{trans} = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{pmatrix}, M_{rot} = M_{rot_z}, M_{scale} = \begin{pmatrix} d & 0 & 0 & 0 \\ 0 & d & 0 & 0 \\ 0 & 0 & d & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}
Исходный код
Здесь я опишу только ту часть проекта, которая отвечает за преобразования координат. Так как в качестве объекта сцены выбран прямоугольник, то для описания всех его параметров, кроме координат вершин в VAO, выбрана структура:
typedef struct { float ms; float tr[3]; float angle; unsigned int tex; float trans[16]; } MyBox;
Здесь ms — параметр, определяющий масштаб, tr — вектор перемещения, angle — угол поворота, tex — идентификатор текстуры, trans — матрица, равная произведению матриц переноса, масштабирования и поворота.
Следующие методы позволяют сформировать матрицу преобразования координат вершин, выделить память для структуры, описывающей обект сцены и заполнить её данными, удалить обект из памяти:
void makeBoxTrans(MyBox* box) { float trans[] = { box->ms*cos(box->angle), -box->ms*sin(box->angle), 0.0, box->tr[0], box->ms*sin(box->angle), box->ms*cos(box->angle), 0.0, box->tr[1], 0.0, 0.0, box->ms, box->tr[2], 0.0, 0.0, 0.0, 1.0 }; memcpy(box->trans, trans, sizeof(trans)); }; MyBox* makeBox(float ms, float tx, float ty, float tz, float angle, unsigned int tex) { MyBox* box = (MyBox*)malloc(sizeof(MyBox)); box->ms = ms; box->tr[0] = tx; box->tr[1] = ty; box->tr[2] = tz; box->tex = tex; box->angle = angle; makeBoxTrans(box); return box; }; void freeMyBox(MyBox* box) { free(box); };
Следующий фрагмент описывает процесс создания набора «коробок», в котором параметры мастабирования, перемещения и поворота — случайные величины:
MyBox* boxes[BOX_COUNT]; for(int i=0; i<BOX_COUNT-1; i++) { float ms = (float)(rand() % 100) / 500.0 + 0.1; float tx = (float)(rand() % 150) / 100.0 - 0.75; float ty = (float)(rand() % 150) / 100.0 - 0.75; float tz = (float)(rand() % 150) / 100.0 - 0.75; float angle = (float)(rand() % 360) / 360 * 6.28; boxes[i] = makeBox(ms, tx, ty, tz, angle, tex_box); }; boxes[BOX_COUNT-1] = makeBox(1.0, 0.0, 0.0, 0.1, 0.0, tex_back);
Последний элемет в массиве boxes — это фон сцены, тоже представляющий из себя коробку, но с другой текстурой и занимающую всё видимое пространство по осям x и y.
Чтобы корректно отобразить объекты сцены, необходимо передать матрицу, описывающую преобразования вершин в vertex шейдер. Предположим, что в vertex шейдере переменная с именем trans должна содержат такую матрицу, тогда получим идентификатор расположения этой переменной следующим образом:
unsigned int uniform_trans = glGetUniformLocation(shaderProgram, "trans");
Теперь в цикле рисования сцены достаточно добавить следующий код:
for(int i=0; i<BOX_COUNT; i++) { glUseProgram(shaderProgram); /* Активируем шейдерную программу */ glUniformMatrix4fv(uniform_trans, 1, GL_FALSE, boxes[i]->trans); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, boxes[i]->tex); glBindVertexArray(VAO); /* Активируем массив с данными о вершинах */ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); /* Отображаем треугольник */ };
В выделенной строке мы загружаем матрицу в vertex шейдер. Первый аргумент — идентификатор переменной шейдера, второй агрумент — количество матриц, третий аргумент — флаг, определяющий — будет ли матрица транспонирована при передаче, червертый аргумент — указатель на блок данных матрицы.
Система OpenGL воспринимет матрицы так, как если бы номер столбца указывался первым. Следовательно, в коде шейдера необходимо поменять порядок умножения, либо указать третим аргументом метода glUniformMatrix4fv флаг GL_TRUE.
Код vertex шейдера:
#version 300 es layout (location = 0) in vec3 aPos; layout (location = 1) in vec2 tCoords; out vec3 pos; out vec2 tcoords; uniform mat4 trans; void main() { pos = aPos; tcoords = tCoords; gl_Position = vec4(aPos, 1.0f) * trans; }
Выделенная строка содержит описание глобальной переменной, содержащей данные матрицы преобразования.
Добавить комментарий