Реализация OpenGL Java Matrix4f

66
6

Я пытаюсь написать класс с плавающей матрицей 4 * 4 для создания 3D-пространства с матрицами моделей, представлений и проекций. В своем текущем состоянии, когда я пытаюсь повернуть матрицу вида, он, похоже, также применяет перевод, и пространство искажается (как будто оно было сжато). Вычисление проекции, вида и модели выполняется в вершинном шейдере.

Edit5: Нерабочее состояние функций преобразования находится ниже:

public class Mat4f {
public float m00, m10, m20, m30,
m01, m11, m21, m31,
m02, m12, m22, m32,
m03, m13, m23, m33;

public Mat4f() {
loadIdentity();
}

public Mat4f loadIdentity() {
m00 = 1.0f; m10 = 0.0f; m20 = 0.0f; m30 = 0.0f;
m01 = 0.0f; m11 = 1.0f; m21 = 0.0f; m31 = 0.0f;
m02 = 0.0f; m12 = 0.0f; m22 = 1.0f; m32 = 0.0f;
m03 = 0.0f; m13 = 0.0f; m23 = 0.0f; m33 = 1.0f;
return this;
}

public Mat4f store(FloatBuffer buffer) {
buffer.put(m00);
buffer.put(m01);
buffer.put(m02);
buffer.put(m03);

buffer.put(m10);
buffer.put(m11);
buffer.put(m12);
buffer.put(m13);

buffer.put(m20);
buffer.put(m21);
buffer.put(m22);
buffer.put(m23);

buffer.put(m30);
buffer.put(m31);
buffer.put(m32);
buffer.put(m33);

buffer.flip();

return this;
}

public Mat4f loadPerspective(float fov, float ratio, float near, float far) {
m11 = (float) (1.0f / (Math.tan(fov / 2.0f)));
m00 = m11 / ratio;
m22 = -(far + near) / (far - near);
m23 = -1.0f;
m32 = -2.0f * far * near / (far - near);
m33 = 0.0f;
return this;
}

public Mat4f translate(float x, float y, float z) {
m30 = x;
m31 = y;
m32 = z;
return this;
}

public Mat4f scale(float x, float y, float z) {
m00 = x;
m11 = y;
m22 = z;
return this;
}

public Mat4f rotateX(float x) {
m11 = (float) Math.cos(x);
m12 = (float) Math.sin(x);
m21 = (float) -(Math.sin(x));
m22 = (float) Math.cos(x);
return this;
}

public Mat4f rotateY(float y) {
m00 = (float) Math.cos(y);
m02 = (float) -(Math.sin(y));
m20 = (float) Math.sin(y);
m22 = (float) Math.cos(y);
return this;
}

public Mat4f rotateZ(float z) {
m00 = (float) Math.cos(z);
m01 = (float) Math.sin(z);
m10 = (float) -(Math.sin(z));
m11 = (float) Math.cos(z);
return this;
}
}

И правильный способ сделать это:

public Mat4f translate(float x, float y, float z, Mat4f dest) {
dest.m00 = m00;
dest.m01 = m01;
dest.m02 = m02;
dest.m03 = m03;
dest.m10 = m10;
dest.m11 = m11;
dest.m12 = m12;
dest.m13 = m13;
dest.m20 = m20;
dest.m21 = m21;
dest.m22 = m22;
dest.m23 = m23;
dest.m30 = m00 * x + m10 * y + m20 * z + m30;
dest.m31 = m01 * x + m11 * y + m21 * z + m31;
dest.m32 = m02 * x + m12 * y + m22 * z + m32;
dest.m33 = m03 * x + m13 * y + m23 * z + m33;
return this;
}

public Mat4f translate(float x, float y, float z) {
return translate(x, y, z, this);
}

public Mat4f scale(float x, float y, float z, Mat4f dest) {
dest.m00 = m00 * x;
dest.m01 = m01 * x;
dest.m02 = m02 * x;
dest.m03 = m03 * x;
dest.m10 = m10 * y;
dest.m11 = m11 * y;
dest.m12 = m12 * y;
dest.m13 = m13 * y;
dest.m20 = m20 * z;
dest.m21 = m21 * z;
dest.m22 = m22 * z;
dest.m23 = m23 * z;
dest.m30 = m30;
dest.m31 = m31;
dest.m32 = m32;
dest.m33 = m33;
return this;
}

public Mat4f scale(float x, float y, float z) {
return scale(x, y, z, this);
}

public Mat4f rotateX(float x, Mat4f dest) {
float cos = (float) Math.cos(x);
float sin = (float) Math.sin(x);
float rm11 = cos;
float rm12 = sin;
float rm21 = -sin;
float rm22 = cos;

float nm10 = m10 * rm11 + m20 * rm12;
float nm11 = m11 * rm11 + m21 * rm12;
float nm12 = m12 * rm11 + m22 * rm12;
float nm13 = m13 * rm11 + m23 * rm12;

dest. m20 = m10 * rm21 + m20 * rm22;
dest.m21 = m11 * rm21 + m21 * rm22;
dest.m22 = m12 * rm21 + m22 * rm22;
dest. m23 = m13 * rm21 + m23 * rm22;

dest.m10 = nm10;
dest.m11 = nm11;
dest.m12 = nm12;
dest.m13 = nm13;

return this;
}

public Mat4f rotateX(float x) {
return rotateX(x, this);
}

