LearnOpenGL学习笔记—入门08:Camera

本文是LearnOpenGL学习笔记,聚焦于在OpenGL中配置摄像机。先回顾此前教程,接着介绍摄像机/观察空间,实现基础摄像机类。重点阐述欧拉角摄像机,利用俯仰角和偏航角转换方向向量,通过鼠标和键盘控制视角与移动,还实现缩放功能。最后提及四元数摄像机,留待后续讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0 前言

本节笔记对应的内容 摄像机
入门01中我们配置好了环境中我们配置好了环境
入门02中我们可以检测输入并出现一个有颜色的窗口
入门03中我们初步学习了图形渲染管线,尝试用不同方法画出了三角形和四边形
入门04(上)中我们学习了shader和GLSL的相关知识,并给图形加上了变换的颜色以及彩色。
入门04(下)中我们建立自己的shader类,并能够从外部读取shader的内容。
入门05中我们了解了有关材质的内容
入门06中,我们尝试用旋转矩阵以及四元数的方法组成变换矩阵,让它动起来,并对四元数的理解进行了一定阐述。
入门07中我们学习了坐标系统,了解了从3D空间得到2D平面的过程

前面的教程中我们讨论了观察矩阵以及如何使用观察矩阵移动场景(我们向后移动了一点)。OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。

本节我们将会讨论如何在OpenGL中配置一个摄像机,并且将会讨论FPS风格的摄像机,让你能够在3D场景中自由移动。我们也会讨论键盘和鼠标输入,最终完成一个自定义的摄像机类。

1 摄像机/观察空间概述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述

2 基础摄像机类实现

新建C++类Camera,我们先在Camera.h中把最基础的参数定义了

#pragma once

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class Camera
{
public:
	~Camera();
	//position 相机所在位置  target 相机指向的位置  worldup 世界向上的向量
	Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup);
	
	//摄影机位置
	glm::vec3 Position;
	//Forward 摄影机的“方向”(一个和朝向相反的向量)
	glm::vec3 Forward;
	glm::vec3 Right;
	//摄影机的上方向
	glm::vec3 Up;
	//世界的上方向
	glm::vec3 WorldUp;
};

在cpp写出对应的构造方法

Camera::Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup)
{
	Position = position;
	WorldUp = worldup;
	//normalize 归一化变成单位向量
	//Forward 摄影机的“方向”(一个和朝向相反的向量)
	Forward = glm::normalize(position - target);
	//Right 它代表Camera的右方,用世界的上方向与摄影机朝向叉乘
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	//UP  摄像机的上方向
	Up = glm::cross(Forward, Right);
}

之前的摄影机矩阵也叫作lookat矩阵,GLM已经提供了这些支持。
我们要做的只是定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量)。
接着GLM就会创建一个LookAt矩阵,可以把它当作我们的观察矩阵。
定义一个叫做glm::mat4 GetViewMatrix();的方法

glm::mat4 Camera::GetViewMatrix()
{
	//glm::LookAt函数需要一个摄像机位置、一个目标位置和表示世界空间中的上向量的向量。
	//它会创建一个观察矩阵。
	return glm::lookAt(Position, Position - Forward, WorldUp);
}

我们现在可以尝试一下,在main中#include "Camera.h",然后把view改成相机

//实例化相机
Camera camera(glm::vec3(0, 0, 3.0f), glm::vec3(0, 0, 0), glm::vec3(0, 1.0f, 0));

//model
//glm::mat4 modelMat;
//modelMat = glm::rotate(modelMat, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
	
//view
glm::mat4 viewMat;
// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
//viewMat = glm::translate(viewMat, glm::vec3(0.0f, 0.0f, -3.0f));
viewMat = camera.GetViewMatrix();

//projection
glm::mat4 projMat;
projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

得到和以前一样的结果就可以啦
在这里插入图片描述

3 欧拉角摄像机

为了能够改变视角,我们需要改变方向向量,下面先介绍欧拉角的方式。
欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值。
一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:
在这里插入图片描述

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

对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。
给定一个俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。
把斜边边长定义为1,俯仰角P和偏航角Y推导得到Forward向量
在这里插入图片描述
这样我们就有了一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。
在相机头文件中加上

//pitch俯仰角和yaw偏航角
Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup);
float Pitch;
float Yaw;

写出构造方法

Camera::Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup)
{
	Position = position;
	WorldUp = worldup;
	Pitch = pitch;
	Yaw = yaw;
	Forward.x = cos(glm::radians(Pitch)) * sin(glm::radians(Yaw));
	Forward.y = sin(glm::radians(Pitch));
	Forward.z = cos(glm::radians(Pitch)) * cos(glm::radians(Yaw));
	Forward = glm::normalize(Forward);
	//Right 它代表摄像机空间的x轴的正方向
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	//UP 一个指向摄像机的正y轴向量
	Up = glm::cross(Forward, Right);
}

