首页 > 其他分享 >OpenGL 摄像机视角详解

OpenGL 摄像机视角详解

时间:2023-11-08 15:14:25浏览次数:33  
标签:OpenGL 坐标系 摄像机 详解 position 观察 我们 向量

1. 摄像机

摄像机就好像是我们的眼睛,我们从摄像机的方向观察世界空间中的模型。摄像机远离模型,模型自然就变小了(透视投影下),然而,在GL中事实上并没有摄像机的概念。但是我们可以通过移动世界空间远离我们的摄像机来模拟摄像机远离世界的感觉。这也正是在上一章中,我们的观察矩阵是(0,0,-3)的原因。我们通过将世界矩阵向屏幕里移动3个单位模拟摄像机向屏幕外移动三个单位。

上一章中,我们只是很简单的设置了一个观察矩阵,这一章,我们来仔细说一下如何设置这个观察矩阵,也就是,如何确定摄像机所在的坐标系。

2. 视察矩阵

事实上我们所要做的事就是确定一个观察矩阵,就可以完成从世界空间到观察空间的转换。而这在三维空间内,实际就是确定观察空间坐标系相对于世界空间坐标系的位置及各轴的夹角即可。如果你空间感足够好的话,或者相对位置足够简单的话,你可能很快的就可以写出观察坐标系的表达式。但情况比较复杂的时候你可能需要耗费大量时间去完成这个转换,下面,我将尝试用一个通俗易懂的方式来介绍一下如何去生成这个坐标。

 

 3. 摄像机的位置

这个很好理解,比如你规定了地面上一个物体作为世界的原点,那么随着你眼睛相对远点的位置改变时,你所看到的物体的样子也会随之改变。所以眼睛相对于远点的位置会直接影响物体的样子,同理,我们也需要确定摄像机相对于世界空间坐标原点的位置。这里你可以看下上面图中的第一个图片来理解。坐标系就是世界空间,我们要确定的就是摄像机在世界空间中的位置。

4. 摄像机的方向

同样,即是你站在一个位置不动,你将目光集中在物体上不同的点时,你所看到的物体也不同。或者说,你目光的方向改变时,物体也跟着改变。同理,我们还需要确定摄像机观察的方向。第二张图中就显示了摄像机在当前位置看向世界空间远点的示例。

5. 摄像机的滚转角

好了,现在你站在一个位置不动,目光也一直盯着物体的中心点不动,你还可以让你看到的物体改变。除了闭眼睛,你还可以歪一下头,你看到的东西是不是斜过来了(你非说没变那是因为强大的大脑已经帮你转换回来了又,你可以把眼睛换成手机摄像头然后倾斜手机再看看,手动滑稽)。所以,我们要确定摄像机在世界空间中摆放的夹角。这么表述可能不清楚,稍微借一点坐标系的概念。我们摄像机的方向就是观察坐标系的Z轴。但是一个Z轴确定却并不能确定一个坐标系,我们至少要确定两个坐标轴,才能通过两个坐标轴确定第三个坐标轴从而建立一个坐标系。第四张图就显示了确定三个轴夹角后的坐标系。

6. 视察坐标系

 

经过上面的论述,我们知道了,我们只要知道摄像机的位置摄像机的方向x或y轴中任意一个轴的方向即可确定。

 

如果我们不改变滚转角保证了摄像机坐标系的x轴与世界坐标系的y轴总是空间垂直,我们就可以通过摄像机方向向量与世界坐标系的y轴的方向向量叉乘从而获得摄像机坐标系的x轴(两向量叉乘将获得同时垂直于两个向量的第三个向量)。

Look At

我们已经知道如何去构建一个摄像机坐标系了,不过怎么通过这些元素构建出观察矩阵呢?glm为我们提供了LookAt(position,target,up)函数。它含有三个参数:

  • position,第一个参数就是我们摄像机在世界坐标系的位置了
  • target,第二个参数是我们观察的点的位置,就是我们目光汇聚的那个点了,为什么是目标点呢?因为通过position减去target我们就可以获得摄像机方向的向量了
  • up,第三个参数是一个与摄像机坐标系x轴垂直的向量。为什么是这个向量呢?因为我们可以通过position和target确定摄像机的方向,也就是摄像机坐标系z轴。再找到一个也与x轴垂直的向量即可确定x轴的方向向量了。

所以,上一章中我们生成的观察矩阵可以通过lookAt函数这样生成:

上面函数中,描述了我们的摄像机在世界坐标系的(0,0,3)位置,我们观察的点就是世界坐标原点,这个up向量就是世界坐标系的y轴的方向向量。

7. 圆周运动

 

那么现在,我们将我们的target保持在(0,0,0)这个点上,通过改变position来改变我们的lookAt矩阵。我们可以大概猜想一下结果就应该是我们围着一个东西转圈一直盯着这个东西的样子。

 

下面这段代码在渲染循环中:

 8. 水平运动

 

接下来我们来模拟一下我们日常生活中的视角。我们想一下,一定是我们相对世界的position一直是改变的,我们眼睛的焦距是不变的,始终看我们自身位置前的某一个位置。

 

那么lookAt函数变成了这样:

那么现在我们还是只要改变我们的position就好了。

这里我们用键盘来接收我们想要做的移动的输入,代码如下:

上述左右移动时,我们看到我们用front向量与up向量相乘后标准化获取了right向量。这里之所以直接使用世界坐标系中的front向量和up向量,是因为我们当前观察角度的摄像机坐标系与世界坐标是的各轴完全是平行的,只是原点不同而已。

这样,我们就在这个世界中可以前后左右自由移动了。

9. 移动速度

