OpenGL(8)之摄像机

1,830 阅读9分钟

title: OpenGL(8)之摄像机

date: 2020-07-12 16:37

category: 图形学

tags: opengl

OpenGL(8)之摄像机

0.前言

OpenGL本身没有摄像机概念,但是可以通过场景中的所有物体往相反方向移动的方式来模拟摄像机,产生一种我们在移动的感觉,而不是场景在移动。

1.摄像机/观察空间

摄像机/观察空间(Camera/View Space)的概念是观察的以摄像机的视角作为场景原点时,场景中所有的顶点坐标,视图矩阵把所有的世界坐标变换为相对于摄像机位置与方向的视图空间坐标。

定义一个摄像机,需要它在世界空间中的位置,观察方向(摄像机的方向),一个指向其右侧的向量和一个指向其上方的向量。

从上述概念中可得知:摄像机的位置以及摄像机的方向,

  1. 摄像机的位置

在世界空间中定义一个指向摄像机位置的向量即可,通过GLM的内置函数定义一个位置向量:

glm::vec3 cameraPos = glm::vec3(0.0f,0.0f,3.0f);
  1. 摄像机方向(z轴)

在OpenGL中使用的是右手坐标系,z轴的正方向是从屏幕指向你的,如果希望摄像机向后移动, 就需要将场景沿着z轴正方向移动;

假设物体的正面朝向z轴正方向(该物体的z方向分量为大于0),那么想用摄像机观察这个物体的正面,需要将摄像机放在物体所在位置沿着z轴正方向平移一段距离(其实这里就是相当于摄像机和物体同时处于z轴正方向,即摄像机前移了),并让摄像机的朝向指向物体的正面。

由于使用的是右手坐标系,所以摄像机的z轴正方向应该是在摄像机的后面,下图摄像机蓝色轴

那么摄像机指向目标的方向向量根据向量减法来求得,需要通过目标的位置向量减去摄像机的位置向量,这个求出来的指向目标的方向向量的方向与摄像机z轴的方向相反,我们需要做出相应的方向向量调整以便构建摄像机的观察坐标系,因此,需要用摄像机的位置向量减去目标的位置向量。

glm::vec3 cameraTarget = glm::vec3(0.0f,0.0f,0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos-cameraTarget);
  1. 右轴的概念(x轴)

右向量代表的是摄像机空间的x轴的正方向。为获取右向量,可通过矩阵的叉乘运算来求的,定义一个上向量,把上向量与第2步求的方向向量(z轴)进行叉乘,结果会同时垂直于两个向量,因此会得到指向x轴正方向的那个向量,(如果交换两个向量叉乘的顺序即z*y会得到相反的指向x轴的向量);

glm::vec3 up = glm::vec3(0.0f,1.0f,0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up,cameraDirection));

  1. 上轴的概念(y轴)

有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了, 只需要将右向量和方向向量进行叉乘;

glm::vec3  cmeraUp = glm::cross(cameraDirecton,cameraRight);

2.Look At矩阵

使用该矩阵是为了创建一个观察给定目标的观察矩阵,这样做的好处就是如果是3个相互垂直(非线形)的轴定义了一个坐标空间(就是以摄像机为中心点创建的一个坐标空间),可使用3个轴外加一个平移向量来创建该坐标空间,进行场景中物体的观测,并且还可以利用该矩阵乘以任意向量将该物体变换到这个空间中。

通过3个互相垂直的轴和一个定义的摄像机空间的位置坐标,就可以创建Look At矩阵了。

LookAt = \left[{
	\begin{matrix}
	Rx & Ry & Rz & 0\\
	Ux & Uy & Uz & 0\\
	Dx & Dy & Dz & 0\\
	0  & 0  & 0  & 1\\
\end{matrix}
}
\right] * \left[{
	\begin{matrix}
	1 & 0 & 0 & -Px\\
	0 & 1 & 0 & -Py\\
	0 & 0 & 1 & -Pz\\
	0 & 0 & 0 & 1\\
\end{matrix}
}\right]

其中R是右向量(x轴),U是上向量(y轴),D是方向向量(指向观察目标的向量) P是摄像机的位置向量。 这里对摄像机的位置向量进行了取反操作,其原因就是希望将世界空间的坐标平移到摄像机中来,自身移动的相反方向就是摄像机的方向。

通过GLM创建一个LookAt矩阵,将其当成观察矩阵: 定义一个摄像机位置,一个目标位置以及一个世界空间的上向量; 即可求得右向量(摄像机位置与目标位置之差得出方向向量,方向向量与世界空间的上向量叉乘得出右向量,这里就可以获得摄像机整体所需的四个向量。)

glm::mat4 view;

view = glm::lookAt(glm::vec3(0.0f,0.0f,3.0f),//位置向量
	glm::vec3(0.0f,0.0f,0.0),//目标向量
	glm::vec3(0.0f,1.0f,0.0f)//世界空间中的上向量
	)

glm::LookAt函数需要一个位置、目标和上向量,创建一个观察矩阵,如果我们需要把摄像机在场景中旋转,需要将摄像机的注视点保持在(0,0,0).