在main函数中可以改角度试试,pitch yaw都是15度,即看向左下方

//实例化相机  position 相机所在位置  pitch yaw  worldup 世界向上的向量
	Camera camera(glm::vec3(0, 0, 3.0f),15.0f,15.0f, glm::vec3(0, 1.0f, 0));

在这里插入图片描述
接下来我们用鼠标控制视角以及前后左右上下移动,先进行键盘控制前后左右上下,注释掉之前的相机,这时候相机会有个默认位置

//建立camera
glm::vec3 cameraPos = 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);

Camera camera(cameraPos, cameraFront, cameraUp);

在相机里写一下前后左右上下的方法与移动速度

float cameraPosSpeed;
void PosUpdateForward();
void PosUpdateBackward();
void PosUpdateLeft();
void PosUpdateRight();
void PosUpdateUp();
void PosUpdateDown();

写出对应构造方法
当我们按下WASDQE键的任意一个,摄像机的位置都会相应更新。如果我们希望向前/后/上/下移动,我们就把位置向量加上或减去方向向量。
如果我们希望向左右移动,我们使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动就可以了。
这样就创建了使用摄像机时熟悉的横移(Strafe)效果。

//-Forward是当前相机的朝向
//朝前
void Camera::PosUpdateForward()
{
	Position += cameraPosSpeed * -Forward;
}
//朝后
void Camera::PosUpdateBackward()
{
	Position -= cameraPosSpeed * -Forward;
}
//朝上
void Camera::PosUpdateUp()
{
	Position += cameraPosSpeed * WorldUp;
}
//朝下
void Camera::PosUpdateDown()
{
	Position -= cameraPosSpeed * WorldUp;
//左边
void Camera::PosUpdateLeft()
{
	Position -= glm::normalize(glm::cross(-Forward, Up)) * cameraPosSpeed;
}
//右边
void Camera::PosUpdateRight()
{
	Position += glm::normalize(glm::cross(-Forward, Up)) * cameraPosSpeed;
}

在main.cpp的main函数中写一下鼠标隐藏

//关闭鼠标显示
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

接下里写一下键盘控制,我们想保证移动的匀速,但是因为根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。
结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。我们必须确保它在所有硬件上移动速度都一样。
图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。
我们把摄像机速度都去乘以deltaTime值。
结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的移动速度需要变得更高来平衡渲染所花去的时间。
使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。
在建立camera之后加上

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

在渲染循环的末尾写上

//计算时间差保证不同硬件都是原速
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

接下来在main的键盘控制函数里用上时间差

void processInput(GLFWwindow* window) {
	//看是不是按下esc键 然后退出
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}
	//更流畅点的摄像机系统
	if (deltaTime != 0) {
		camera.cameraPosSpeed = 5 * deltaTime;
	}
	//camera前后左右根据镜头方向移动
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		camera.PosUpdateForward();
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		camera.PosUpdateBackward();
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		camera.PosUpdateLeft();
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		camera.PosUpdateRight();
	if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
		camera.PosUpdateUp();
	if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
		camera.PosUpdateDown();
}

这时候键盘控制还不能动,因为我们没有更新里面这些向量,接下来我们可以用鼠标移动摄像头,我们打算把更新函数放在鼠标的监听函数里。
我们先定义一下更新函数作为铺垫

private:
	void UpdateCameraVectors();

它的构造方法

void Camera::UpdateCameraVectors()
{
	Forward.x = cos(glm::radians(Pitch)) * sin(glm::radians(Yaw));
	Forward.y = sin(glm::radians(Pitch));					
	Forward.z = cos(glm::radians(Pitch)) * cos(glm::radians(Yaw));
	Forward = glm::normalize(Forward);
	//Right 它代表摄像机空间的x轴的正方向
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	//UP 一个指向摄像机的正y轴向量
	Up = glm::cross(Forward, Right);
}

偏航角和俯仰角是通过鼠标(或手柄)移动获得的,水平的移动影响偏航角,竖直的移动影响俯仰角。
它的原理就是,储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。
如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离。

我们在关闭鼠标显示的后面写上监听功能

//回调函数监听鼠标
glfwSetCursorPosCallback(window, mouse_callback);

