如何将投影点转换为屏幕坐标(视口矩阵)

时间:2014-04-05 21:10:27

标签: opengl matrix 3d directx viewport

我正在制作3D软件渲染器,我已经完成了翻译,旋转和缩放矩阵。

现在我有了一个透视投影矩阵,它将深度透视应用于我的所有点。我不能做的是将最终透视投影矢量投影到屏幕坐标(视口变换)。

这是我现在正在使用的透视投影矩阵: http://puu.sh/7XikH.jpg(抱歉无法发布图片)

所以基本上我需要一个矩阵,它将采用已经透视的投影矢量并将其转换为屏幕坐标。

好的,这里有一些代码:

这是透视投影矩阵,我创建其中一个并乘以我所拥有的最后一个矩阵,因此这与最终完全平移的旋转和缩放矩阵相乘

    public static Matrix4 CreateProjectionMatrix(double znear, double zfar, int width, int height, double fov)
    {
        double[,] ret = new double[4, 4];

        double e = 1 / Math.Tan(MathUtil.GetRadian(fov) / 2.0);
        double ar = width / height;
        double zp = zfar + znear;
        double zn = zfar - znear;

        ret[0, 0] = e; ret[0, 1] = 0;       ret[0, 2] = 0;          ret[0, 3] = 0;
        ret[1, 0] = 0; ret[1, 1] = e / ar;  ret[1, 2] = 0;          ret[1, 3] = 0;
        ret[2, 0] = 0; ret[2, 1] = 0;       ret[2, 2] = -(zp / zn); ret[2, 3] = -((2 * zfar * znear) / zn);
        ret[3, 0] = 0; ret[3, 1] = 0;       ret[3, 2] = -1;         ret[3, 3] = 0;

        return new Matrix4(ret);    
    }

其他矩阵按此顺序相乘:

    public Matrix4 GetTransformationMatrix()
    {
        Matrix4 translationm = Matrix4.CreateTranslationMatrix(Translation);
        Matrix4 rotationm = Matrix4.CreateRotationMatrix(Rotation);
        Matrix4 scalem = Matrix4.CreateScaleMatrix(Scale);

        //scale -> rotate -> translate
        return translationm * (rotationm * scalem);
    }

这是最终的矩阵,我通过应用透视投影获得的矩阵,然后我将所有顶点乘以结果:

    public Matrix4 GetProjectedTransformationMatrix()
    {
        Matrix4 projectionm = Projection.GetProjectionMatrix();
        Matrix4 tranformationm = GetTransformationMatrix();

        Matrix4 ret = projectionm * tranformationm;

        return ret;
    }

我错过了一个视口矩阵,当我将最终矢量乘以时,会给出正确的x和y屏幕坐标。虽然我没有使用OpenGL,但我认为OpenGL或Direct3D设置其视口矩阵的方式是可行的。

被修改

我已经解决了这个问题,虽然不是我喜欢的方式。

我正在寻找一个可以处理所有投影和坐标变换的矩阵,这个变量可以将已经变换的点与所有模型和视图矩阵相乘并将其转换为屏幕坐标。

我现在正在做的是应用之前相同的透视投影矩阵,然后将x和y除以z,然后应用屏幕尺寸因子,如下所示:

public Point GetProjectedPoint(Vector vec, Matrix4 mat)
{
    Vector point = vec * mat;

    double px = point.X / point.Z * Width;
    double py = point.Y / point.Z * Height;

    return new Point(px, py);
}

我认为这样做的方式是透视投影(http://puu.sh/7XikH.jpg)除以Z,因为那是“透视(更远的点数=更小)”效果来自的地方,这会给我最终世界坐标(这让我感到困惑,因为我在最终视口投影上除以Z,所以透视投影究竟是什么?)。

然后将其转换为屏幕坐标,视口矩阵将首先将所有内容缩放到规范化设备坐标,然后将其映射到屏幕坐标。

如果有人可以澄清管道应该是什么以及我应该做什么,或者解释使用适当矩阵的更好方法我会很感激。

1 个答案:

答案 0 :(得分:3)

我无法理解为什么你决定在这个解决方案中用剪辑空间Z划分你的坐标。

您应该将矢量的所有分量(包括W)除以W,然后生成的矢量位于NDC中。在正交投影中,W将是常数并且通常 1.0 。从视角来看,W将根据与zNear的距离而变化。无论您使用何种投影,在通过ModelView和Projection矩阵(剪辑空间)输出乘法结果后,OpenGL和Direct3D执行除法以生成NDC坐标。这是在顶点输出之后发生的管道的不可编程部分。

XYZW范围之外的任何点:剪切此分割后的[-1,1],或者以另一种方式表示位于视口之外的点。更正式地说,这通常被描述为:

-Clip W ≤Clip X,Y,Z ≤Clip W

透视会稍微改变游戏,因此NDC坐标空间实际上将更多的相机空间压缩到远平面的可见部分而不是近平面。在 除以W之后,远点往往会向视口 的中心收敛。但事实仍然是除法仍由W而不是Z.

如果您只是将X和Y除以Z,那么深度范围将无法正常工作。在投影和裁剪之后Z也需要在[-1,1]范围内,因为剪裁空间Z坐标在渲染完成之前仍然有另一个变换。

实际上,您当前的视口转换实现中完全没有深度范围。将屏幕空间视为2D坐标空间很有诱惑力,但Z仍然存在。屏幕空间Z不用于定位像素,但您应该非常熟悉屏幕空间Z,因为这是深度缓冲区存储的内容。您需要深度范围(glDepthRange (...))才能执行视口转换的最后一部分。