首页 > 解决方案 > 如何实现透视投影

问题描述

我有一个光线追踪的迷你项目,我已经开始用 Java 编写了。

我自己实现了所有东西,没有任何像 OpenGl 这样的库,一切都是我从头开始实现的——相机、渲染器、图像写入器等......问题是,当我放置一个对象时——比如说——一个球体,那就是没有定位在屏幕的中心,它变成了某种椭圆形。对于(有限)圆柱体也是如此 - 它出于某种原因径向延伸。

渲染过程是这样的:我的相机是用原点、方向和向上向量定义的,右边的向量是叉积方向 X 向上。对于视图平面中的每个点,我构造一条从该点到相机原点的射线,并拍摄它以找到与场景中每个几何体的交点。对于每个几何图形,我取最近的交点,计算它的颜色,并将它的颜色写入位于视图平面的那个点的像素(我从中构造了一条射线)。

现在,我已经阅读了很多关于透视投影的内容,并尝试使用以下链接实现一个:http: //ogldev.atspace.co.uk/www/tutorial12/tutorial12.html 但未能使用它。我已经在我的 Transform 类中实现了它,我试图做的是将投影矩阵 M 乘以我在场景中找到的任何最近的交点 p。不幸的是,由于某种原因,结果保持不变。

注意:M*p 的乘法是一个新的向量 4(v1、v2、v3、v4),因此它被转换为一个新的向量 3(v1/v4、v2/v4、v3/v4)。所以那个乘法的返回值就是那个vector3。

为了计算视场角 θ,我计算了 2 条光线与视平面两侧之间的角度。


如果这里太乱,这里是一些代码GitHub 链接: https ://github.com/elyasaf755/3D-Engine

我的 Transform 类字段及其相关功能

public class Transform {
    private static double _zNear;
    private static double _zFar;
    private static double _width;//screen width
    private static double _height;//screen height
    private static double _fov;//field of view

    private Vector3D _translation;
    private Vector3D _rotation;
    private Vector3D _scale;

    public Matrix4 getTransformation(){
        Matrix4 translationMatrix = new Matrix4().initTranslation(
                this._translation.getPoint().getX().getCoord(),
                this._translation.getPoint().getY().getCoord(),
                this._translation.getPoint().getZ().getCoord()
        );

        Matrix4 rotationMatrix = new Matrix4().initRotation(
                _rotation.getPoint().getX().getCoord(),
                _rotation.getPoint().getY().getCoord(),
                _rotation.getPoint().getZ().getCoord()
        );

        Matrix4 scaleMatrix = new Matrix4().initScale(
                _scale.getPoint().getX().getCoord(),
                _scale.getPoint().getY().getCoord(),
                _scale.getPoint().getZ().getCoord()
        );

        return translationMatrix.mult(rotationMatrix.mult(scaleMatrix));
    }

    public Matrix4 getProjectedTransformation(){

        Matrix4 transformationMatrix = getTransformation();
        Matrix4 projectionMatrix = new Matrix4().initProjection(_fov, _width, 
                                                    _height, _zNear, _zFar);

        return projectionMatrix.mult(transformationMatrix);
    }
}

我的 Matrix4 类字段及其相关功能

public class Matrix4 {
    private double[][] _m;

    public Matrix4 initProjection(double fov, double width, double height, 
                                             double zFar, double zNear){

        double ar = width / height;
        double tanHalfFov = Math.tan(Math.toRadians(fov / 2));
        double zRange = zNear - zFar;

        setRow(0,1 / (tanHalfFov * ar),0,           0,                     0);
        setRow(1,0,                    1/tanHalfFov,0,                     0);
        setRow(2,0,                    0,           (-zNear - zFar) / zRange,2 
                                                     * zFar * zNear / zRange);
        setRow(3,0,                    0,           1,                     0);

        return this;
    }

    public Matrix4 mult(Matrix4 matrix) {
        Matrix lhs = new Matrix(_m);
        Matrix rhs = new Matrix(matrix.getMatrix());

        Matrix result = lhs.mult(rhs);//simple matrix multiplication


        return new Matrix4(result.get_matrix());
     }
}

我的矩阵类字段及其相关功能

public class Matrix {
    private double[][] _matrix;
    private int _numOfRows;
    private int _numOfCols;

    public Matrix mult(Matrix matrix) {
        if (_numOfCols != matrix.getRows())
            return null;

        Matrix result = new Matrix(_numOfRows, matrix.getColumns());


        for (int i = 0; i < _numOfRows; i++)
        {
            for (int j = 0; j < matrix.getColumns(); j++)
            {
                BigDecimal sum = new BigDecimal(0, MathContext.UNLIMITED);

                for (int k = 0; k < matrix.getRows(); k++)
                {
                    BigDecimal temp = new BigDecimal(
                                      _matrix[i][k]*matrix.get_element(k, j), 
                                      MathContext.UNLIMITED);
                    sum = sum.add(temp);
                }

                Coordinate temp = new Coordinate(sum.doubleValue());

                result.set_element(i, j, temp.getCoord());
            }
        }

        return result;
    }
}

我的相机类字段及其相关功能

public class Camera {
private Point3D _origin;
private Vector3D _direction;
private Vector3D _up;
private Vector3D _right;

public Ray constructRayThroughPixel(int Nx, int Ny, int i, int j, double 
                screenDistance, double screenWidth, double screenHeight){
    //Fix pixel locations
    i = Nx - i - 1;
    //j = Ny - j;


    Point3D p0 = get_origin();
    Vector3D direction = get_direction();
    Vector3D up = get_up();
    Vector3D right = get_right();

    //Image center point
    Point3D Pc = p0.add(direction.scale(screenDistance));

    //Pixel ratios
    double Rx = screenWidth / Nx; //Pixel width
    double Ry = screenHeight / Ny; //Pixel height

    //Center pixel
    double Xi = (i - Nx / 2.0)*Rx + Rx / 2.0;
    double Yj = (j - Ny / 2.0)*Ry + Ry / 2.0;

    Point3D p_ij;
    if (Xi == 0 && Yj == 0){
        p_ij = new Point3D(Pc);
    }
    else if (Xi == 0){
        p_ij = new Point3D(Pc.add(Vector3D.ZERO.subtract(up.scale(Yj))));
    }
    else if (Yj == 0){
        p_ij = new Point3D(
               Pc.add((right.scale(Xi)).subtract(Vector3D.ZERO)));
    }
    else{
        p_ij = new Point3D
               (Pc.add((right.scale(Xi)).subtract(up.scale(Yj))));
    }

    Vector3D v_ij = p_ij.subtract(p0);

    return new Ray(p0, v_ij.normalized());
}

}

我希望任何对象在渲染后都能保持其原始形状,无论其位置如何。

当球体不居中时 当球体不居中时

当球体居中时 当球体居中时

我能做的一件事是让相机离物体真的很远,这样我就不会注意到那些延伸。但我想从根本上解决这个问题。

标签: javaperspectivecamera

解决方案


推荐阅读