如何转换3D点到2D透视投影? [英] How to convert a 3D point into 2D perspective projection?

查看:828
本文介绍了如何转换3D点到2D透视投影?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在与使用贝塞尔曲线和曲面绘制著名犹他茶壶。使用16控制点贝塞尔补丁,我已经能够绘制茶壶并使用世界相机的功能,这使得旋转产生的茶壶的能力显示出来,而现在使用正交投影。

I am currently working with using Bezier curves and surfaces to draw the famous Utah teapot. Using Bezier patches of 16 control points, I have been able to draw the teapot and display it using a 'world to camera' function which gives the ability to rotate the resulting teapot, and am currently using an orthographic projection.

其结果是,我有一个平面的茶壶,预计作为正投影的目的是preserve平行线。

The result is that I have a 'flat' teapot, which is expected as the purpose of an orthographic projection is to preserve parallel lines.

不过,我想用透视投影给茶壶深度。我的问题是,一个人如何采取从三维的XYZ顶点回到'世界相机的功能,而这种转换为二维坐标。我想要使​​用投影平面在z = 0,并允许用户使用键盘上的箭头键来确定焦距和图像尺寸。

However, I would like to use a perspective projection to give the teapot depth. My question is, how does one take the 3D xyz vertex returned from the 'world to camera' function, and convert this into a 2D coordinate. I am wanting to use the projection plane at z=0, and allow the user to determine the focal length and image size using the arrow keys on the keyboard.

我编程这在Java中,并已全部投入事件处理程序设置的,并且还写其处理基本矩阵乘法的矩阵类。我一直在读通过维基百科和其他资源的一段时间,但我不能完全得到人们如何完成这一转变的句柄。

I am programming this in java and have all of the input event handler set up, and have also written a matrix class which handles basic matrix multiplication. I've been reading through wikipedia and other resources for a while, but I can't quite get a handle on how one performs this transformation.

推荐答案

我看这个问题是有点老了,但我决定给那些谁通过搜索找到这个问题的答案呢。
重新present 2D / 3D转换时下的标准方法是使用齐次坐标。的 [X,Y,瓦特] 的用于2D和 [X,Y,Z,W] 的为三维。既然你在3D的三个轴以及翻译,这些信息在一个4x4变换矩阵非常适合。我将使用列主要矩阵表示法中对此的解释。所有的矩阵是4×4,除非另有说明。
从3D点,一个栅格化点,线,面的阶段看起来是这样的:

I see this question is a bit old, but I decided to give an answer anyway for those who find this question by searching.
The standard way to represent 2D/3D transformations nowadays is by using homogeneous coordinates. [x,y,w] for 2D, and [x,y,z,w] for 3D. Since you have three axes in 3D as well as translation, that information fits perfectly in a 4x4 transformation matrix. I will use column-major matrix notation in this explanation. All matrices are 4x4 unless noted otherwise.
The stages from 3D points and to a rasterized point, line or polygon looks like this:

  1. 将您的三维点的逆矩阵的摄像头,然后提供他们所需的任何转换。如果你有表面法线,变换他们很好,但以w设置为零,因为你不想翻译法线。你变换法线与基体必须的各向同性的;缩放和剪切使法线格式不正确。
  2. 变换点用夹子空间矩阵。此矩阵缩放x和y带和高宽比,视场中,鳞片ž由近及远剪切面,和插头的'老'Z为瓦特改造后,你要分的x,y和z由瓦特这就是所谓的透视分割
  3. 现在,您的顶点是剪辑空间,并且要进行裁剪,这样你就不会呈现视区范围之外的任何像素。 萨瑟兰 - 霍奇曼剪裁最wides使用$ P $垫裁剪算法。
  4. 变换x和y相对于瓦特和半宽和半高。您的x和y坐标现在在视区坐标。 w被丢弃,但1 / w和z通常保存,因为1 / w被要求做透视-正确插穿过多边形表面和z被存储在Z缓冲器和用于深度测试。
  1. Transform your 3D points with the inverse camera matrix, followed with whatever transformations they need. If you have surface normals, transform them as well but with w set to zero, as you don't want to translate normals. The matrix you transform normals with must be isotropic; scaling and shearing makes the normals malformed.
  2. Transform the point with a clip space matrix. This matrix scales x and y with the field-of-view and aspect ratio, scales z by the near and far clipping planes, and plugs the 'old' z into w. After the transformation, you should divide x, y and z by w. This is called the perspective divide.
  3. Now your vertices are in clip space, and you want to perform clipping so you don't render any pixels outside the viewport bounds. Sutherland-Hodgeman clipping is the most widespread clipping algorithm in use.
  4. Transform x and y with respect to w and the half-width and half-height. Your x and y coordinates are now in viewport coordinates. w is discarded, but 1/w and z is usually saved because 1/w is required to do perspective-correct interpolation across the polygon surface, and z is stored in the z-buffer and used for depth testing.

