如何转换3D点到2D透视投影? [英] How to convert a 3D point into 2D perspective projection?
问题描述
我目前正在与使用贝塞尔曲线和曲面绘制著名犹他茶壶。使用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:
- 将您的三维点的逆矩阵的摄像头,然后提供他们所需的任何转换。如果你有表面法线,变换他们很好,但以w设置为零,因为你不想翻译法线。你变换法线与基体必须的各向同性的;缩放和剪切使法线格式不正确。
- 变换点用夹子空间矩阵。此矩阵缩放x和y带和高宽比,视场中,鳞片ž由近及远剪切面,和插头的'老'Z为瓦特改造后,你要分的x,y和z由瓦特这就是所谓的透视分割的
- 现在,您的顶点是剪辑空间,并且要进行裁剪,这样你就不会呈现视区范围之外的任何像素。 萨瑟兰 - 霍奇曼剪裁最wides使用$ P $垫裁剪算法。
- 变换x和y相对于瓦特和半宽和半高。您的x和y坐标现在在视区坐标。 w被丢弃,但1 / w和z通常保存,因为1 / w被要求做透视-正确插穿过多边形表面和z被存储在Z缓冲器和用于深度测试。
- 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.
- 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.
- 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.
- 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屋!