OpenGL Projection Matrix :http://www.songho.ca/opengl/gl_projectionmatrix.html

OpenGL投影矩阵详解
本文深入讲解了OpenGL中两种主要的投影方式:透视投影与正交投影。详细介绍了如何通过六个参数构建GL_PROJECTION矩阵,并解释了从眼坐标到裁剪坐标再到归一化设备坐标的转换过程。

OpenGL Projection Matrix

Related Topics: OpenGL Transformation

Updates: The MathML version is available here.

Overview

A computer monitor is a 2D surface. A 3D scene rendered by OpenGL must be projected onto the computer screen as a 2D image. GL_PROJECTION matrix is used for this projection transformation. First, it transforms all vertex data from the eye coordinates to the clip coordinates. Then, these clip coordinates are also transformed to the normalized device coordinates (NDC) by dividing with w component of the clip coordinates.

A triangle clipped by frustum
A triangle clipped by frustum

Therefore, we have to keep in mind that both clipping (frustum culling) and NDC transformations are integrated into GL_PROJECTION matrix. The following sections describe how to build the projection matrix from 6 parameters; left, right, bottom, top, near and far boundary values.

Note that the frustum culling (clipping) is performed in the clip coordinates, just before dividing by wc. The clip coordinates, xc, yc and zc are tested by comparing with wc. If each clip coordinate is less than -wc, or greater than wc, then the vertex will be discarded. Then, OpenGL will reconstruct the edges of the polygon where clipping occurs.

Perspective Projection

OpenGL Perspective Frustum and NDC
Perspective Frustum and Normalized Device Coordinates (NDC)

In perspective projection, a 3D point in a truncated pyramid frustum (eye coordinates) is mapped to a cube (NDC); the range of x-coordinate from [l, r] to [-1, 1], the y-coordinate from [b, t] to [-1, 1] and the z-coordinate from [n, f] to [-1, 1].

Note that the eye coordinates are defined in the right-handed coordinate system, but NDC uses the left-handed coordinate system. That is, the camera at the origin is looking along -Z axis in eye space, but it is looking along +Z axis in NDC. Since glFrustum() accepts only positive values of near and far distances, we need to negate them during the construction of GL_PROJECTION matrix.

In OpenGL, a 3D point in eye space is projected onto the near plane (projection plane). The following diagrams show how a point (xe, ye, ze) in eye space is projected to (xp, yp, zp) on the near plane.

Top View of Frustum
Top View of Frustum
Side View of Frustum
Side View of Frustum

From the top view of the frustum, the x-coordinate of eye space, xe is mapped to xp, which is calculated by using the ratio of similar triangles;

From the side view of the frustum, yp is also calculated in a similar way;

Note that both xp and yp depend on ze; they are inversely propotional to -ze. In other words, they are both divided by -ze. It is a very first clue to construct GL_PROJECTION matrix. After the eye coordinates are transformed by multiplying GL_PROJECTION matrix, the clip coordinates are still a homogeneous coordinates. It finally becomes the normalized device coordinates (NDC) by divided by the w-component of the clip coordinates. (See more details on OpenGL Transformation.)
Clip CoordinatesNormalized Device Coordinates

Therefore, we can set the w-component of the clip coordinates as -ze. And, the 4th of GL_PROJECTION matrix becomes (0, 0, -1, 0).

Next, we map xp and yp to xn and yn of NDC with linear relationship; [l, r] ⇒ [-1, 1] and [b, t] ⇒ [-1, 1].


Mapping from xp to xn

 


Mapping from yp to yn

 

Then, we substitute xp and yp into the above equations.

Note that we make both terms of each equation divisible by -ze for perspective division (xc/wc, yc/wc). And we set wc to -ze earlier, and the terms inside parentheses become xc and yc of the clip coordiantes.

From these equations, we can find the 1st and 2nd rows of GL_PROJECTION matrix.

Now, we only have the 3rd row of GL_PROJECTION matrix to solve. Finding zn is a little different from others because ze in eye space is always projected to -n on the near plane. But we need unique z value for the clipping and depth test. Plus, we should be able to unproject (inverse transform) it. Since we know z does not depend on x or y value, we borrow w-component to find the relationship between zn and ze. Therefore, we can specify the 3rd row of GL_PROJECTION matrix like this.

In eye space, we equals to 1. Therefore, the equation becomes;

To find the coefficients, A and B, we use the (ze, zn) relation; (-n, -1) and (-f, 1), and put them into the above equation.

To solve the equations for A and B, rewrite eq.(1) for B;