此阶段是实际的投影,因为Z不是用作组件中的位置的任何其他。

This stage is the actual projection, because z isn't used as a component in the position any more.

该计算考虑到了现场。无论是棕褐色采用弧度或学位无关,但是的角度的必须匹配。请注意,结果达到无穷大的角度的接近180度。这是一个奇异性,因为它是不可能有一个联络点宽。如果你想要的数值稳定性,保持的角度的小于或等于179度。

This calculates the field-of view. Whether tan takes radians or degrees is irrelevant, but angle must match. Notice that the result reaches infinity as angle nears 180 degrees. This is a singularity, as it is impossible to have a focal point that wide. If you want numerical stability, keep angle less or equal to 179 degrees.

fov = 1.0 / tan(angle/2.0)

还要注意的是1.0 /棕褐色(45)= 1,这里其他人建议只是除以z。这里的结果是明确的。你会得到一个90度视场和1的纵横比。使用齐次坐标像这样有几个其他优点为好;例如,我们可以进行裁剪对远近飞机而不把它当作一个特例。

Also notice that 1.0 / tan(45) = 1. Someone else here suggested to just divide by z. The result here is clear. You would get a 90 degree FOV and an aspect ratio of 1:1. Using homogeneous coordinates like this has several other advantages as well; we can for example perform clipping against the near and far planes without treating it as a special case.

这是剪辑矩阵的布局。 的aspectRatio 的是宽度/高度。因此,基于FOV视场的x分量的调整以适合年。远远近近的系数,这是距离的远近裁剪平面。

This is the layout of the clip matrix. aspectRatio is Width/Height. So the FOV for the x component is scaled based on FOV for y. Far and near are coefficients which are the distances for the near and far clipping planes.

[fov * aspectRatio][        0        ][        0              ][        0       ]
[        0        ][       fov       ][        0              ][        0       ]
[        0        ][        0        ][(far+near)/(far-near)  ][        1       ]
[        0        ][        0        ][(2*near*far)/(near-far)][        0       ]

屏幕投影

裁剪后,这是最后的转型,让我们的屏幕坐标。

Screen Projection

After clipping, this is the final transformation to get our screen coordinates.

new_x = (x * Width ) / (2.0 * w) + halfWidth;
new_y = (y * Height) / (2.0 * w) + halfHeight;

C语言简单的例子实现++

#include <vector>
#include <cmath>
#include <stdexcept>
#include <algorithm>

struct Vector
{
    Vector() : x(0),y(0),z(0),w(1){}
    Vector(float a, float b, float c) : x(a),y(b),z(c),w(1){}

    /* Assume proper operator overloads here, with vectors and scalars */
    float Length() const
    {
        return std::sqrt(x*x + y*y + z*z);
    }

    Vector Unit() const
    {
        const float epsilon = 1e-6;
        float mag = Length();
        if(mag < epsilon){
            std::out_of_range e("");
            throw e;
        }
        return *this / mag;
    }
};