我们看到,我们处理键盘输入是在渲染循环中处理的。试想如果我们的渲染循环循环一次的事件长,那么我们改变position经过的时间间隔就长,反之就短。那么如果一次渲染循环的时间是在一个范围内浮动的,那么物体运动的速度看起来也就是一个浮动的过程,我们应该监测每次渲染循环(更准确的应该是每次键盘事件处理)的时间间隔,通过这个时间间隔决定我们这一次position改变的数量

这里我们只要将我们之前定义的摄像机的速度在乘上一个时间间隔系数即可:

 10. 视角移动

目前为止,我们实现了在世界中水平自由移动了,但是我们还不能转头也不能抬头。我们只要改变我们观察点的位置即可。这里,为了保证我们的焦距是不变的,所以我们要将front向量标准化。

现在我们的摄像机坐标系与世界坐标系的各轴是平行的。我们想抬头呢,我们就以x轴旋转坐标系,想左右牛头就以y轴旋转坐标系即可。

11. 欧拉角

 

我们先来看一下坐标系旋转角的概念和图示。

 

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll)。

 

俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。

我们来看一下如何计算这个向量。

 12. 鼠标输入

首先,我们要告诉GL,如果捕捉光标的话,我们不应该展示光标(当然你也可以展示)。

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

然后我们应该设置鼠标事件的回调:

glfwSetCursorPosCallback(window, mouse_callback);

接下来就是回调函数了:

13. 缩放

 目前为止,我们可以自由在世界中移动,改变视角了已经。但是我们还想给摄像机添加一个缩放功能,你可能说我走近点东西不就大了么。的确是这样的,不过我们也可以在原地做到这件事。我们可以利用透视的特性完成这件事。在同样的距离观察一个物体,视野越小时,我们能观察到的物体的部分越小,但是我们的屏幕是不变的,也就是屏幕上显示的物体的部分越小,这样就好像物体被放大了一样。所以我们只要改动这个fov值就可以完成缩放。

标签:OpenGL,坐标系,摄像机,详解,position,观察,我们,向量
From: https://www.cnblogs.com/rmb999/p/17817397.html

相关文章

  • 数据类型详解
     注意:前边定义了后边输出就不用加引号,如果前边没有定义就后边加引号,如果加的是单引号就只能输入一个字符,如果是双引号就可以输入好多 ......
  • 30张图详解IP地址网络知识
    你们好,我的网工朋友。IP地址是所有网络初级课程里最先涉及到的技术点,对于IP地址的合理规划是网络设计的重要环节,必须拿捏。IP地址规划的好坏,影响到网络路由协议算法的效率,影响到网络的性能,影响到网络的扩展,影响到网络的管理,也必将直接影响到网络应用的进一步发展。今天和你分享一篇......
  • 详解 Calico 三种模式(与 Fannel 网络对比学习)
    1.概述Calico是一个基于BGP的纯三层网络方案。它在每个计算节点都利用Linuxkernel实现了一个高效的虚拟路由器vRouter来进行数据转发。每个vRouter都通过BGP协议将本节点上运行容器的路由信息向整个Calico网络广播,并自动设置到达其他节点的路由转发规则。Calico保......
  • free -m 详解
    来源:https://blog.51cto.com/5250070/16609551.用途说明free命令用来显示内存使用状况。displayinformationaboutfreeandusedmemoryonthesystem。free命令相对于top提供了更简洁的查看系统内存使用状况:[root@localhost~]#free-mtotal......
  • IApplicationBuilder详解
    在上节中我们已经得知WebApplication实现了IApplicationBuilder,我们浅谈了其pipe特质和构建方法,本节中将深入了解ApplicationBuilder以窥探IApplicationBuilder真相publicinterfaceIApplicationBuilder{IServiceProviderApplicationServices{get;set;}I......
  • RequestContextHolder详解(获取request对象的四种方法)
    方法1、Controller中加参数来获取request注意:只能在Controller中加入request参数。一般,我们在Controller中加参数获取HttpServletRequest,如下所示:@RestController@RequestMapping("/gap")publicclassPlantTraceController{@PostMapping("/plantTrace")publicResult......
  • JS之splice()方法详解
    JS中splice方法可以用来对js的数组进行删除,添加,替换等操作。1.删除功能,第一个参数为第一项位置,第二个参数为要删除几个;使用方法:array.splice(index,num),返回值为删除的内容,结果值为array2.插入功能,第一个参数(插入位置),第二个参数(0),第三个参数(插入的项)。使用方法:array.splice(index,0,......
  • OpenGL 坐标系统详解
    GL中的坐标系是标准设备坐标,即他的每个坐标轴的取值范围都是[-1.0,1.0]。通常,我们输入到顶点着色器中的顶点坐标都会被转换为标准化设备坐标,然后进行光栅化,转变成屏幕坐标。然而事实上,从顶点坐标到屏幕坐标是一个较为复杂的过程。总体来讲为了某些计算更加方便,会经过5个坐标系统的......
  • ADC-过零检测详解
    ADC-过零检测详解1、反电动势波形的起源下图展示了内转子磁极的磁感应强度B的分布情况。定义磁感应强度方向向外为正在0°的时候,处于正反方向交界处,磁感应强度为零;然后开始线性增加,在A点时达到最大然后一直保持恒定值不变,直到B点开始下降,到180°的时候下降到零。然后开始负......
  • 2、Text组件详解
    TextStyle的参数 //代码块importMimport'package:flutter/material.dart';voidmain(){runApp(MaterialApp(home:Scaffold(appBar:AppBar(title:constText("你好Flutter")),body:constMyApp(),),));}//代码块statelessWclassMyAppexten......