https://blog.csdn.net/smstong/article/details/50290327

学习OpenGL必须要有一个感性的认识,最经典的类比就是照相机照相模型。现在几乎人人都有一部自带照相功能的手机了,所以这个模型对于绝大多数人来说都是非常容易理解的。

1 实际照相步骤

1.1 布置场景和调整照相机位置


之所以把布置场景和调整相机位置放到了一起,是因为这两个步骤的顺序不是固定的,摄影师既可以自己移动相机,也可以指挥被拍的人移动位置。

1.3 选择镜头,对焦(Focus)

镜头的选择主要是为了达到合适的视角范围,镜头确定以后,焦距就确定了。

一般来说,调焦(Focus)并不是改变镜头的焦距,而是改变镜头与胶片的距离(像距)。

凸透镜能成像,一般用凸透镜做照相机的镜头时,它成的最清晰的像一般不会正好落在焦点上,或者说,最清晰的像到光心的距离(像距)一般不等于焦距,而是略大于焦距。具体的距离与被照的物体与镜头的距离(物距)有关,物距越大,像距越小,(但实际上总是大于焦距)。

1.4 按下快门

一切都调整好了,就可以拍照了。按下快门的那一刻,相机位置、场景布置都定格了,不再改变。感光器件(胶片)会记录下这一时刻的场景,绘制成一个矩形的二位像素图。

1.5 在电脑窗口中欣赏图片

然后就可以把照片转移到电脑中,使用看图软件把图片放到合适的位置进行欣赏了。

2 OpenGL的相机模型

2.0 确定胶片位置

OpenGL中没有真正的胶片,必须把拍摄到的图像放到一个指定的地方显示,这个地方就是视口。在上篇博文中已经专门介绍了视口的设置方法了,这里不再累述。

2.1 确立场景(世界)坐标系

与实际照相不同的是,OpenGL默认的物理世界里是没有任何物体的。需要程序员通过代码来建立场景中的物体。建模的最基本要素是确定物体的位置,而要描述物体位置首先要做的就是确定一个世界(场景)坐标系。这个坐标系必须是标准的笛卡尔右手坐标系,而其原点和长度单位却没有任何限制,由程序员根据实际需要自由确定。例如,对于整个城市的场景,原点可以设置在市政府,x,y方向可以分别设置为东西方向和南北方向,长度单位设置为米。而对于分子结构的场景,长度单位则可能是纳米了。

同其他状态变量一样,OpenGL的世界坐标系也有默认值,那就是一个标准右手三维直角坐标系,y轴向上,原点位置和长度单位没有定义。

创建坐标系是程序员的思维中进行的,无法直接体现到代码上,通过后面相机、物体的定位坐标数值来体现。

2.2 在世界坐标系中确定相机位置与方向

与场景中的其他物体一样,相机也是在世界坐标系中进行定位的。在OpenGL中,改变相机位置和方向的行为叫做视图变换,完成这个功能的函数是gluLookAt()

