java - 如何使用JOML在3D投影到2D平面上模拟OpenGL之类的模型、 View 矩阵?

标签 java math opengl 3d projection-matrix

经过多年学习 OpenGL 和线性代数入门类(class),我最近终于理解了模型、 View 和投影矩阵的要点。基本上,模型矩阵将 3D 模型的顶点坐标转换为 3D 世界中的顶点坐标(相对于 3D 世界的原点平移、旋转和缩放模型)。 View 矩阵将 3D 世界的顶点坐标转换为相对于相机的顶点坐标(通常只是世界相对于相机的平移和旋转),投影矩阵用于将相机 View 中的顶点坐标计算/转换为2D 平面(通常是屏幕)上的投影。

我正在尝试在不使用 OpenGL 的情况下在 2D 平面上的 3D 投影中创建一个相机系统,但使用 JOML,这是一个用于 OpenGL 的 Java 数学(主要是线性代数数学)库,通常与 LightWeight Java Game Library 3 一起使用。我能够在 OpenGL 中创建一个相机系统,使用上述 3 个矩阵就非常容易了。但是,当我使用相同的精确矩阵(以及一些额外的代码以便投影出现在屏幕上)时,我只能在 2D 平面上进行投影。模型矩阵和 View 矩阵似乎对模型在屏幕上的投影方式没有任何影响。

这是我用来在屏幕上投影立方体的代码:

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 个单位到世界的右边。但事实并非如此,如下图所示:

up-close centered rotating cube

如图所示;立方体清晰居中,近距离观察。

我正在尝试找到一种解决方案,使我能够在 LWJGL3 应用程序和 3D 投影应用程序上保留相同的“OpenGL”基本代码(更准确地说是 JOML 基本代码)。即使用相同的模型、 View 和投影矩阵在两个应用程序上生成相同的投影。

最佳答案

您错过了Perspective divide 。剪辑空间坐标是 Homogeneous coordinates 。您必须将齐次剪切空间坐标转换为 Cartesian通过将 xyz 分量除以 来标准化设备坐标(所有分量都在 [-1, 1] 范围内) >w 组件:

tempvec = tempvec.mul(projectionMatrix);
cube4f[i][j] = new Vector4f(
    tempvec.x / tempvec.w,
    tempvec.y / tempvec.w,
    tempvec.z / tempvec.w,
    1.0f);

由于顶点是点而不是 vector ,因此顶点坐标的第四个分量必须是 1 而不是 0:

Vector4f tempvec = new Vector4f(cube3f[i][j], 0.0f).mul(modelMatrix);

Vector4f tempvec = new Vector4f(cube3f[i][j], 1.0f).mul(modelMatrix);

关于java - 如何使用JOML在3D投影到2D平面上模拟OpenGL之类的模型、 View 矩阵?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64012912/

相关文章:

algorithm - 我是否可以始终假设角值 !=1 的 mvp 矩阵正在执行缩放?

c++ - 没有 SDL 窗口的 SDL_Events

java - 移植C代码;需要有关按位运算和指针语法的帮助

java - MongoDb 多重排序

java - 多个编辑文本

java - 将结果集值添加到 Hashmap Map<String,Object> 类型中

python - 检测图像的最外边缘并基于它进行绘图

math - 阅读有关神经网络的书籍(并理解它们)所需的先决条件

c++ - VBO 的顶点数组 -OpenGL

opengl - ffmpeg 到 opengl 纹理与 glTexImage2D