接着写鼠标的监听方法

  • 在处理FPS风格摄像机的鼠标输入的时候,我们必须在最终获取方向向量之前做下面这几步:
    1.计算鼠标距上一帧的偏移量。
    2.把偏移量添加到摄像机的俯仰角和偏航角中。
    3.对偏航角和俯仰角进行最大和最小值的限制。
    4.计算方向向量。
float lastX;
float lastY;
bool firstMouse = true;
//鼠标控制镜头方向
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
	if (firstMouse == true) 
	{
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}
	float deltaX, deltaY;
	float sensitivity = 0.05f;
	deltaX = (xpos - lastX)*sensitivity;
	deltaY = (ypos - lastY)*sensitivity;
	
	lastX=xpos;
	lastY = ypos;

	camera.ProcessMouseMovement(deltaX, deltaY);
	
};

接着在camera类里写上处理鼠标移动的方法,这里的加减号与移动方向的对应关系可以思考一下

void Camera::ProcessMouseMovement(float deltaX, float deltaY)
{
	Pitch += deltaY;
	Yaw -= deltaX;
	UpdateCameraVectors();
	if (Pitch > 89.0f)
		Pitch = 89.0f;
	if (Pitch < -89.0f)
		Pitch = -89.0f;
}

注意这时候要把viewMat = camera.GetViewMatrix();放入渲染循环中。
然后我们做一下缩放功能
在之前的教程中我们说视野(Field of View)或fov定义了我们可以看到场景中多大的范围。
当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。
我们会使用鼠标的滚轮来放大。
与鼠标移动、键盘输入一样,我们需要一个鼠标滚轮的回调函数:

float fov = 45.0f;
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
  if(fov >= 1.0f && fov <= 45.0f)
    fov -= yoffset;
  if(fov <= 1.0f)
    fov = 1.0f;
  if(fov >= 45.0f)
    fov = 45.0f;
}
  • 当滚动鼠标滚轮的时候,yoffset值代表我们竖直滚动的大小。
  • 当scroll_callback函数被调用后,我们改变全局变量fov变量的内容。
  • 因为45.0f是默认的视野值,我们将会把缩放级别(Zoom Level)限制在1.0f到45.0f。

透视投影矩阵上用fov变量作为它的视野,记得放到渲染循环里

//projection
glm::mat4 projMat;
projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);

在main函数中记得放入监听滚轮

//回调函数监听滚轮
glfwSetScrollCallback(window, scroll_callback);

这时候应该就能得到可以用键鼠移动的摄像头啦

3.1 完整代码

这次改动比较多 首先是main.cpp

#include <iostream>

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "Shader.h"
#include "Camera.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


//float vertices[] = {
//	//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
//		 0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
//		 0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
//		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
//		-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
//};
//0 1 2   2 3 0
//unsigned int indices[] = {
//	0,1,2,
//	2,3,0
//};

float vertices[] = {
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

glm::vec3 cubePositions[] = {
  glm::vec3(0.0f,  0.0f,  0.0f),
  glm::vec3(2.0f,  5.0f, -15.0f),
  glm::vec3(-1.5f, -2.2f, -2.5f),
  glm::vec3(-3.8f, -2.0f, -12.3f),
  glm::vec3(2.4f, -0.4f, -3.5f),
  glm::vec3(-1.7f,  3.0f, -7.5f),
  glm::vec3(1.3f, -2.0f, -2.5f),
  glm::vec3(1.5f,  2.0f, -2.5f),
  glm::vec3(1.5f,  0.2f, -1.5f),
  glm::vec3(-1.3f,  1.0f, -1.5f)
};

//建立camera
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

Camera camera(cameraPos, cameraTarget, cameraUp);

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

void processInput(GLFWwindow* window) {
	//看是不是按下esc键 然后退出
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}
	//更流畅点的摄像机系统
	if (deltaTime != 0) {
		camera.cameraPosSpeed = 5 * deltaTime;
	}
	//camera前后左右根据镜头方向移动
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		camera.PosUpdateForward();
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		camera.PosUpdateBackward();
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		camera.PosUpdateLeft();
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		camera.PosUpdateRight();
	if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
		camera.PosUpdateUp();
	if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
		camera.PosUpdateDown();
}
float lastX;
float lastY;
bool firstMouse = true;

//鼠标控制镜头方向
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
	if (firstMouse == true)
	{
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}
	float deltaX, deltaY;
	float sensitivity = 0.05f;
	deltaX = (xpos - lastX)*sensitivity;
	deltaY = (ypos - lastY)*sensitivity;

	lastX = xpos;
	lastY = ypos;

	camera.ProcessMouseMovement(deltaX, deltaY);

};
//缩放
float fov = 45.0f;

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
	if (fov >= 1.0f && fov <= 45.0f)
		fov -= yoffset;
	if (fov <= 1.0f)
		fov = 1.0f;
	if (fov >= 45.0f)
		fov = 45.0f;
}


