如何使用 JOML 模拟 OpenGL 模型,在 3D 投影到 2D 平面上查看矩阵?
how to use JOML to simulate OpenGL like Model, View Matrices on 3D projection to 2D plane?
经过多年学习 OpenGL 初学者课程和线性代数课程,我最近终于理解了模型、视图和投影矩阵的意义。基本上,模型矩阵将 3D 模型的顶点坐标转换为 3D 世界中的顶点坐标(相对于 3D 世界的原点平移、旋转和缩放模型)。 View Matrix 将 3D 世界的顶点坐标转换为相对于相机的顶点坐标(通常只是世界相对于相机的平移和旋转)而 Projection Matrix 用于 compute/convert Camera 中的顶点坐标查看二维平面(通常是屏幕)上的投影。
我正在尝试在没有 OpenGL 的情况下在 2D 平面上的 3D 投影中创建相机系统,但是使用 JOML,它是一个 Java OpenGL 的数学(主要是线性代数数学)库,经常与LightWeight Java 游戏库 3. 我能够在 OpenGL 中创建一个相机系统,使用上述 3 个矩阵非常容易。但是当我使用相同的精确矩阵(和一些额外的代码以便投影出现在屏幕上)时,我只能在 2D 平面上进行投影。模型矩阵和视图矩阵似乎对模型在屏幕上的投影方式没有任何影响。
这是我用来在屏幕上投影立方体的代码:
private float theta = 0;
@Override
public void render(Graphics g) {
Vector3f cube3f[][] = {
// SOUTH
{ new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f) },
{ new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, -0.5f, -0.5f) },
// EAST
{ new Vector3f( 0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f) },
{ new Vector3f( 0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, -0.5f, 0.5f) },
// NORTH
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f) },
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, 0.5f) },
// WEST
{ new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, -0.5f) },
{ new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f(-0.5f, -0.5f, -0.5f) },
// TOP
{ new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, 0.5f) },
{ new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, -0.5f) },
// BOTTOM
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, -0.5f) },
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, -0.5f, -0.5f) },
};
Vector4f cube4f[][] = new Vector4f[cube3f.length][cube3f[0].length];
for(int i = 0; i < cube3f.length; i++) {
for(int j = 0; j < cube3f[i].length; j++) {
Matrix4f modelMatrix = new Matrix4f()
.rotate((float)Math.toRadians(theta), new Vector3f(0.0f, 1.0f, 0))
.rotate((float)Math.toRadians(theta), new Vector3f(1.0f, 0, 0))
.translate(new Vector3f(0, 5, 5)); // this is supposed to move the cube up 5 units and away 5 units
Vector4f tempvec = new Vector4f(cube3f[i][j], 0.0f).mul(modelMatrix);
Matrix4f viewMatrix = new Matrix4f().translate(new Vector3f(theta, 0, -20)); //this is supposed to translate the camera back 20 units
tempvec = tempvec.mul(viewMatrix);
Matrix4f projectionMatrix = new Matrix4f().identity().setPerspective((float)Math.toRadians(70.0f), 1280.0f/720.0f, 0.1f, 1000.0f);
cube4f[i][j] = tempvec.mul(projectionMatrix);
//following code makes the projection appear inside the screen's borders
cube4f[i][j].x += 1.0f;
cube4f[i][j].y += 1.0f;
cube4f[i][j].x *= 0.5f * 1280.0f;
cube4f[i][j].y *= 0.5f * 720.0f;
}
}
Graphics2D g2d = (Graphics2D)g;
g2d.setBackground(new Color(32, 32, 32, 255));
g2d.clearRect(0, 0, 1280, 720);
g2d.setColor(Color.WHITE);
for(int i = 0; i < cube4f.length; i++) {
g2d.drawLine((int)cube4f[i][0].x, (int)cube4f[i][0].y, (int)cube4f[i][1].x, (int)cube4f[i][1].y);
g2d.drawLine((int)cube4f[i][1].x, (int)cube4f[i][1].y, (int)cube4f[i][2].x, (int)cube4f[i][2].y);
g2d.drawLine((int)cube4f[i][2].x, (int)cube4f[i][2].y, (int)cube4f[i][0].x, (int)cube4f[i][0].y);
}
}
@Override
public void update() {
theta++;
}
在上面的代码中,立方体应该距离相机 25 个单位(因为立方体距离世界原点 5 个单位,而相机在相反方向上距离世界 20 个单位)和世界右侧 5 个单位。但事实并非如此,如下图所示:
如图所示;立方体清晰居中,近距离观察。
我正在尝试找到一种解决方案,使我能够在 LWJGL3 应用程序和 3D 投影应用程序上保留相同的“OpenGL”基本代码(更准确地说是 JOML 基本代码)。那就是使用相同的模型、视图和投影矩阵在两个应用程序上生成相同的投影。
您通过除以 x
、y
和 z
组件错过了 Perspective divide. The clip space coordinate is a Homogeneous coordinates. You have to transform the Homogeneous clip space coordinate to a Cartesian 标准化设备坐标(所有组件都在 [-1, 1] 范围内)来自 w
cpmponent:
tempvec = tempvec.mul(projectionMatrix);
cube4f[i][j] = new Vector4f(
tempvec.x / tempvec.w,
tempvec.y / tempvec.w,
tempvec.z / tempvec.w,
1.0f);
由于顶点是点而不是向量,因此顶点坐标的第 4 个分量必须是 1 而不是 0:
Vector4f tempvec = new Vector4f(cube3f[i][j], 0.0f).mul(modelMatrix);
Vector4f tempvec = new Vector4f(cube3f[i][j], 1.0f).mul(modelMatrix);
经过多年学习 OpenGL 初学者课程和线性代数课程,我最近终于理解了模型、视图和投影矩阵的意义。基本上,模型矩阵将 3D 模型的顶点坐标转换为 3D 世界中的顶点坐标(相对于 3D 世界的原点平移、旋转和缩放模型)。 View Matrix 将 3D 世界的顶点坐标转换为相对于相机的顶点坐标(通常只是世界相对于相机的平移和旋转)而 Projection Matrix 用于 compute/convert Camera 中的顶点坐标查看二维平面(通常是屏幕)上的投影。
我正在尝试在没有 OpenGL 的情况下在 2D 平面上的 3D 投影中创建相机系统,但是使用 JOML,它是一个 Java OpenGL 的数学(主要是线性代数数学)库,经常与LightWeight Java 游戏库 3. 我能够在 OpenGL 中创建一个相机系统,使用上述 3 个矩阵非常容易。但是当我使用相同的精确矩阵(和一些额外的代码以便投影出现在屏幕上)时,我只能在 2D 平面上进行投影。模型矩阵和视图矩阵似乎对模型在屏幕上的投影方式没有任何影响。
这是我用来在屏幕上投影立方体的代码:
private float theta = 0;
@Override
public void render(Graphics g) {
Vector3f cube3f[][] = {
// SOUTH
{ new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f) },
{ new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, -0.5f, -0.5f) },
// EAST
{ new Vector3f( 0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f) },
{ new Vector3f( 0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, -0.5f, 0.5f) },
// NORTH
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f) },
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, 0.5f) },
// WEST
{ new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, -0.5f) },
{ new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f(-0.5f, -0.5f, -0.5f) },
// TOP
{ new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f(-0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, 0.5f) },
{ new Vector3f(-0.5f, 0.5f, -0.5f), new Vector3f( 0.5f, 0.5f, 0.5f), new Vector3f( 0.5f, 0.5f, -0.5f) },
// BOTTOM
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, -0.5f) },
{ new Vector3f( 0.5f, -0.5f, 0.5f), new Vector3f(-0.5f, -0.5f, -0.5f), new Vector3f( 0.5f, -0.5f, -0.5f) },
};
Vector4f cube4f[][] = new Vector4f[cube3f.length][cube3f[0].length];
for(int i = 0; i < cube3f.length; i++) {
for(int j = 0; j < cube3f[i].length; j++) {
Matrix4f modelMatrix = new Matrix4f()
.rotate((float)Math.toRadians(theta), new Vector3f(0.0f, 1.0f, 0))
.rotate((float)Math.toRadians(theta), new Vector3f(1.0f, 0, 0))
.translate(new Vector3f(0, 5, 5)); // this is supposed to move the cube up 5 units and away 5 units
Vector4f tempvec = new Vector4f(cube3f[i][j], 0.0f).mul(modelMatrix);
Matrix4f viewMatrix = new Matrix4f().translate(new Vector3f(theta, 0, -20)); //this is supposed to translate the camera back 20 units
tempvec = tempvec.mul(viewMatrix);
Matrix4f projectionMatrix = new Matrix4f().identity().setPerspective((float)Math.toRadians(70.0f), 1280.0f/720.0f, 0.1f, 1000.0f);
cube4f[i][j] = tempvec.mul(projectionMatrix);
//following code makes the projection appear inside the screen's borders
cube4f[i][j].x += 1.0f;
cube4f[i][j].y += 1.0f;
cube4f[i][j].x *= 0.5f * 1280.0f;
cube4f[i][j].y *= 0.5f * 720.0f;
}
}
Graphics2D g2d = (Graphics2D)g;
g2d.setBackground(new Color(32, 32, 32, 255));
g2d.clearRect(0, 0, 1280, 720);
g2d.setColor(Color.WHITE);
for(int i = 0; i < cube4f.length; i++) {
g2d.drawLine((int)cube4f[i][0].x, (int)cube4f[i][0].y, (int)cube4f[i][1].x, (int)cube4f[i][1].y);
g2d.drawLine((int)cube4f[i][1].x, (int)cube4f[i][1].y, (int)cube4f[i][2].x, (int)cube4f[i][2].y);
g2d.drawLine((int)cube4f[i][2].x, (int)cube4f[i][2].y, (int)cube4f[i][0].x, (int)cube4f[i][0].y);
}
}
@Override
public void update() {
theta++;
}
在上面的代码中,立方体应该距离相机 25 个单位(因为立方体距离世界原点 5 个单位,而相机在相反方向上距离世界 20 个单位)和世界右侧 5 个单位。但事实并非如此,如下图所示:
如图所示;立方体清晰居中,近距离观察。
我正在尝试找到一种解决方案,使我能够在 LWJGL3 应用程序和 3D 投影应用程序上保留相同的“OpenGL”基本代码(更准确地说是 JOML 基本代码)。那就是使用相同的模型、视图和投影矩阵在两个应用程序上生成相同的投影。
您通过除以 x
、y
和 z
组件错过了 Perspective divide. The clip space coordinate is a Homogeneous coordinates. You have to transform the Homogeneous clip space coordinate to a Cartesian 标准化设备坐标(所有组件都在 [-1, 1] 范围内)来自 w
cpmponent:
tempvec = tempvec.mul(projectionMatrix);
cube4f[i][j] = new Vector4f(
tempvec.x / tempvec.w,
tempvec.y / tempvec.w,
tempvec.z / tempvec.w,
1.0f);
由于顶点是点而不是向量,因此顶点坐标的第 4 个分量必须是 1 而不是 0:
Vector4f tempvec = new Vector4f(cube3f[i][j], 0.0f).mul(modelMatrix);
Vector4f tempvec = new Vector4f(cube3f[i][j], 1.0f).mul(modelMatrix);