inline float Dot(const Vector& v1, const Vector& v2)
{
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
}

class Matrix
{
    public:
    Matrix() : data(16)
    {
        Identity();
    }
    void Identity()
    {
        std::fill(data.begin(), data.end(), float(0));
        data[0] = data[5] = data[10] = data[15] = 1.0f;
    }
    float& operator[](size_t index)
    {
        if(index >= 16){
            std::out_of_range e("");
            throw e;
        }
        return data[index];
    }
    Matrix operator*(const Matrix& m) const
    {
        Matrix dst;
        int col;
        for(int y=0; y<4; ++y){
            col = y*4;
            for(int x=0; x<4; ++x){
                for(int i=0; i<4; ++i){
                    dst[x+col] += m[i+col]*data[x+i*4];
                }
            }
        }
        return dst;
    }
    Matrix& operator*=(const Matrix& m)
    {
        *this = (*this) * m;
        return *this;
    }

    /* The interesting stuff */
    void SetupClipMatrix(float fov, float aspectRatio, float near, float far)
    {
        Identity();
        float f = 1.0f / std::tan(fov * 0.5f);
        data[0] = f*aspectRatio;
        data[5] = f;
        data[10] = (far+near) / (far-near);
        data[11] = 1.0f; /* this 'plugs' the old z into w */
        data[14] = (2.0f*near*far) / (near-far);
        data[15] = 0.0f;
    }

    std::vector<float> data;
};

inline Vector operator*(const Vector& v, const Matrix& m)
{
    Vector dst;
    dst.x = v.x*m[0] + v.y*m[4] + v.z*m[8 ] + v.w*m[12];
    dst.y = v.x*m[1] + v.y*m[5] + v.z*m[9 ] + v.w*m[13];
    dst.z = v.x*m[2] + v.y*m[6] + v.z*m[10] + v.w*m[14];
    dst.w = v.x*m[3] + v.y*m[7] + v.z*m[11] + v.w*m[15];
    return dst;
}

typedef std::vector<Vector> VecArr;
VecArr ProjectAndClip(int width, int height, float near, float far, const VecArr& vertex)
{
    float halfWidth = (float)width * 0.5f;
    float halfHeight = (float)height * 0.5f;
    float aspect = (float)width / (float)height;
    Vector v;
    Matrix clipMatrix;
    VecArr dst;
    clipMatrix.SetupClipMatrix(60.0f * (M_PI / 180.0f), aspect, near, far);
    /*  Here, after the perspective divide, you perform Sutherland-Hodgeman clipping 
        by checking if the x, y and z components are inside the range of [-w, w].
        One checks each vector component seperately against each plane. Per-vertex
        data like colours, normals and texture coordinates need to be linearly
        interpolated for clipped edges to reflect the change. If the edge (v0,v1)
        is tested against the positive x plane, and v1 is outside, the interpolant
        becomes: (v1.x - w) / (v1.x - v0.x)
        I skip this stage all together to be brief.
    */
    for(VecArr::iterator i=vertex.begin(); i!=vertex.end(); ++i){
        v = (*i) * clipMatrix;
        v /= v.w; /* Don't get confused here. I assume the divide leaves v.w alone.*/
        dst.push_back(v);
    }

    /* TODO: Clipping here */

    for(VecArr::iterator i=dst.begin(); i!=dst.end(); ++i){
        i->x = (i->x * (float)width) / (2.0f * i->w) + halfWidth;
        i->y = (i->y * (float)height) / (2.0f * i->w) + halfHeight;
    }
    return dst;
}

如果你还在思考这一点,对OpenGL规范所涉及的数学一个非常好的参考。 该DevMaster论坛上 http://www.devmaster.net/ 有很多相关的软件光栅以及不错的文章。

If you still ponder about this, the OpenGL specification is a really nice reference for the maths involved. The DevMaster forums at http://www.devmaster.net/ have a lot of nice articles related to software rasterizers as well.

这篇关于如何转换3D点到2D透视投影?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