int main() {
	

	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	
	//Open GLFW Window
	GLFWwindow* window = glfwCreateWindow(800,600,"My OpenGL Game",NULL,NULL);
	if(window == NULL)
	{
		printf("Open window failed.");
		glfwTerminate();
		return - 1;
	}
	glfwMakeContextCurrent(window);
	//关闭鼠标显示
	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
	//回调函数监听鼠标
	glfwSetCursorPosCallback(window, mouse_callback);
	//回调函数监听滚轮
	glfwSetScrollCallback(window, scroll_callback);

	//Init GLEW
	glewExperimental = true;
	if (glewInit() != GLEW_OK) 
	{
		printf("Init GLEW failed.");
		glfwTerminate();
		return -1;
	}
	
	glViewport(0, 0, 800, 600);
	glEnable(GL_DEPTH_TEST);
	//glEnable(GL_CULL_FACE);
	//glCullFace(GL_BACK);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");

	unsigned int VAO;
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER,VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//unsigned int EBO;
	//glGenBuffers(1, &EBO);
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	// 位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	// 颜色属性
	//glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	//glEnableVertexAttribArray(1);
	// uv属性
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(2);

	stbi_set_flip_vertically_on_load(true);
	unsigned int TexBufferA;
	glGenTextures(1, &TexBufferA);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TexBufferA);

	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	// 加载并生成纹理
	int width, height, nrChannel;
	unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannel, 0);
	if (data) {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		printf("Failed to load texture"); 
	}
	stbi_image_free(data);


	unsigned int TexBufferB;
	glGenTextures(1, &TexBufferB);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, TexBufferB);


	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	// 加载并生成纹理
	unsigned char *data2 = stbi_load("awesomeface.png", &width, &height, &nrChannel, 0);
	if (data2) {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		printf("Failed to load texture");
	}
	stbi_image_free(data2);

	////计算变换矩阵
	glm::mat4 trans;
 //   trans = glm::translate(trans, glm::vec3(1.0f, 0.0f, 0.0f));
	////trans = glm::rotate(trans, glm::radians(45.0f), glm::vec3(0.0, 0.0, 1.0));
	////glm::quat MyQuaternion = angleAxis(glm::radians(45.0f), glm::vec3(0.0, 0.0, 1.0));
	//glm::quat MyQuaternion = glm::quat(cos(glm::radians(22.5f)),0,0,sin(glm::radians(22.5f)));
	//glm::mat4 RotationMatrix = glm::mat4_cast(MyQuaternion);
	//trans *= RotationMatrix;
	//trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
	
	//实例化相机  position 相机所在位置  target 相机指向的位置  worldup 世界向上的向量
	//Camera camera(glm::vec3(0, 0, 3.0f), glm::vec3(0, 0, 0), glm::vec3(0, 1.0f, 0));
	//实例化相机  position 相机所在位置  pitch yaw  worldup 世界向上的向量
	//Camera camera(glm::vec3(0, 0, 3.0f),15.0f,15.0f, glm::vec3(0, 1.0f, 0));
	
	//model
	//glm::mat4 modelMat;
	//modelMat = glm::rotate(modelMat, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
	
	//view
	glm::mat4 viewMat;
	// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
	//viewMat = glm::translate(viewMat, glm::vec3(0.0f, 0.0f, -3.0f));
	//viewMat = camera.GetViewMatrix();

	

	while (!glfwWindowShouldClose(window)) 
	{
		processInput(window);

		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, TexBufferA);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, TexBufferB);

		glBindVertexArray(VAO);
		//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

		viewMat = camera.GetViewMatrix();
		//projection
		glm::mat4 projMat;
		projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);

		for (unsigned int i = 0; i < 10; i++)
		{
			glm::mat4 modelMat;
			modelMat = glm::translate(modelMat, cubePositions[i]);
			float angle = 20.0f * i;
			modelMat = glm::rotate(modelMat, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));


			myShader->use();
			glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 0);
			glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 1);

			//glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans));
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));

			//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
			glDrawArrays(GL_TRIANGLES, 0, 36);
		}


		

		glfwSwapBuffers(window);
		glfwPollEvents();


		//计算时间差保证不同硬件都是原速
		float currentFrame = glfwGetTime();
		deltaTime = currentFrame - lastFrame;
		lastFrame = currentFrame;
	}
	glfwTerminate();
	return 0;
	
}

然后是Camera.h文件