public Mat4f rotateY(float y, Mat4f dest) {
float cos = (float) Math.cos(y);
float sin = (float) Math.sin(y);
float rm00 = cos;
float rm02 = -sin;
float rm20 = sin;
float rm22 = cos;

float nm00 = m00 * rm00 + m20 * rm02;
float nm01 = m01 * rm00 + m21 * rm02;
float nm02 = m02 * rm00 + m22 * rm02;
float nm03 = m03 * rm00 + m23 * rm02;

dest.m20 = m00 * rm20 + m20 * rm22;
dest.m21 = m01 * rm20 + m21 * rm22;
dest.m22 = m02 * rm20 + m22 * rm22;
dest.m23 = m03 * rm20 + m23 * rm22;

dest.m00 = nm00;
dest.m01 = nm01;
dest.m02 = nm02;
dest.m03 = nm03;

return this;
}

public Mat4f rotateY(float y) {
return rotateY(y, this);
}

public Mat4f rotateZ(float z, Mat4f dest) {
float cos = (float) Math.cos(z);
float sin = (float) Math.sin(z);
float rm00 = cos;
float rm01 = sin;
float rm10 = -sin;
float rm11 = cos;

float nm00 = m00 * rm00 + m10 * rm01;
float nm01 = m01 * rm00 + m11 * rm01;
float nm02 = m02 * rm00 + m12 * rm01;
float nm03 = m03 * rm00 + m13 * rm01;

dest.m10 = m00 * rm10 + m10 * rm11;
dest.m11 = m01 * rm10 + m11 * rm11;
dest.m12 = m02 * rm10 + m12 * rm11;
dest.m13 = m03 * rm10 + m13 * rm11;

dest.m00 = nm00;
dest.m01 = nm01;
dest.m02 = nm02;
dest.m03 = nm03;

return this;
}

public Mat4f rotateZ(float z) {
return rotateZ(z, this);
}

Чтобы изменить матрицы, я использовал следующий порядок преобразований:

public void transform() {
mMat.loadIdentity();
mMat.translate(position.x, position.y, position.z);
mMat.rotateX((float) Math.toRadians(orientation.x));
mMat.rotateY((float) Math.toRadians(orientation.y));
mMat.rotateZ((float) Math.toRadians(orientation.z));
mMat.scale(scale.x, scale.y, scale.z);
}

public void updateCamera() {
Vec3f position = World.camera.getPosition();
vMat.loadIdentity();
vMat.rotateX((float) Math.toRadians(World.camera.getPitch()));
vMat.rotateY((float) Math.toRadians(World.camera.getYaw()));
vMat.translate(-position.x, -position.y, position.z);
}

Редактирование: проекция перспективы прекрасна, а также перевод, но если я храню модельную матрицу в Mat4f, то вращение модели следует за камерой.

Edit2: ориентация модели больше не следует за поворотом камеры, когда я использую Mat4f в качестве матрицы модели. Матрица, трансляция и масштабирование проекции хорошо работают.

Edit3: Отредактированный код, применяемое вращение не является полным вращением круга, модель качается влево и вправо.

Edit4: Я попытался сделать вращение с матричным умножением

спросил(а) 2016-03-22T18:19:00+03:00 4 года, 3 месяца назад
1
Решение
101

Вопрос в том, что я не принимал во внимание более ранние преобразования. Однако я не использовал матричное умножение. Я скопировал соответствующие части из JOML, которые вы можете найти по адресу: https://github.com/JOML-CI/JOML. Я отредактирую вопрос, чтобы отразить, в чем проблема, и каково конечное состояние класса. Спасибо JOML contributors и Derhass за помощь!

ответил(а) 2016-03-24T21:13:00+03:00 4 года, 3 месяца назад
39

Ваш матричный класс не реализует состав преобразований. Каждый из ваших матричных методов просто перезаписывает определенные элементы.

Например, рассмотрите следующую последовательность операций:

translate(1,1,1);
rotateX(45);
translate(1,1,1);

Такая последовательность математически должна быть представлена матричным умножением T * R * T (где T и R - основные матрицы перевода/поворота с соответствующими параметрами).

Однако то, что делает ваш код, это просто создание T (потому что изначально матрица является идентификатором), перезапись некоторых частей для вращения и, наконец, перезапись части перевода снова.

Чтобы исправить ваш код, вам необходимо реализовать правильный метод многоуровневой матрицы и фактически использовать его всякий раз, когда вы хотите применить дальнейшее преобразование к матрице. Возможно, было бы полезно создать некоторые методы для базовых матриц преобразования.

Тем не менее, это по-прежнему выглядит так, как обычно для меня.

Ваш макет членов mXY предполагает, что вы используете математическое соглашение, где m12 будет второй строкой, третьим столбцом. Затем ваш метод store помещает матрицу в буфер с основным расположением столбцов. Такой буфер может быть непосредственно использован устаревшими функциями матрицы GL (например, glLoadMatrix или glMultMatrix). Он также был бы "стандартным" макетом для матричных униформ, где для параметра transpose задано значение GL_FALSE.

Однако, глядя на ваш метод translate:

public Mat4f translate(float x, float y, float z) {
m30 = x;
m31 = y;
m32 = z;
return this;
}

Это установило бы вектор перевода в последнюю строку, а не в последний столбец. (Все остальные функции также используют транспонированный макет).

В настоящее время GL использует matrix * vector соглашение (в отличие от vector * matrix которую предпочитает D3D). В этом случае ваши матрицы переносятся на то, что ожидает GL. Если вы используете шейдеры, то вам решать, какой порядок умножения вы используете, но он должен соответствовать соглашению, которое вы используете для своих матриц, и M * v == v^T * M^T

ответил(а) 2016-03-23T20:20:00+03:00 4 года, 3 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема