1.简介

基本是翻译和补充 http://www.songho.ca/opengl/gl_projectionmatrix.html

计算机显示器是一个2D的平面,一个3D的场景要被OpenGL渲染必须被投影到2D平面上以生成2D的图像。在OpenGL中,GL_PROJECTION矩阵可以用来进行投影变换。首先,它将所有的顶点数据从相机坐标系(eye coordinates)转换到裁剪坐标系(clip coordinates),然后通过除以裁剪空间坐标的w值,将裁剪空间坐标系转换到归一化设备坐标系(normalized device coordinates,NDC)

我们需要注意的一点就是,裁剪和NDC变换都通过GL_PROJECTION矩阵来完成。之后的文章,将会利用6个参数来构建投影矩阵,这六个参数是:left,right,bottom,top,near,far,分别为近裁剪面的左右下上边界,近裁剪面,远裁剪面。

视锥体剔除是在裁剪坐标下进行的,在转换到NDC坐标系之前。已经变换到裁剪坐标系的坐标\(x_c,y_c,z_c\)会和\(w_c\)进行比较,如果裁剪坐标大于\(w_c\)或小于\(-w_c\),则顶点会被剔除,OpenGL会重建多边形的边。

ps.解释一下为什么要和\(w_c\)进行比较。因为NDC坐标的范围是\([-1,1]\),而裁剪坐标和NDC坐标之间的关系是\(x_c/w_c = x_n\),所以\(x_c\)必须得在\([-w_c,w_c]\)之间才可见,其他两个轴同理。不是在NDC坐标阶段进行裁剪,是因为不可见的顶点,没有必要在对其进行运算,会消耗资源。在作用完投影矩阵后,得到的是齐次坐标,OpenGL会自动除以\(w_c\),以得到笛卡尔坐标,OpenGL应该是在除以\(w_c\)之前进行视锥体剔除工作。

2.透视投影

在透视投影中,1个3D的点在一个像被切了一刀的金字塔的视锥体中,此时的坐标系是相机坐标系,这个坐标系会被映射正方体的NDC坐标系中。

  • \(x:[l,r]->[-1,1]\)
  • \(y:[b,t]->[-1,1]\)
  • \(z:[-n,-f]->[-1,1]\)

相机坐标系定义在右手坐标系,NDC是左手坐标系,所以相机朝着-Z的方向看去,而NDC朝着+Z的方向看去。因为glFrustum()裁剪面的参数必须为正数,所以在创建投影矩阵的时候,我们要对其进行去取反。

ps.glFrustum是opengl类库中的函数,它是将当前矩阵与一个透视矩阵相乘,把当前矩阵转变成透视矩阵,在使用它之前,通常会先调用glMatrixMode(GL_PROJECTION).

void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal),left,right指明相对于垂直平面的左右坐标位置,bottom,top指明相对于水平剪切面的下上位置,nearVal,farVal指明相对于深度剪切面的远近的距离,两个必须为正数。

在OpenGL中,1个3D的点将会被投影到近裁剪平面上,下图展示了点\((x_e,y_e,z_e)\)如何投影到\((x_p,y_p,z_p)\)。

在视锥体的顶视图,我们可以利用相似三角形计算\(x_p\)的值

\[\frac{x_p}{x_e} = \frac{-n}{z_e}\\
x_p = \frac{-nx_e}{z_e}=\frac{nx_e}{-z_e}
\]

同理,在侧视图中,利用相似三角形计算\(y_p\)的值

\[\frac{y_p}{y_e} = \frac{-n}{z_e}\\
y_p = \frac{-ny_e}{z_e}=\frac{ny_e}{-z_e}
\]

我们观察到\(x_p,y_p\)都依赖于\(z_e\),他们都除以\(z_e\),这是第一个线索,来帮助我们构建透视投影矩阵。当相机坐标系经过透视投影矩阵变换后,得到的是裁剪坐标系的齐次坐标,最后通过除以齐次坐标的\(w_c\),来得到NDC

\[\begin{bmatrix}
x_c\\
y_c\\
z_c\\
w_c
\end{bmatrix}
=M_{projection}
\begin{bmatrix}
x_e\\
y_e\\
z_e\\
w_e
\end{bmatrix}
,
\begin{bmatrix}
x_n\\
y_n\\
z_n\\
\end{bmatrix}
=
\begin{bmatrix}
x_c/w_c\\
y_c/w_c\\
z_c/w_c\\
\end{bmatrix}
\]