#pragma once

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class Camera
{
public:
	~Camera();
	//position 相机所在位置  target 相机指向的位置  worldup 世界向上的向量
	Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup);
	//pitch俯仰角和yaw偏航角
	Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup);
	
	//摄影机位置
	glm::vec3 Position;
	//Forward 摄影机的“方向”(一个和朝向相反的向量)
	glm::vec3 Forward;
	glm::vec3 Right;
	//摄影机的上方向
	glm::vec3 Up;
	//世界的上方向
	glm::vec3 WorldUp;
	float Pitch;
	float Yaw;
	float cameraPosSpeed;

	glm::mat4 GetViewMatrix();
	void ProcessMouseMovement(float deltaX, float deltaY);
	void PosUpdateForward();
	void PosUpdateBackward();
	void PosUpdateLeft();
	void PosUpdateRight();
	void PosUpdateUp();
	void PosUpdateDown();
private:
	void UpdateCameraVectors();
};

然后是Camera.cpp

#include "Camera.h"

Camera::Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup)
{
	Position = position;
	WorldUp = worldup;
	//normalize 归一化变成单位向量
	//Forward 摄影机的“方向”(一个和朝向相反的向量)
	Forward = glm::normalize(position - target);
	//Right 它代表Camera的右方,用世界的上方向与摄影机朝向叉乘
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	//UP  摄像机的上方向
	Up = glm::cross(Forward, Right);
}

glm::mat4 Camera::GetViewMatrix()
{
	//glm::LookAt函数需要一个摄像机位置、一个目标位置和表示世界空间中的上向量的向量。
	//它会创建一个观察矩阵。
	return glm::lookAt(Position, Position - Forward, WorldUp);
}

Camera::Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup)
{
	Position = position;
	WorldUp = worldup;
	Pitch = pitch;
	Yaw = yaw;
	Forward.x = cos(glm::radians(Pitch)) * sin(glm::radians(Yaw));
	Forward.y = sin(glm::radians(Pitch));
	Forward.z = cos(glm::radians(Pitch)) * cos(glm::radians(Yaw));
	Forward = glm::normalize(Forward);
	//Right 它代表摄像机空间的x轴的正方向
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	//UP 一个指向摄像机的正y轴向量
	Up = glm::cross(Forward, Right);
}

void Camera::ProcessMouseMovement(float deltaX, float deltaY)
{
	Pitch += deltaY;
	Yaw -= deltaX;
	UpdateCameraVectors();
	if (Pitch > 89.0f)
		Pitch = 89.0f;
	if (Pitch < -89.0f)
		Pitch = -89.0f;
}

//-Forward是当前相机的朝向
//朝前
void Camera::PosUpdateForward()
{
	Position += cameraPosSpeed * -Forward;
}
//朝后
void Camera::PosUpdateBackward()
{
	Position -= cameraPosSpeed * -Forward;
}
//朝上
void Camera::PosUpdateUp()
{
	Position += cameraPosSpeed * WorldUp;
}
//朝下
void Camera::PosUpdateDown()
{
	Position -= cameraPosSpeed * WorldUp;
//左边
void Camera::PosUpdateLeft()
{
	Position -= glm::normalize(glm::cross(-Forward, Up)) * cameraPosSpeed;
}
//右边
void Camera::PosUpdateRight()
{
	Position += glm::normalize(glm::cross(-Forward, Up)) * cameraPosSpeed;
}


void Camera::UpdateCameraVectors()
{
	Forward.x = cos(glm::radians(Pitch)) * sin(glm::radians(Yaw));
	Forward.y = sin(glm::radians(Pitch));
	Forward.z = cos(glm::radians(Pitch)) * cos(glm::radians(Yaw));
	Forward = glm::normalize(Forward);
	//Right 它代表摄像机空间的x轴的正方向
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	//UP 一个指向摄像机的正y轴向量
	Up = glm::cross(Forward, Right);
}


Camera::~Camera()
{
}

4 四元数摄像机(?)

这个摄像机系统是一个FPS风格的摄像机,它能够满足大多数情况需要,而且与欧拉角兼容,但是在创建不同的摄像机系统,比如飞行模拟摄像机时就要当心万向节锁。
每个摄像机系统都有自己的优点和不足,比如,这个FPS摄像机不允许俯仰角大于90度,而且我们使用了一个固定的上向量(0, 1, 0),这在需要考虑滚转角的时候就不能用了。

这个FPS风格的摄像机好像不太好用四元数,或者也没必要
等找到合适的例子的时候再回来写吧
最好的摄像机系统是使用四元数(Quaternions)的,但我们将会把这个留到后面用到再讨论

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值