void WINAPI gluLookAt(
GLdouble eyex,
GLdouble eyey,
GLdouble eyez,
GLdouble centerx,
GLdouble centery,
GLdouble centerz,
GLdouble upx,
GLdouble upy,
GLdouble upz
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

首先,确定的是照相机的坐标,通过(eyex, eyey, eyez)确定。

其次,是确定镜头的朝向,通过瞄准线上一点(centerx,centery,centerz)指定,需要注意的是,这个点并不是拍摄时对焦的点,只是用来确定镜头的方向而已。最终方向就是从点(eyex,eyey,eyez)到(centerx,centery,centerz)的连线方向。

最后,是旋转镜头,通过指定向上方向上经过的一点(upx,upy,upz)确定。需要注意的是,向上方向是由(0,0,0)到(upx,upy,upz)连线确定的,与相机坐标(eyex,eyey,eyez)没有关系。

OpenGL同样会在初始化时,为相机的位置和方向在世界坐标系中指定一个默认值。那就是,照相机位于原点,镜头朝向z轴负方向,向上方向为y轴正方向。相当于调用了gluLookAt(0,0,0,0,0,0,0,1,0)

2.3 在世界坐标系中建立物理世界模型

与实际照相不同,OpenGL里的物体都是虚拟的,想象出来的。具体来说,是通过在世界坐标系里确定其位置来描述的。这种虚拟的特性给了OpenGL比实际照相更多的控制性。例如,虚拟的物体可以任意摆放,任意建立或删除,任意着色,任意形状。只要你敢想象,没有人能阻挡你建立一座倒立的房子。由于这些物体可以任意移动,所以完全可以把照相机始终固定,只是通过移动物体来达到和移动相机相同的效果。相机成像的结果就是相机和物体相对位置来确定的,所以理论上移动物体和移动相机都能达到相同的效果。实际上,OpenGL就是使用了一个矩阵来描述这种相对关系,这就是大名鼎鼎的模型视图矩阵。

移动物体在OpenGL里被叫做模型变换。视图变换和模型变换改变的都是同一个模型视图矩阵。模型变换就是修改模型视图矩阵,OpenGL为最常用的三种变换提供了函数:

/*平移*/
void WINAPI glTranslatef(
GLfloat x,
GLfloat y,
GLfloat z
);
/*旋转*/
void WINAPI glRotatef(
GLfloat angle,
GLfloat x,
GLfloat y,
GLfloat z
);
/*缩放*/
void WINAPI glScalef(
GLfloat x,
GLfloat y,
GLfloat z
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

一般情况下,这三种变换完全能够满足程序的需要。如果需要特殊的变化,OpenGL也提供了直接操作矩阵的方式。


void WINAPI glMultMatrixf(
const GLfloat *m
);
  • 1
  • 2
  • 3
  • 4

2.4 视图变换与模型变换的抉择

具体是采用视图变换还是模型变换,取决于具体的场景渲染需求,往往一种变换比另一种变换更直接、更容易理解、更简单。例如驾驶飞机游戏中,从飞机内部向外看时,则视图变换明显要更简单、更符合正常思维。

2.5 在照相机坐标系中确定可视范围,对焦(投影变换)

镜头有个横向有个视角问题,纵深也有范围,不可能看到无限远的物体。实际照相时镜头的不同决定了水平视角大小,光线的强弱决定了能拍摄的最大纵深,对焦决定了胶片位置。在OpenGL中,则通过一个函数完成了这些功能。

OpenGL提供了如下函数来完成这个任务。

void WINAPI glFrustum(
GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble zNear,
GLdouble zFar
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这个可视范围也是在照相机坐标系中指定的。照相机坐标系的定义如下:

  • 原点位于照相机本身
  • 照相机的向上方向(由gluLookAt指定)为y轴正方向,照相机朝向为z轴负方向,x轴根据右手定则确定;
  • 长度单位与世界坐标系相同

这样近平面左下角坐标就是(left, bottom, -near),右上角坐标是(right, top, -near)。 
注意:参数中的zNear, zFar都是距离值,必须为正;其他参数则是坐标值,可正可负。

由于近平面也是最终的投影平面,所以zNear也就成了对焦距离了。

实际使用时,glFrustum中各个参数的确定不是很方便,所以OpenGL提供了另外一个更方便调用的函数来确定可视范围。

gluPerspective()

void WINAPI gluPerspective(
GLdouble fovy,
GLdouble aspect,
GLdouble zNear,
GLdouble zFar
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

当然,这个函数中的参数也是在相机坐标系中确定的。其中zNear, zFar的含义与glFrustum()中的相同。 
fovy是在垂直方向的可视角度,单位为度,范围为(0.0,180.0),aspect是水平方向可视角度范围与垂直方向可视角度范围的比例,也就是水平方向可视范围为 fovy*aspect。

这个函数定义了一个对称的可视空间,用起来要比glFrustum方便很多。

2.6 调用glBegin(); …; glEnd()拍照

前面的所有的设置确定了最终拍摄的范围、方向、存放地。最后一步就是进行实际拍摄了。在OpenGL中,拍摄动作是伴随建模一起的。

3 OpenGL相机模型与实际相机的不同

虽然用相机拍照过程来比喻OpenGL渲染过程非常适合,但是毕竟OpenGL是计算机软件行为,所以和真正的还是相机拍摄过程还是有很多的不同之处。

3.1 物体和相机都可以任意移动

这个在前面已经说过了。

3.2 最终的“照片”可以是多次拍摄合成的

OpenGL的每一次glBegin(); …; glEnd(); 相当于一次拍摄,而最终的“照片”是多次拍摄的合成结果,而且每次拍摄都可以使用不同的场景、不同的相机位置、不同的视口….。

程序中可以无限次调用glBegin(); …; glEnd()进行拍摄。

3.3 特殊的投影方式—正投影

前面介绍的glFrustum()以非常接近实际相机的方式设置了可是范围与投影方式(透视投影)。然而OpenGL毕竟是软件,灵活性要比实际相机强得多,它还提供了一种正投影的方式。这相当于照相机的镜头不是凸透镜,而是平面玻璃,现实中不可能存在这样的照相机。

void WINAPI glOrtho(
GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble zNear,
GLdouble zFar
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这个函数确定的可视范围是一个平行于视线的长方体。同样也是在照相机坐标系中点定义坐标。

 
可视范围内的物体,无论近远,投影到近平面上后都不改变其大小。

4 相机模型使用建议

4.1 尽量让编程符合这个模型

照相机模型很好的反映了OpenGL内部的运算过程,非常适合构造场景时使用它来帮助思考。程序中也尽量使用这种模型去实现。除非特殊需要,避免不符合该模型的操作方法,例如一帧图像的多次渲染使用不同相机位置,但是使用同一个视口,这相当于拍摄了多张照片,然后叠加到了一起,容易让人摸不着头脑。

4.2 注意区分两大坐标系:世界坐标系和相机坐标系。

首先在世界坐标系中确定相机位置、视线方向、向上方向,然后在相机坐标系中确定可视范围。

世界坐标系的轴方向由程序员随意定义,只要满足笛卡尔右手定则就可以:例如数学上常见的三维坐标系,往往是z轴向上,而不是OpenGL中常见的y轴向上。

相机坐标系的原点,各轴方向则必须由相机,视线方向和向上方向来确定:原点必定位于相机本身,视线方向必定是z轴负方向,向上方向必定是y轴正向。

4.3 实例

下面是一个漫游观察四面体的小实例。

初始化时,设置可视空间和相机初始位置。

// 安排相机
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(
3, 0, 1,
0, 0, 1,
0, 0, 1); // 确定可视范围
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(100, 1, 0.5, 100);
::InvalidateRect(hWnd, NULL, TRUE);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

鼠标单击一下客户区,照相机就会绕z轴你是神旋转5度,可视空间不再改变。代码如下:

void OnLeftButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static float a = 0; float x, y, z;
x = 3 * cos(a*3.14/180);
y = 3 * sin(a*3.14/180);
z = 1;
a += 5;
while(a > 360) {
a -= 360;
} glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x, y, z, 0, 0, 1, 0, 0, 1);
::InvalidateRect(hWnd, NULL, TRUE);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

实际的渲染代码为:

void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
glClear(GL_COLOR_BUFFER_BIT);
// 绘制世界坐标系
glColor3f(1, 1, 1);
glBegin(GL_LINES);
glVertex3d(0, 0, 0);
glVertex3d(100, 0, 0);
glVertex3d(0, 0, 0);
glVertex3d(0, 100, 0);
glVertex3d(0, 0, 0);
glVertex3d(0, 0, 100);
glEnd();
// 在原点处,建立一个四面体,四个顶点分别位于三个轴上,和原点处。
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glBegin(GL_TRIANGLES);
glColor3f(1, 0, 0);
glVertex3d(0, 1, 0);
glVertex3d(1, 0, 0);
glVertex3d(0, 0, 0); glColor3f(0, 1, 0);
glVertex3d(1, 0, 0);
glVertex3d(0, 0, 2);
glVertex3d(0, 0, 0); glColor3f(0, 0, 1);
glVertex3d(0, 0, 2);
glVertex3d(0, 1, 0);
glVertex3d(0, 0, 0); glColor3f(1, 1, 0);
glVertex3d(1, 0, 0);
glVertex3d(0, 1, 0);
glVertex3d(0, 0, 2); glEnd();
HDC hdc = ::GetDC(hWnd);
::SwapBuffers(hdc);
::ReleaseDC(hWnd, hdc);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

运行截图:

源码下载

OpenGL边用边学------2 经典照相机模型的更多相关文章

  1. 一步步教你轻松学朴素贝叶斯模型算法Sklearn深度篇3

    一步步教你轻松学朴素贝叶斯深度篇3(白宁超   2018年9月4日14:18:14) 导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果.所以很受欢迎,对 ...

  2. 如何将同一云服务下的虚拟机从经典部署模型迁移到 Azure Resource Manager

    适用场景 用户希望将特定云服务下的所有虚拟机从经典部署模型(以下简称:ASM)迁移到 Azure Resource Manager(以下简称:ARM). Note 如果云服务下使用 VNET 也希望将 ...

  3. 如何将同一 VNET 下的虚拟机从经典部署模型迁移到 Azure Resource Manager

    本文内容 适用场景 解决方案 适用场景 用户拥有多个云服务但是在同一个 VNET 下,希望将这些虚拟机从经典部署模型(以下简称:ASM)迁移到 Azure Resource Manager(以下简称: ...

  4. 将 ExpressRoute 线路从经典部署模型转移到 Resource Manager 部署模型

    本文概述将 Azure ExpressRoute 线路从经典部署模型转移到 Azure Resource Manager 部署模型的效果. Azure 当前使用两种部署模型:Resource Mana ...

  5. 使用 Azure PowerShell 将 IaaS 资源从经典部署模型迁移到 Azure Resource Manager

    以下步骤演示了如何使用 Azure PowerShell 命令将基础结构即服务 (IaaS) 资源从经典部署模型迁移到 Azure Resource Manager 部署模型. 也可根据需要通过 Az ...

  6. 规划将 IaaS 资源从经典部署模型迁移到 Azure Resource Manager

    尽管 Azure 资源管理器提供了许多精彩功能,但请务必计划迁移,以确保一切顺利进行. 花时间进行规划可确保执行迁移活动时不会遇到问题. Note 以下指导的主要参与者为 Azure 客户顾问团队,以 ...

  7. 有关平台支持的从经典部署模型到 Azure Resource Manager 的迁移的技术深入探讨

    本文将深入探讨如何从 Azure 经典部署模型迁移到 Azure Resource Manager 部署模型. 本文将介绍资源和功能级别的资源,让用户了解 Azure 平台如何在两种部署模型之间迁移资 ...

  8. 平台支持的从经典部署模型到 Azure Resource Manager 的 IaaS 资源迁移

    本文介绍如何才能将基础结构即服务 (IaaS) 资源从经典部署模型迁移到 Resource Manager 部署模型. 用户可以阅读有关 Azure Resource Manager 功能和优点的更多 ...

  9. 有关从经典部署模型迁移到 Azure Resource Manager 部署模型的常见问题

    此迁移计划是否影响 Azure 虚拟机上运行的任何现有服务或应用程序? 不可以. VM(经典)是公开上市的完全受支持的服务. 你可以继续使用这些资源来拓展你在 Azure 上的足迹. 如果我近期不打算 ...

随机推荐

  1. 寻找 IBatisNet 批量插入(批量复制) 的心路历程

    1.IBatisNet本身就是一个比较早的ORM框架,再加上是从java iBatis移值过来的,其流行程度不是特别高资料也不是很多(一年前),不过最近好像搜索比较多了一些(可能是训练的结果吧) 2. ...

  2. 【太虚AR_v0.1】使用教程 | SLAM(Markerless)

    https://blog.csdn.net/VOID_AR/article/details/77715274

  3. Oracle Function:TO_CHAR

    Description The Oracle/PLSQL TO_CHAR function converts a number or date to a string.将数字转换为日期或字符串 Syn ...

  4. Hello Redis - Voting on articles

    Redis in Action JOSIAH L. CARLSON MANNING Shelter Island ONE_WEEK_IN_SECONDS = 7 * 86400 VOTE_SCORE ...

  5. 2018/03/18 isset、empty、is_null的区别

    在平常的工作中,判断一个值是否为空的情况时,会直接使用 if ($var) 这种形式,有时也会使用这三个函数进行比较,但是当时也没有很深入的学习. -- 还是通过实例来判断这几个的用法和场景 首先定义 ...

  6. CF989C A Mist of Florescence 构造

    正解:构造 解题报告: 先放传送门yep! 然后构造题我就都直接港正解了QwQ没什么可扯的QwQ 这题的话,首先这么想吼 如果我现在构造的是个4*4的 举个栗子 AABB ACBB AADB DBCA ...

  7. Python描述器引导(转)

    原文:http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html 1. Python描述器引导(翻译) 作者: Raymond ...

  8. 【Python虫师】多窗口定位

    <注意>iframe框架 iframe也称作嵌入式框架,嵌入式框架和框架网页类似,它可以把一个网页的框架和内容嵌入在现有的网页中. 框架(framework)是一个基本概念上的结构,用于去 ...

  9. 走进C++程序世界------IO标准库介绍

    流概述    流是C++标准的组成部分,流的主要目标是,将从磁盘读取文件或将输入写入控制台屏幕的问题封装起来,创建流后程序猿就能够使用它.流将负责处理全部的细节. IO类库 在C++输入、输出操作是通 ...

  10. 总结)Nginx/LVS/HAProxy负载均衡软件的优缺点详解

    总结)Nginx/LVS/HAProxy负载均衡软件的优缺点详解 PS:Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件,本人都在多个项目中实施过,参考了一些资料,结合自己的一些使 ...