因此我们可以设置\(w_c\)的值为\(-z_e\),现在投影矩阵看起来是

\[\begin{bmatrix}
x_c\\
y_c\\
z_c\\
w_c
\end{bmatrix}
=
\begin{bmatrix}
.&.&.&.\\
.&.&.&.\\
.&.&.&.\\
0&0&-1&0\\
\end{bmatrix}
\begin{bmatrix}
x_e\\
y_e\\
z_e\\
w_e
\end{bmatrix}
\]

接着,我们需要将\(x_p,y_p\)映射到\(x_n,y_n\),\([l,r]->[-1,1],[b,t]->[-1,1]\)。

相当于是给定l,我要得到-1,给定r,我要得到1,这不就是给定二维平面上的两个点,求其直线方程的问题。

\[令x_n = kx_p+\beta,求其斜率为\frac{1-(-1)}{r-l}=\frac{2}{r-l}\\
带入点(r,1),1 = \frac{2r}{r-l}+\beta\\
化简求得\beta=-\frac{r+l}{r-l}\\
最终得x_n = \frac{2x_p}{r-l}-\frac{r+l}{r-l}
\]

\[令y_n = ky_p+\beta,求其斜率为\frac{1-(-1)}{t-b}=\frac{2}{t-b}\\
带入点(t,1),1 = \frac{2t}{t-b}+\beta\\
化简求得\beta=-\frac{t+b}{t-b}\\
最终得y_n = \frac{2y_p}{t-b}-\frac{t+b}{t-b}
\]

现在有了从\(x_e,y_e\)到\(x_p,y_p\)和从\(x_p,y_p\)到\(x_n,y_n\),现在联立一下就可以得到从\(x_e,y_e\)到\(x_n,y_n\)的关系表达式。

\[x_n = \frac{2x_p}{r-l}-\frac{r+l}{r-l}\\
x_p = \frac{-nx_e}{z_e}=\frac{nx_e}{-z_e}\\
最终可以化简为(\underbrace{\frac{2n}{r-l}x_e+\frac{r+l}{r-l}z_e}_{x_c})/-z_e
\]

同理

\[y_n = \frac{2y_p}{t-b}-\frac{t+b}{t-b}\\
y_p = \frac{-ny_e}{z_e}=\frac{ny_e}{-z_e}\\
最终可以化简为(\underbrace{\frac{2n}{t-b}y_e+\frac{t+b}{t-b}z_e}_{y_c})/-z_e
\]

现在我们的透视矩阵现在是这个样子

\[\begin{bmatrix}
x_c\\
y_c\\
z_c\\
w_c
\end{bmatrix}
=
\begin{bmatrix}
\frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\
0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\
.&.&.&.\\
0&0&-1&0\\
\end{bmatrix}
\begin{bmatrix}
x_e\\
y_e\\
z_e\\
w_e
\end{bmatrix}
\]

现在还剩下矩阵的第三行。\(z_n\)和其他两个轴的坐标稍有不同,因为\(z_e\)总是投影到-n的近裁剪面,但是我们需要不同的z值来进行裁剪和深度测试,另外我们应该可以进行逆操作(逆变换)。因为我们知道z的值不依赖于x,y,我们借用w的值来寻找\(z_n,z_e\)之间的关系,因此我们指定第三行矩阵为

\[\begin{bmatrix}
x_c\\
y_c\\
z_c\\
w_c
\end{bmatrix}
=
\begin{bmatrix}
\frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\
0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\
0&0&A&B\\
0&0&-1&0\\
\end{bmatrix}
\begin{bmatrix}
x_e\\
y_e\\
z_e\\
w_e
\end{bmatrix}
\]

\[z_n = z_c/w_c = \frac{Az_e+Bw_e}{-z_e}
\]

在相机坐标系中,\(w_e\)的值是1,因此有\(z_n = \frac{Az_e+B}{-z_e}\),为了获得A和B的值,我们使用\((z_e,z_n)\)的关系,\((-n,-1),(-f,1)\),然后将他们代入表达式。

\[\frac{-An+B}{n}=-1\\
\frac{-Af+B}{f}=1
\]

联立,这是一个简单二元一次方程组,容易求得

\[A = -\frac{f+n}{f-n}\\
B = -\frac{2fn}{f-n}
\]

