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

How to convert projected points to screen coordinates(Viewport matrix)

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

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

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

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

好的,这里有一些代码:

这是透视投影矩阵,我创建了其中的一个,并用它乘以最后一个矩阵,所以这个矩阵乘以最后一个完全翻译的旋转和缩放的矩阵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    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);    
    }

其他矩阵按以下顺序相乘:

1
2
3
4
5
6
7
8
9
    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);
    }

这是最后一个矩阵,我通过应用透视投影得到的矩阵,然后将所有顶点与结果相乘:

1
2
3
4
5
6
7
8
9
    public Matrix4 GetProjectedTransformationMatrix()
    {
        Matrix4 projectionm = Projection.GetProjectionMatrix();
        Matrix4 tranformationm = GetTransformationMatrix();

        Matrix4 ret = projectionm * tranformationm;

        return ret;
    }

我缺少一个视区矩阵,当我用最后一个向量相乘时,它会给我正确的x和y屏幕坐标。虽然我不使用OpenGL,但我认为OpenGL或Direct3D设置其视区矩阵的方式是可行的。

编辑

我已经解决了这个问题,尽管不是我想要的方式。

我在寻找一个能处理所有投影和坐标转换的矩阵,这个矩阵可以利用所有模型和视图矩阵的乘法,把已经转换的点转换成屏幕坐标。

我现在要做的是应用以前的透视投影矩阵,然后将x和y除以z,然后应用屏幕大小因子,如下所示:

1
2
3
4
5
6
7
8
9
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,所以透视是什么?投影到底在做什么?).

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

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


我很难理解为什么在这个解中你决定用剪辑空间z来划分坐标。

您应该将向量(包括w)的所有分量除以w,然后得到的向量在ndc中。在正射投影中,w是常数,通常为1.0。从长远来看,w将根据与zNear的距离而变化。无论使用哪种投影,在输出模型视图和投影矩阵(剪辑空间)的乘法结果后,OpenGL和Direct3D都会立即执行w除法以生成ndc坐标。这是管道的一个不可编程部分,只在顶点输出之后发生。

任何超出范围的点:XYZW:[-1,1]在该分区被剪裁后,或者用另一种方式表示位于您的视区之外的点。更正式地说,这通常被描述为:

&-clipwle;clipx,y,zle;clipw

透视图稍微改变了游戏,所以ndc坐标空间实际上比近平面压缩了更多的相机空间到远平面的可见部分。被w除后,远处的点趋于收敛到视点的中心,但事实仍然是被w除,而不是z除。

如果只是用x和y除以z,那么深度范围就不能正常工作。在投影和剪切之后,Z还需要在范围内[-1,1],因为剪辑空间Z坐标在渲染完成之前还有另一个转换要进行。

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