OpenGL边用边学------2 经典照相机模型
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 经典照相机模型的更多相关文章
- 一步步教你轻松学朴素贝叶斯模型算法Sklearn深度篇3
一步步教你轻松学朴素贝叶斯深度篇3(白宁超 2018年9月4日14:18:14) 导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果.所以很受欢迎,对 ...
- 如何将同一云服务下的虚拟机从经典部署模型迁移到 Azure Resource Manager
适用场景 用户希望将特定云服务下的所有虚拟机从经典部署模型(以下简称:ASM)迁移到 Azure Resource Manager(以下简称:ARM). Note 如果云服务下使用 VNET 也希望将 ...
- 如何将同一 VNET 下的虚拟机从经典部署模型迁移到 Azure Resource Manager
本文内容 适用场景 解决方案 适用场景 用户拥有多个云服务但是在同一个 VNET 下,希望将这些虚拟机从经典部署模型(以下简称:ASM)迁移到 Azure Resource Manager(以下简称: ...
- 将 ExpressRoute 线路从经典部署模型转移到 Resource Manager 部署模型
本文概述将 Azure ExpressRoute 线路从经典部署模型转移到 Azure Resource Manager 部署模型的效果. Azure 当前使用两种部署模型:Resource Mana ...
- 使用 Azure PowerShell 将 IaaS 资源从经典部署模型迁移到 Azure Resource Manager
以下步骤演示了如何使用 Azure PowerShell 命令将基础结构即服务 (IaaS) 资源从经典部署模型迁移到 Azure Resource Manager 部署模型. 也可根据需要通过 Az ...
- 规划将 IaaS 资源从经典部署模型迁移到 Azure Resource Manager
尽管 Azure 资源管理器提供了许多精彩功能,但请务必计划迁移,以确保一切顺利进行. 花时间进行规划可确保执行迁移活动时不会遇到问题. Note 以下指导的主要参与者为 Azure 客户顾问团队,以 ...
- 有关平台支持的从经典部署模型到 Azure Resource Manager 的迁移的技术深入探讨
本文将深入探讨如何从 Azure 经典部署模型迁移到 Azure Resource Manager 部署模型. 本文将介绍资源和功能级别的资源,让用户了解 Azure 平台如何在两种部署模型之间迁移资 ...
- 平台支持的从经典部署模型到 Azure Resource Manager 的 IaaS 资源迁移
本文介绍如何才能将基础结构即服务 (IaaS) 资源从经典部署模型迁移到 Resource Manager 部署模型. 用户可以阅读有关 Azure Resource Manager 功能和优点的更多 ...
- 有关从经典部署模型迁移到 Azure Resource Manager 部署模型的常见问题
此迁移计划是否影响 Azure 虚拟机上运行的任何现有服务或应用程序? 不可以. VM(经典)是公开上市的完全受支持的服务. 你可以继续使用这些资源来拓展你在 Azure 上的足迹. 如果我近期不打算 ...
随机推荐
- SSH教程从零打造在线网盘系统前言&目录
本系列教程内容提要 本系列教程是一个学习教程,是关于Java工程师的SSH(Struts2+Spring+Hibernate)系列教程,本教程将会分为四个部分和大家一同打造一个在线网盘系统,由于教程是 ...
- 小程序中navigator和wx.navigateTo,wx.redirectTo,wx.reLaunch,wx.switchTab,wx.navigateBack的用法
如果用一句话来表明navigator和API中wx.系列的跳转有什么区别,那就是navigator是在wxml中用标签添加open-type属性来达到和wx.系列一样的效果. navigator的属性 ...
- php intval的取值范围:与操作系统相关
php intval的取值范围:与操作系统相关,32位系统上为-2147483648到2147483647,64位系统上为-9223372036854775808到922337203685477580 ...
- linux Service start
1. crontab的方式 2. 服务的方式.该服务能够持续监测minerd是否在运行,如果没有在运行就会运行minerd:服务也可以做成开机自启动.该服务执行的内容如下,该服务是判断目标服务器的pa ...
- 深入理解朴素贝叶斯(Naive Bayes)
https://blog.csdn.net/li8zi8fa/article/details/76176597 朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法.朴素贝叶斯原理简 ...
- html中一些文字标签
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>菜鸟 ...
- Word文档中的格式标记大全
在Word中有很多的格式设置,很多格式设置都会有一些标记,这些标记是隐藏的,在打印文档时是不会打印出来的,但是它们却起着结构化文档的大作用.如果你在编辑文档,不妨点击格式标记开关,看看都有哪些格式标记 ...
- redis连接池的标准用法:
from .conf import HOST, PORT, POOL_NAME import redis redis_pool = redis.ConnectionPool(host=HOST, po ...
- sql server 基本问题解决思路
1.数据库故障排查步骤,如何处理紧急数据库问题;首先根据报错信息找到故障原因.然后实施对应的解决方案.2.SQL调优步骤,如何来判断SQL语句存在问题,怎么定位问题,如何解决这些问题:可以建立一个Pe ...
- python 面向对象 新式类和经典类
# 经典类写法 # schoolMember.__init__(self, name, age, sex) # 新式类写法 super(Teather, self).__init__(name, ag ...