所以最终得到

\[z_n = \frac{-\frac{f+n}{f-n}z_e--\frac{2fn}{f-n}}{-z_e}
\]

最终整个投影矩阵的表达式为

\[\begin{bmatrix}
x_c\\
y_c\\
z_c\\
w_c
\end{bmatrix}
=
\begin{bmatrix}
\frac{2n}{r-l}&0&\frac{r+l}{r-l}&0\\
0&\frac{2n}{t-b}&\frac{t+b}{t-b}&0\\
0&0&-\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\
0&0&-1&0\\
\end{bmatrix}
\begin{bmatrix}
x_e\\
y_e\\
z_e\\
w_e
\end{bmatrix}
\]

这个投影矩阵是一般的视锥体,如果是对称的话,有\(r=-l,t=-b\),那么有

\[r+l=0,r-l=2r(width)\\
t+b=0,t-b=2t(height)
\]

最后矩阵可以简单的化为

\[\begin{bmatrix}
\frac{n}{r}&0&0&0\\
0&\frac{n}{t}&0&0\\
0&0&-\frac{f+n}{f-n}&-\frac{2fn}{f-n}\\
0&0&-1&0\\
\end{bmatrix}
\]

注意观察\(z_e,z_n\)的关系式,这是一个非线性的反比例函数,这意味着,在近裁剪平面的是很好,精度很高,而在远裁剪面的时候,精度很低。当\([-n,-f]\)很大时,可能导致深度精度问题(z-fighting),一个较小的\(z_e\)的变化,在远裁剪面可能不会影响\(z_n\)的值,n和f之间的距离应该短一些,从而最小化这个问题。

ps.因为浮点数会存在精度问题,毕竟计算机的存储是离散的。

3.正交投影

正交投影的要比透视投影简单许多,\(x_e,y_e,z_e\)相机坐标系将会线性映射到NDC坐标系。我们仅需要将长方体变为正方体,然后移动至原点。

\[x_n = \frac{1-(-1)}{r-l}x_e+\beta\\
代入(r,1),最终可得\\
x_n = \frac{2}{r-l}x_e-\frac{r+l}{r-l}
\]

同理

\[y_n = \frac{1-(-1)}{t-b}y_e+\beta\\
代入(t,1),最终可得\\
y_n = \frac{2}{t-b}y_e-\frac{t+b}{t-b}
\]

同理

\[z_n = \frac{1-(-1)}{-f-(-n)}z_e+\beta\\
代入(-f,1),最终可得\\
z_n = \frac{-2}{f-n}z_e-\frac{f+n}{f-n}
\]

因为w的值在正交投影中不必要,所以我们设置为1,因此正交投影矩阵为

\[
\begin{bmatrix}
\frac{2}{r-l}&0&0&-\frac{r+l}{r-l}\\
0&\frac{2}{t-b}&0&-\frac{t+b}{t-b}\\
0&0&-\frac{2}{f-n}&-\frac{f+n}{f-n}\\
0&0&0&1\\
\end{bmatrix}
\]

同透视投影一样,如果是对称的话,那么就可以矩阵就可以变简单

\[
\begin{bmatrix}
\frac{1}{r}&0&0&0\\
0&\frac{1}{t}&0&0\\
0&0&-\frac{2}{f-n}&-\frac{f+n}{f-n}\\
0&0&0&1\\
\end{bmatrix}
\]