Substitute eq.(1') to B in eq.(2), then solve for A;

Put A into eq.(1) to find B;

We found A and B. Therefore, the relation between ze and zn becomes;

Finally, we found all entries of GL_PROJECTION matrix. The complete projection matrix is;
OpenGL Perspective Projection Matrix
OpenGL Perspective Projection Matrix

This projection matrix is for a general frustum. If the viewing volume is symmetric, which is and , then it can be simplified as;

Before we move on, please take a look at the relation between ze and zn, eq.(3) once again. You notice it is a rational function and is non-linear relationship between ze and zn. It means there is very high precision at the near plane, but very little precision at the far plane. If the range [-n, -f] is getting larger, it causes a depth precision problem (z-fighting); a small change of ze around the far plane does not affect on zn value. The distance between n and f should be short as possible to minimize the depth buffer precision problem.

Comparison of depth precision
Comparison of Depth Buffer Precisions

Orthographic Projection

OpenGL Orthographic Volume and NDC
Orthographic Volume and Normalized Device Coordinates (NDC)

Constructing GL_PROJECTION matrix for orthographic projection is much simpler than perspective mode.

All xe, ye and ze components in eye space are linearly mapped to NDC. We just need to scale a rectangular volume to a cube, then move it to the origin. Let's find out the elements of GL_PROJECTION using linear relationship.

 


Mapping from xe to xn

 


Mapping from ye to yn

 


Mapping from ze to zn

 

Since w-component is not necessary for orthographic projection, the 4th row of GL_PROJECTION matrix remains as (0, 0, 0, 1). Therefore, the complete GL_PROJECTION matrix for orthographic projection is;
OpenGL Orthographic Projection Matrix
OpenGL Orthographic Projection Matrix

It can be further simplified if the viewing volume is symmetrical, and .
OpenGL Symmetric Orthographic Projection Matrix


在计算机图形学中,透视投影矩阵用于将3D场景投影到2D屏幕上,模拟人眼或相机的视角效果。使用Eigen库可以方便地实现一个函数来逐个元素构建透视投影矩阵。 以下是一个实现 `get_projection_matrix` 函数的示例,该函数根据给定的 `eye_fov`(视野角度)、`aspect_ratio`(纵横比)、`zNear`(近裁剪平面)和 `zFar`(远裁剪平面)参数逐个元素地构建透视投影矩阵: ```cpp #include <Eigen/Dense> #include <cmath> Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) { // 创建一个4x4单位矩阵 Eigen::Matrix4f projection = Eigen::Matrix4f::Identity(); // 计算视野角度的正切值 float tan_half_fov = std::tan(eye_fov / 2.0f); // 构建透视投影矩阵的各个元素 projection(0, 0) = 1.0f / (aspect_ratio * tan_half_fov); projection(1, 1) = 1.0f / tan_half_fov; projection(2, 2) = -(zFar + zNear) / (zFar - zNear); projection(2, 3) = -1.0f; projection(3, 2) = -(2.0f * zFar * zNear) / (zFar - zNear); projection(3, 3) = 0.0f; return projection; } ``` ### 代码解析 1. **包含头文件**: - `Eigen/Dense`:用于使用Eigen库中的密集矩阵和向量类型。 - `cmath`:用于数学函数,如 `std::tan` 和 `std::cos`。 2. **创建单位矩阵**: - 使用 `Eigen::Matrix4f::Identity()` 创建一个4x4的单位矩阵,并将其赋值给 `projection` 变量。单位矩阵是透视投影矩阵的基础。 3. **计算视野角度的正切值**: - `tan_half_fov` 是视野角度的一半的正切值,用于后续计算投影矩阵的缩放因子。 4. **逐个元素设置透视投影矩阵**: - **第0行第0列**:`projection(0, 0)` 设置为 `1.0f / (aspect_ratio * tan_half_fov)`,用于控制水平方向的缩放。 - **第1行第1列**:`projection(1, 1)` 设置为 `1.0f / tan_half_fov`,用于控制垂直方向的缩放。 - **第2行第2列**:`projection(2, 2)` 设置为 `-(zFar + zNear) / (zFar - zNear)`,用于控制深度方向的线性变换。 - **第2行第3列**:`projection(2, 3)` 设置为 `-1.0f`,用于将深度值映射到 [-1, 1] 范围。 - **第3行第2列**:`projection(3, 2)` 设置为 `-(2.0f * zFar * zNear) / (zFar - zNear)`,用于将深度值映射到 [-1, 1] 范围。 - **第3行第3列**:`projection(3, 3)` 设置为 `0.0f`,用于将齐次坐标转换为3D坐标。 5. **返回投影矩阵**: - 最后返回构建好的透视投影矩阵 `projection`。 ### 示例用法 ```cpp int main() { float eye_fov = M_PI / 4.0f; // 45度视野角度 float aspect_ratio = 16.0f / 9.0f; // 16:9纵横比 float zNear = 0.1f; // 近裁剪平面 float zFar = 100.0f; // 远裁剪平面 Eigen::Matrix4f proj_matrix = get_projection_matrix(eye_fov, aspect_ratio, zNear, zFar); // 打印投影矩阵 std::cout << "Projection Matrix:\n" << proj_matrix << std::endl; return 0; } ``` 在这个示例中,`main` 函数调用了 `get_projection_matrix` 并打印了生成的透视投影矩阵。 ### 注意事项 - 确保在编译时链接了Eigen库,并且正确设置了包含路径。 - 视野角度 `eye_fov` 应该以弧度为单位传递,如果使用角度值,可以使用 `M_PI / 180.0f` 进行转换。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值