Неожиданно для себя обнаружил, что совершенно не понимаю как работают проекционная и видовая матрицы.
Как бы высокоуровнево пишут, что проекционная задает способ проецирования (ортография, перспектива) и пространство отсечения (куб или усеченная пирамида).
Видовая матрица отвечает за положение объекта перед камерой.
У меня есть вот такой простенький вертексный шейдер (видовая матрица пока что просто единичная)
attribute vec4 Position;
uniform mat4 Projection;
uniform mat4 ModelView;
void main()
{
gl_Position = Projection * ModelView * Position;
}так вот, иду я значит в документацию, например сюда https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd373965(v=vs.85).aspx и смотрю как стряпается ортографическая матрица, переношу ее, закидываю в нее параметры ширины и высоты экрана. На экране при этом рисуется совершенно абсурдная бурда.
float projection[] =
{
2.0f / (right - left), 0.0f, 0.0f, -(right + left) / (right - left),
0.0f, 2.0f / (top - bottom), 0.0f, -(top + bottom) / (top - bottom),
0.0f, 0.0f, -2.0f / (far - near), -(far + near) / (far - near),
0.0f, 0.0f, 0.0f, 1.0f
};
GLint projectionUniform = glGetUniformLocation(shader.program, "Projection");
glUniformMatrix4fv(projectionUniform, 1, GL_FALSE, projection);видовая, опять-таки повторюсь, просто единичная
float modelView[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; GLint modelviewUniform = glGetUniformLocation (shader.program, "ModelView"); glUniformMatrix4fv( modelviewUniform, 1, GL_FALSE, modelView);
Треугольник точно правильный, состоит из следующих вертексов
GLfloat v[] =
{
-0.5, -0.5,
0.0, 0.5,
0.5, -0.5
};Как бы, превые два диагональных значения проекционной матрицы я могу объяснить - это мы "сжимаем" наш ортонормированный базис и сжать его надо в половину экранных размеров, потому что нуль в середине, а единицы по краям (не знаю как объяснить понятнее). по этому для того, чтобы получить положение вертекса на экране надо домножить его на 1/size/2 или преобразуя дробь - на 2/size.
Что за near и far? как их вообще можно обозначить? в особенности при ортографичесокой проекции? Этож по хорошему можно смотреть очень далеко и все равно все видеть. И почему по оси Z изменен знак?
И если я все правильно понимаю, то тут выходит, что мы умножаем матрицу на вектор (а не наоборот), а такое преобразование портит вектору значение параметра w делая его равным tx*x + ty*y + tz*z + w. Я так понимаю, что это все и портит. Зачем это нужно?
float projection[] = { 2.0f / (right - left), 0.0f, 0.0f, -( right + left) / ( right - left), 0.0f, 2.0f / ( top - bottom), 0.0f, -( top + bottom) / ( top - bottom), 0.0f, 0.0f, -2.0f / ( far - near), -( far + near) / ( far - near), 0.0f, 0.0f, 0.0f, 1.0f };
В OpenGL матрицы column-major.
Правка:
Хранятся column-major, в смысле.
>Что за near и far?
Передняя и задняя плоскости отсечения. Этап отсечения всё-равно в конвеере присутствует. Для перспективной - это помогает не делить на 0. Для ортогональной - конкретно это менее критично. Но диапазон и точность буфера глубины всё-равно от них зависят.
>И почему по оси Z изменен знак?
В системе координат по умолчанию OpenGL "вперёд" - это -z. Но near и far принимаются с положительным знаком (для удобства?). Соответственно в матрице он меняется.
>а такое преобразование портит вектору значение параметра w делая его равным tx*x + ty*y + tz*z + w
Вот такого быть не должно, если не ошибаюсь.
> https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd373965(v=vs.85).aspx
https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd373965… vs.85%29.aspx
Скобочки можно эскейпать (%28, %29). Мелочь, а приятно.
FordPerfect
Ну, давайте вместе посмотрим, и если что вы меня поправите. Смотрите, у меня есть радиус-вектор
x, y, z, w
и проекционная матрица
nx, 0, 0, tx 0, ny, 0, ty 0, 0, nz, tz 0, 0, 0, 1
умножаем матрицу на вектор, столбцы на строки
{nx * x + 0 * y + 0 * z + 0 * w},
{0 * x + ny * y + 0 * z + 0 * w},
{0 * x + 0 * y + nz * z + 0 * w},
{tx * x + ty * y + tz * z + 1 * w}или в сокращенной записи
{nx * x}
{ny * y}
{nz * z}
{tx * x + ty * y + tz * z + 1 * w}Такая вот ерунда выходит. И ведь просто ладно бы если я где-то при этом накосячил, но ведь при отрисовке вообще какая-то ересь получается.
там есть красивая картинка где-то посередине
http://www.scratchapixel.com/lessons/3d-basic-rendering/perspecti… line-clipping
Fennec
Математически спецификация OpenGL сформулирована в терминах вектор-столбцов:
\(A=
\begin{bmatrix}
n_x & 0 & 0 & t_x\\
0 & n_y & 0 & t_y\\
0 & 0 & n_z & t_z\\
0 & 0 & 0 & 1
\end{bmatrix}\), \(\mathbf{b}=
\begin{bmatrix}
x\\
y\\
z\\
w
\end{bmatrix}\)
\(A \cdot \mathbf{b} =
\begin{bmatrix}
n_x x + t_x w\\
n_y y + t_y w\\
n_z z + t_z w\\
w
\end{bmatrix}\)
FordPerfect
Окей, тогда у меня такой вопрос:
Операция умножения двух матриц выполнима только в том случае, если число столбцов в первом сомножителе равно числу строк во втором
получается, что при таком представлении мы не можем умножить вектор на матрицу?
codingmonkey
минуту, сейчас гляну статью
Fennec
Ну да, вектор-столбец 4x1 на матрицу 4x4 множится только в одном порядке: матрица*вектор. Обратно - не определено.
Другое дело, что GLSL это позволяет, превращая вектор в строку (и обратно):
https://en.wikibooks.org/wiki/GLSL_Programming/Vector_and_Matrix_… ons#Operators
If a vector is multiplied to a matrix from the left, the result corresponds to multiplying a row vector from the left to the matrix. This corresponds to multiplying a column vector to the transposed matrix from the right:

FordPerfect
то есть выходит GLSL делает эти операции коммутативными?
Тогда выходит, что ортографическая матрица - это просто матрица масштабирования и перемещения?
и все же, в моем случае получается такая история: например возьмем разрешение экрана 800х600, а начало координат хочу чтобы было в левом верхнем углу, тогда параметры должны быть
float left = 0; float right = width; float bottom = -height; float top = 0; float far = -1; float near = 1;
1 / 400, 0, 0, -1,
0, 1 / -300, 0, -1,
0, 0, 1 0,
0, 0, 0, 1сжимаем ось х в 400 раз
сжимаем ось у в 300 раз
ось z не трогаем
но по 2 осям отступаем на 1 назад (кстати внезапно стало понятно, зачем такие значения для far и near). Вопрос: для чего? В смысле, я же хотел их подвинуть в совсем другое место, что я делаю не так?
Fennec
>но по всем трем осям отступаем на 1 назад. Вопрос: для чего?
В OpenGL отсечение происходит по \([-1; \, +1] \times [-1; \, +1] \times [-1; \, +1]\). Соответственно к этим координатам и приводится.
Строго говоря, отсечение происхожит в однородных координатах (clip space):
-w < x < +w
-w < y < +w
-w < z < +w
https://www.opengl.org/wiki/Vertex_Post-Processing
Правка: обобщённых → однородных.
Fennec
>то есть выходит GLSL делает эти операции коммутативными?
Нет.
Справа:
\(A \cdot b\)
Слева:
\((b^T \cdot A)^T=A^T \cdot b\)
А вообще, это ж не секретные вещи и они есть в OpenGL Programming Guide: The Official Guide to Learning OpenGL (в народе "Red Book").
FordPerfect
О, книжка.
Окей, спасибо большое за все объяснения и ссылки!
На самом деле нет разницы какой математикой определять видимые вершины (которые попадают в область камеры), но системе нужно как то определить запускать ли фрагментный шейдер или переходить к следующего примитиву в вершинном, и было принято решение ввести стандарт: программист любым способом должен привести координаты вершины в локальные координаты камеры, и если после этого у всех трех координат полигона хотя бы одна компонента x,y,z не входят в диапазон от -w до w, тогда фрагментный шейдер вообще не запускается, поскольку все три вершины полигона лежат вне области камеры, а значит примитив не виден. Это касается и оргогональной и перспективной проекции. Вот русское подробное описание общепринятой математики такого преобразования координат, где объясняется что происходит с "w" компонентой вектора и почему параметры в матрице именно такие - http://gamesmaker.ru/programming/directx/virtualnaya-kamera-persp… ya-proekciya/ (на сайте описывается решение в DirectX которое по сути ничем не отличается от OpenGL).
Phisix
И вам большое спасибо за информацию.
Похоже я разобрался.
Тема в архиве.