为了能够使得摄像机在场景中旋转(意味着摄像机绕着场景做圆周运动,这就是下面讨论的为什么每一帧的坐标点是圆上的一点),我们需要不断的去更新摄像机的位置,这样在不断的渲染过程中就可以达到摄像机在旋转的效果;

如何去实现呢?

上述已经给出了答案,需要使用到三角学的知识,为每一帧创建一个x和z坐标,代表圆上的一点,将其作为摄像机的位置,通过重新计算x和y坐标,遍历圆上所有的点,摄像机就会绕着场景进行旋转。

既然操作的是圆,势必需要定义这个圆的半径radius,在每次渲染迭代中使用 GLFW内置函数glfwGetTime重新创建观察矩阵,来扩大这个圆。

float radius = 10.f;
float cameraX = sin(glfwGetTime())*radius;
float cameraZ = cos(glfwGetTime())*radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(cameraX,0.0,cameraZ),
	glm::vec3(0.0,0,0,0,0),glm::vec3(0.0,1.0,0.0));

通过上述一段小代码,摄像机会随着时间流逝围绕着场景转动。

3.自由移动

让摄像机绕着场景转动,上述已经实现了,但是也是一种取巧的方式,如果摄像机移动可以为我们所控,那又该如何去做?

首先需要去设置一个摄像机系统,所以在程序前定义一些摄像机变量:

//摄像机的位置向量
glm::vec3 cameraPosition = glm:vec3(0.0f,0.0f,3.0f);
//方向向量
glm::vec3 cameraFront = glm::vec3(0.0f,0.0f,-1.0f);
//上向量
glm::vec3 cameraUp = glm::vec3(0.0f,1.0f,0.0f);

LookAt函数

view = glm::lookAt(cameraPosition,cameraPosition+cameraFront,cameraUp);

首先将摄像机位置设置为之前定义的comeraPosition,摄像机方向的当前位置加上定义的方向向量comeraFront,如此就可以保证移动过程,摄像机始终都注视着目标方向。 那么通过更新comeraPosition向量,即可控制摄像机的自动移动。

3.复习

完成前面几个章节学习,知道如何去创建一个窗口,创建并且编译着色器,通过缓冲对象或者uniform发送顶点数据,绘制物体,使用纹理,理解向量和矩阵,还可以利用这些知识创建一个3D场景并可以通过摄像机来进行移动。

复习一下相关的词汇表:

  • OpenGL: 一个定义了函数布局和输出图形API的正式规范

  • GLAD: 一个拓展加载库,用来加载并设定所有的OpenGL函数指针,这样方便使用所有现代OpenGL函数

  • 视口(Viewport):渲染的窗口

  • 图形管线(Graphics Pipline):一个顶点在呈现为像素之前经过的全部过程

  • 着色器(Shader):一个运行在显卡上的小程序,很多阶段的图形管道都可以使用自定义的着色器来替代原有的功能

  • 标准化设备坐标(Normalized Device Coordinate,NDC):顶点在通过在裁剪坐标系中裁剪与透视除法后最终呈现的坐标系,所有位置在其-1.0到1.0的顶点之内都将可见。

  • 顶点缓冲对象(Vertex Buffer Object):一个调用显存并存储所有顶点数据供显卡使用的缓冲对象

  • 顶点数组对象(Vertex Array Object):存储缓冲区和顶点属性状态

  • 索引缓冲对象(Element Buffer Object):一个存储索引并根据索引使用缓冲对象

  • Uniform :一个特殊类型的GLSL变量,是全局的(在一个着色器程序中每一个着色器都能够访问uniform变量),并且只需要被设定一次。

  • 纹理(Texture):一种包裹着物体的特殊类型图像,呈现物体精细的视觉效果

  • 纹理缠绕(Texture Wrapping):定义一种当纹理顶点超出范围(0,1)时指定OpenGL如何采样纹理的模式。

  • 纹理过滤(Texture Filtering):定义一种当有多种纹素选择时指定OpenGL如何采样纹理的模式,通常在纹理被放大情况下发生。

  • 多级渐远纹理(Mipmaps):根据距观察者的距离使用材质的合适大小。

  • stb_image.h:图像加载库

  • 纹理单元(Texture Units):纹理对象绑定到纹理单元,纹理单元的索引传递到shader,shader通过纹理单元获取纹理对象。

  • 向量(Vector):一个定义了在空间中物体的方向和位置。

  • 矩阵(Matrix):一个矩形阵列。

  • GLM:一个为OpenGL打造数学库

  • 局部空间(Local Space):一个物体的初始空间,所有的坐标都是相对于物体的原点的。

  • 世界空间(World Space):所有的坐标都是相对于全局原点。

  • 观察空间(View Space):所有的坐标都是从摄像机的角度来观察。

  • 裁剪空间(Clip Space):在观察空间的基础上,使用了投影,作为顶点坐标的最终的空间,以及顶点着色器的输出。

  • 屏幕空间(Screen Space):所有的坐标都是由屏幕视角来观察,坐标的范围都是从0到屏幕的宽/高。

  • LookAt矩阵:一个特殊类型的观察矩阵,创建了一个坐标系,观察目标的平移、旋转操作。

参考

LearnOpenGL

OpenGL学习笔记:摄像机