[OpenGL](翻译+补充)投影矩阵的推导的更多相关文章

  1. (转)投影矩阵的推导(Deriving Projection Matrices)

    转自:http://blog.csdn.net/gggg_ggg/article/details/45969499 本文乃<投影矩阵的推导>译文,原文地址为: http://www.cod ...

  2. OpenGL中投影矩阵的推导

    本文主要是对红宝书(第八版)第五章中给出的透视投影矩阵和正交投影矩阵做一个简单推导.投影矩阵的目的是:原始点P(x,y,z)对应后投影点P'(x',y',z')满足x',y',z'∈[-1,1]. 一 ...

  3. OpenGL 模型视图投影矩阵 仿射矩阵

    矩阵基础知识 要对矩阵进行运算,必须先要了解矩阵的计算公式,这个知识的内容涉及到了线性代数. 我们知道在Cocos2dx中,有关于平移,旋转,缩放等等操作,都必须要进行矩阵的乘法. 只需要一张图就能理 ...

  4. OpenGL中的投影使用

    OpenGL中的投影使用 在OpenGL中,投影矩阵指定了可视区域的大小和形状.对于正投影与透视投影这两种不同的投影类型,它们分别有各自的用途. 正投影 它适用于2D图形,如文本.建筑画图等.在它的应 ...

  5. 【脚下生根】之深度探索安卓OpenGL投影矩阵

    世界变化真快,前段时间windows开发技术热还在如火如荼,web技术就开始来势汹汹,正当web呈现欣欣向荣之际,安卓小机器人,咬过一口的苹果,winPhone开发平台又如闪电般划破了混沌的web世界 ...

  6. 【转】d3d的投影矩阵推导

    原帖地址:http://blog.csdn.net/popy007/article/details/4091967 上一篇文章中我们讨论了透视投影变换的原理,分析了OpenGL所使用的透视投影矩阵的生 ...

  7. 关于Opengl投影矩阵

    读 http://www.songho.ca/opengl/gl_projectionmatrix.html 0.投影矩阵的功能: 将眼睛空间中的坐标点 [图A的视椎体]     映射到     一个 ...

  8. OpenGL投影矩阵(Projection Matrix)构造方法

    (翻译,图片也来自原文) 一.概述 绝大部分计算机的显示器是二维的(a 2D surface).在OpenGL中一个3D场景需要被投影到屏幕上成为一个2D图像(image).这称为投影变换(参见这或这 ...

  9. OpenGL投影矩阵

    概述 透视投影 正交投影 概述 计算机显示器是一个2D平面.OpenGL渲染的3D场景必须以2D图像方式投影到计算机屏幕上.GL_PROJECTION矩阵用于该投影变换.首先,它将所有定点数据从观察坐 ...

随机推荐

  1. window 10电脑永不熄屏的方法

    你的电脑是不是人还没有离开一会儿,经常锁屏,输入密码??反复反复,特别的折磨人,别急,下面我教你,告别反复,从此我的电脑我做主. 第一步,打开设置,进入个性化界面,点击锁屏界面,往下滑 第二步,找到屏 ...

  2. Spring处理@Configuration的分析

    Spring处理@Configuration的分析 声明:本文若有任何纰漏.错误,还请不吝指出! 序言 @Configuration注解在SpringBoot中作用很大,且不说SpringBoot中的 ...

  3. leetcode 第184场周赛第一题(数组中的字符串匹配)

    一.函数的运用 1,strstr(a,b); 判断b是否为a的子串,如果是,返回从b的开头开始到a的结尾 如“abcdefgh” “de” 返回“defgh”: 如果不是子串,返回NULL: 2,me ...

  4. 接口testing需要的技能

    1.什么是接口测试? 定义:测试系统组件间接口的一种测试.主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点,重点是检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑依赖关系等: 目的 ...

  5. 视口viewport

    一.viewport 1. 何为视口? 视口是浏览器显示网页的矩形区域. 2. 默认视口:模拟一个大约1000像素宽的视口. 理想视口:因设备.操作系统.浏览器而异,一般而言,手机宽带大约在300-5 ...

  6. P2444 [POI2000]病毒 AC自动机

    P2444 [POI2000]病毒 #include <bits/stdc++.h> using namespace std; ; struct Aho_Corasock_Automato ...

  7. 第一章 Python 基础

    1. 为什么学习 Python? 答题路线:a.python的优点,b.python的应用领域广 具体: 优点 1.python语法非常优雅,简单易学 2.免费开源 3.跨平台,可以自由移植 4.可扩 ...

  8. Code Your First API With Node.js and Express: Set Up the Server

    How to Set Up an Express API Server in Node.js In the previous tutorial, we learned what the REST ar ...

  9. Springboot 关于日期时间格式化处理方式总结

    项目中使用LocalDateTime系列作为DTO中时间的数据类型,但是SpringMVC收到参数后总报错,为了配置全局时间类型转换,尝试了如下处理方式. 注:本文基于Springboot2.x测试, ...

  10. 去掉shiro登录时url里的JSESSIONID https://blog.csdn.net/aofavx/article/details/51701012

    经过查找论坛和分析源码,确认了是在ShiroHttpServletResponse里加上的. 因此继承ShiroHttpServletResponse类,覆盖相应方法,再重写 ShiroFilterF ...