You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无矩阵3D投影在负距离场景下的坐标异常问题求助

无矩阵3D投影在负距离场景下的坐标异常问题求助

最近我在手写一个不用投影矩阵的3D透视渲染,纯靠乘除法来计算屏幕坐标,但遇到了一个卡了好久的问题:当顶点移动到相机后方(也就是相对相机的z坐标为负的时候),计算出来的屏幕坐标会直接镜像错乱,导致图形变形得一塌糊涂。

虽然我本来想忽略完全在相机后方的三角形,但现在遇到了部分顶点在相机后的情况——我需要正确处理这些顶点的坐标才能画出正常的边缘,总不能直接把半可见的多边形全丢了吧?

先把我的代码贴出来,方便大家帮我定位问题:

Main 类

package def_pack;
import java.io.IOException;
import javax.swing.JFrame;

public class Main {
    public static void main(String[] args) throws IOException {
        JFrame f = new JFrame("3D Test");
        f.setSize(1920, 1080);
        f.setResizable(false);
        f.setLocationRelativeTo(null);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GamePanel gamePanel = new GamePanel();
        f.add(gamePanel);
        gamePanel.setSize(1920,1080);
        gamePanel.startThread();
        f.setVisible(true);
    }
}

GamePanel 类

package def_pack;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.IOException;
import javax.swing.JPanel;

public class GamePanel extends JPanel implements Runnable{
    public Thread thr;
    public scene s;

    GamePanel() throws IOException{
        s = new scene();
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {e.printStackTrace();}
            try {
                update();
                repaint();
            } catch (IOException e) {e.printStackTrace();}
        }
    }

    public void update() throws IOException {
        s.update();
    }

    public void paintComponent(Graphics g) {
        Graphics2D g2d= (Graphics2D)g;
        g2d.clearRect(0, 0, 1920, 1080);
        try {
            s.draw(g2d);
        } catch (InterruptedException e) {e.printStackTrace();}
    }

    public void startThread() {
        thr = new Thread(this);
        thr.start();
    }
}

vertex3D 类

package def_pack;

public class vertex3D {
    public double x,y,z;
    public double[] transformedCoords= {0,0,0};

    vertex3D(double x, double y, double z){
        this.x=x;
        this.y=-y;
        this.z=z;
    }

    public void update(double camX, double camY, double camZ) {
        // 计算相对于相机的坐标
        this.transformedCoords[0]=this.x-camX;
        this.transformedCoords[1]=this.y-camY;
        this.transformedCoords[2]=this.z-camZ;
    }
}

scene 类

package def_pack;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.io.IOException;
import java.util.ArrayList;

public class scene {
    public double camX=0;
    public double camY=0;
    public double camZ=0;
    public vertex3D[] testQuad = {new vertex3D(-3,-2,2),new vertex3D(-3,-2,4),new vertex3D(-3,3,4),new vertex3D(-3,3,2)};
    public int meshesAmount;
    public ArrayList<vertex3D[]> meshes = new ArrayList<>();
    public Rectangle screen = new Rectangle(0,0,1920,1080);
    public int scrW=1920;
    public int scrH=1080;
    public int sizer=300;

    scene() throws IOException {
        meshes.add(testQuad);
        meshesAmount=meshes.size();
    }

    public void update(){
        // 相机慢慢向前移动,触发顶点进入相机后方的情况
        camZ+=0.001;
        for (int i=0;i<meshesAmount;i++) {
            for (int j=0;j<meshes.get(i).length;j++) {
                meshes.get(i)[j].update(camX,camY,camZ);
            }
        }
    }

    public void draw(Graphics2D g2d) throws InterruptedException {
        for (int i=0;i<meshesAmount;i++) {
            // 只要有一个顶点在相机前就绘制这个多边形
            boolean drawable=false;
            for (int j=0;j<meshes.get(i).length;j++) {
                if (meshes.get(i)[j].transformedCoords[2]>0) {
                    drawable=true;
                }
            }
            if (drawable) {
                int nPoints=meshes.get(i).length;
                int xPoints[] = new int[nPoints];
                int yPoints[]=new int[nPoints];
                for (int j=0;j<meshes.get(i).length;j++) {
                    // 核心的透视计算:用相对z坐标除x、y得到归一化坐标
                    double newX=meshes.get(i)[j].transformedCoords[0]/(meshes.get(i)[j].transformedCoords[2]);
                    double newY=meshes.get(i)[j].transformedCoords[1]/(meshes.get(i)[j].transformedCoords[2]);
                    // 缩放并平移到屏幕中心
                    newX*=sizer;
                    newX/=scrW/(double)scrH;
                    newY*=sizer;
                    newX+=scrW/2;
                    newY+=scrH/2;
                    xPoints[j]=(int)(newX);
                    yPoints[j]=(int)(newY);
                }
                g2d.setColor(Color.cyan);
                Polygon p = new Polygon(xPoints, yPoints, xPoints.length);
                Area polyArea = new Area(p);
                Area screenArea = new Area(screen);
                polyArea.intersect(screenArea);
                g2d.fill(polyArea);
            }
        }
    }
}

我已经试过的办法(都没解决问题)

  1. 尝试给相机后的顶点z坐标取倒数取反:
if (behindCam[j]) { 
    meshes.get(i)[j].transformedCoords[2]=-1/meshes.get(i)[j].transformedCoords[0]; 
}

结果是当z在0到-1之间时,计算出来的坐标直接爆炸,虽然z<-1后看起来正常,但中间这段完全没法用。

  1. 尝试直接把投影后的x、y取反:
newX*=-1; 
newY*=-1;

这只是简单的镜像,透视比例完全不对,图形还是畸形的。

  1. 尝试直接把z坐标取反:
meshes.get(i)[j].transformedCoords[2]=-meshes.get(i)[j].transformedCoords[2];

相当于把相机后的顶点强行拉到前面,透视效果假得离谱,完全不是我要的。

  1. 还试过把z坐标限制在0.0001避免负数,但这样透视的近大远小直接失效,看起来就像2D图形平移,完全没3D感了。

问题表现的几个阶段

  • 初始状态:四边形正常显示,透视效果也对
  • 顶点接近相机z=0时:图形开始出现明显变形
  • 一半四边形在相机后时:直接镜像错乱,图形完全没法看

现在我需要的是:当顶点在相机后方时,能正确计算它的屏幕坐标(或者正确处理这种部分可见的多边形),而不是出现镜像或者坐标爆炸的情况。有没有大佬能帮我分析一下问题出在哪,或者给个可行的解决思路?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 11:38:09