当前位置: 首页 > article >正文

OpenGL入门最后一章观察矩阵(照相机)

        前面的一篇文章笔者向大家介绍了模型变化矩阵,投影矩阵。现在只剩下最后一个观察矩阵没有和大家讲了。此片文章就为大家介绍OpenGL入门篇的最后一个内容。

观察矩阵

        前面的篇章当中,我们看到了即使没有观察矩阵,我们也能对绘制出来的模型有一个很好的控制,那么观察矩阵存在的意义又是什么了?那么我们现在来想象一下这样一件事,我们的屏幕上绘制了特别多的方块,现在我们要把它们全部向屏幕的右边移动一定的距离,如果是前面的做法的话所有的模型都要再乘上一个向右平移的矩阵,这样做完全没有问题,只是这会让程序的效率变得很差,而且你的GPU除了计算一下像素颜色之外基本上就没有做其他任何事,把绝大部分的任务都交给了CPU,这样的任务分配是不合理的,所以我们需要上传一个观察矩阵ViewMatrixGPU,让他把所有的顶点按照某一个方向进行平移,这样我们的任务就合理分配了。(什么样的任务应该交给CPU,什么样的任务又交给GPU这里也有不少东西需要研究)

        看过资料的朋友们都知道,在OpenGL当中想用创建一个观察矩阵只需要使用glm::lookAt函数即可

glm::lookAt(m_Position, m_Forward, m_Up);
//glm::vec3 m_Position 照相机的位置
//glm::vec3 m_Forward 观察的位置
//glm::vec3 m_Up 相机屏幕的上方的方向

这样我们就得到了一个观察矩阵了,看起来也没什么好说的对不对。但是困难的往往是处理与之相关的数学问题,前面说我们要让屏幕上的所有的方块全部向右移动一段距离,那么我们应该怎么设置这个观察矩阵了?直接给答案

glm::vec3 m_Position(-0.3f,0.0f,3.0f)
glm::vec3 m_Forward(-0.3f,0.0f,0.0f);
glm::vec3 m_Up(0.0f,1.0f,0.0f);

glm::lookAt(m_Position, m_Forward, m_Up);

可能有的朋友们就要问了,相机为什么要往左边移动,并且看向左边了?要回答这个问题也很简单,大家现在把手机拿出来打开照相机,然后手机屏幕与电脑屏幕保持水平,现在把手机水平向左开始移动,你就会发现手机屏幕中的物体朝着反方向进行移动了,如果再把手机对准刚才位置,你就会发现所有的物体都行了旋转拉绳,这当然不是我们想要的结果,所以我们要让手机继续朝向正前方。观察矩阵和我们的照相机的工作原理一摸一样,所以观察矩阵也可以叫作相机矩阵。

制作一个可以观察物体的矩阵

        观察矩阵其实介绍到这里也就算是结束了,我们现在来做一个可以实际使用的相机。比如说我现在制作了一个立方体,每一个面的颜色都不一样,它刚被绘制出来的时候我只能看见一两个面,但是我想观察其他的面,如果说我去修改模型让它自己转过来,这也有点太蠢了!所以我们要做一个进行鼠标控制的照相机。

        我的打算是,让照相机沿着一个球面进行运动,鼠标坐标的变化来确定照相机在球体坐标上的夹角。

鼠标x方向上的偏移量决定\theta角的大小,鼠标y方向上的偏移量决定\phi角的大小,有了理论的基础过后我们看一下具体如何实现。

照相机类:

Camera.h

#pragma once
#include<glm/glm.hpp>

class Camera {
public:
	Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yam = 0.0f, float pitch =90.0f);

	Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch);

	glm::mat4 GetViewMatrix();
	void ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch = true);
	void ProcessMouseScroll(float yoffset);
	
	inline float GetCameraZoom() const { return m_Zoom; }
private:
	void UpdateCameraVectors();

private:
	glm::vec3 m_Position, m_Front, m_Up, m_Right, m_WorldUp;
	float m_Yaw, m_Pitch;
	float m_MouseSensitivity;
	float m_Zoom;
};

Camera.cpp

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

#include"Camera.h"

Camera::Camera(glm::vec3 position ,glm::vec3 up,float yam,float pitch):m_Front(glm::vec3(0.0f,0.0f,-1.0f)),m_MouseSensitivity(0.1f),m_Zoom(45.0f) {
	m_Position = position;
	m_WorldUp = up;
	m_Yaw = yam;
	m_Pitch = pitch;
	UpdateCameraVectors();
}

Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch):m_Front(glm::vec3(0.0f,0.0f,-1.0f)),m_MouseSensitivity(0.1f),m_Zoom(45.0f) {
	m_Position = glm::vec3(posX, posY, posZ);
	m_WorldUp = glm::vec3(upX, upY, upZ);
	m_Yaw = yaw;
	m_Pitch = pitch;
	UpdateCameraVectors();
}

glm::mat4 Camera::GetViewMatrix() {
	return glm::lookAt(m_Position, glm::vec3(0.0f, 0.0f, 0.0f), m_Up);
}

void Camera::ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch){
	xoffset *= m_MouseSensitivity;
	yoffset *= m_MouseSensitivity;

	m_Yaw += xoffset;
	m_Pitch += yoffset;

	if (constrainPitch) {
		m_Pitch = m_Pitch > 179.0f ? 179.0f : m_Pitch;
		m_Pitch = m_Pitch < 0.1f ? 0.1f : m_Pitch;
	}

	UpdateCameraVectors();
}

void Camera::ProcessMouseScroll(float yoffset) {
	m_Zoom -= yoffset;
	m_Zoom = m_Zoom < 1.0f ? 1.0f : m_Zoom;
	m_Zoom = m_Zoom > 45.0f ? 45.0f : m_Zoom;
}

void Camera::UpdateCameraVectors() {
	glm::vec3 position(0.0f,0.0f,0.0f);
	position.x = 5.0f * sinf(glm::radians(m_Yaw)) * sinf(glm::radians(m_Pitch));
	position.y = 5.0f * cosf(glm::radians(m_Pitch));
	position.z = 5.0f * cosf(glm::radians(m_Yaw)) * sinf(glm::radians(m_Pitch));

	m_Front = glm::normalize(-position);
	m_Position = position;

	m_Right = glm::normalize(glm::cross(m_Front, m_WorldUp));
	m_Up = glm::normalize(glm::cross(m_Right, m_Front));
}

除此以外,我们还需要处理鼠标输入函数

void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {
	static float lastX = 320.0f, lastY = 240.0f;
	static bool firstMouse = true;

	float xpos = static_cast<float>(xposIn);
	float ypos = static_cast<float>(yposIn);

	if (firstMouse) {
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}

	float xoffset = xpos - lastX;
	float yoffset = lastY - ypos;

	lastX = xpos, lastY = ypos;

	camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffsetIn, double yoffsetIn) {
	camera.ProcessMouseScroll(static_cast<float>(yoffsetIn));
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		run = false;
}

好的主函数,贴在下面,我们就得到了一个可以绕着物体圆转的照相了,因为窗口隐藏了光标,想要推出的画按Esc键即可。

#include<glad/glad.h>
#include<GLFW/glfw3.h>

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

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

static Camera camera(glm::vec3(0.0f, 0.0f, 5.0f));
static bool run = true;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);

int main() {
	glfwInit();

	GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);

	
	glfwMakeContextCurrent(window);
	glfwSetCursorPosCallback(window, mouse_callback);
	glfwSetScrollCallback(window, scroll_callback);

	glfwSetKeyCallback(window, key_callback);
	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
	//需要初始化GLAD
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	float vertexes[] = {
		//front surface
		-0.5f,	-0.5f,	0.5f,	1.0f,	1.0f,0.588f,0.2784f,	//0
		0.5f,	-0.5f,	0.5f,	1.0f,	1.0f,0.588f,0.2784f,	//1
		0.5f,	0.5f,	0.5f,	1.0f,	1.0f,0.588f,0.2784f,	//2
		-0.5f,	0.5f,	0.5f,	1.0f,	1.0f,0.588f,0.2784f,	//3

		//back surface
		-0.5f,	-0.5f,	-0.5f,	1.0f,	0.933f,0.9098f,0.6666f,	//4
		0.5f,	-0.5f,	-0.5f,	1.0f,	0.933f,0.9098f,0.6666f,	//5
		0.5f,	0.5f,	-0.5f,	1.0f,	0.933f,0.9098f,0.6666f,	//6
		-0.5f,	0.5f,	-0.5f,	1.0f,	0.933f,0.9098f,0.6666f,	//7

		//up surface
		-0.5f,	0.5f,	0.5f,	1.0f,	0.0f,0.749f,1.0f,		//8
		0.5f,	0.5f,	0.5f,	1.0f,	0.0f,0.749f,1.0f,		//9
		0.5f,	0.5f,	-0.5f,	1.0f,	0.0f,0.749f,1.0f,		//10
		-0.5f,	0.5f,	-0.5f,	1.0f,	0.0f,0.749f,1.0f,		//11

		//down surface
		-0.5f,	-0.5f,	0.5f,	1.0f,	0.498f,1.0f,0.0f,		//12
		0.5f,	-0.5f,	0.5f,	1.0f,	0.498f,1.0f,0.0f,		//13
		0.5f,	-0.5f,	-0.5f,	1.0f,	0.498f,1.0f,0.0f,		//14
		-0.5f,	-0.5f,	-0.5f,	1.0f,	0.498f,1.0f,0.0f,		//15

		//left surface
		-0.5f,	-0.5f,	-0.5f,	1.0f,	0.5f,0.5f,0.145f,	//16
		-0.5f,	-0.5f,	0.5f,	1.0f,	0.5f,0.5f,0.145f,	//17
		-0.5f,	0.5f,	0.5f,	1.0f,	0.5f,0.5f,0.145f,	//18
		-0.5f,	0.5f,	-0.5f,	1.0f,	0.5f,0.5f,0.145f,	//19

		//right surface
		0.5f,	-0.5f,	-0.5f,	1.0f,	1.0f,0.8941f,0.7686f,	//20
		0.5f,	-0.5f,	0.5f,	1.0f,	1.0f,0.8941f,0.7686f,	//21
		0.5f,	0.5f,	0.5f,	1.0f,	1.0f,0.8941f,0.7686f,	//22
		0.5f,	0.5f,	-0.5f,	1.0f,	1.0f,0.8941f,0.7686f	//24
	};					

	unsigned int indexes[] = {
		//front surface
		0,1,2,
		2,3,0,

		//back surface
		4,5,6,
		6,7,4,

		//up surface
		8,9,10,
		10,11,8,

		//down surface
		12,13,14,
		14,15,12,

		//left surface
		16,17,18,
		18,19,16,

		//right surface
		20,21,22,
		22,23,20
	};

	glEnable(GL_DEPTH_TEST);
	unsigned int buffer = 0, vertexArray = 0, indexBuffer = 0;

	glCreateVertexArrays(1, &vertexArray);
	glBindVertexArray(vertexArray);

	glCreateBuffers(1, &buffer);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), NULL);

	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (const void*)(4 * sizeof(float)));

	glCreateBuffers(1, &indexBuffer);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);

	Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");
	
	while (!glfwWindowShouldClose(window) && run) {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
		glm::mat4 project = glm::perspective(glm::radians(camera.GetCameraZoom()), 640.0f / 480.0f, 0.1f, 100.0f);
		glm::mat4 view = camera.GetViewMatrix();
		glm::mat4 ViewProject = project * view;
		pShader->Bind();
		pShader->UploadUniformat4("u_ViewProject", ViewProject);

		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, NULL);

		glfwSwapBuffers(window);

		glfwPollEvents();
	}

	delete pShader;

	glfwDestroyWindow(window);

	glfwTerminate();
}

void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {
	static float lastX = 320.0f, lastY = 240.0f;
	static bool firstMouse = true;

	float xpos = static_cast<float>(xposIn);
	float ypos = static_cast<float>(yposIn);

	if (firstMouse) {
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}

	float xoffset = xpos - lastX;
	float yoffset = lastY - ypos;

	lastX = xpos, lastY = ypos;

	camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffsetIn, double yoffsetIn) {
	camera.ProcessMouseScroll(static_cast<float>(yoffsetIn));
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		run = false;
}

注意

        你可能注意到了,相机是不能绕过Y轴顶点的,但这里要声明的是并不是笔者的代码写错了,这是因为,如果绕道Y轴顶点,那么你看向的方向和世界向上的方向就平行了,这两个向量叉乘的结果是一个0向量,也就是根本没办法计算相机的右侧是那一边,相机的上边也同样没办法计算。这是纯数学问题导致的,纯数学问题也是最难解决的问题,现在多软件的相机使用都是四元数来解决这个问,有兴趣的朋友可以去查找相关资料。

总结

        此片文章看完OpenGL入门基础篇已经写完了,相信你也可以做一个像样的游戏出来了,比如以前红白机上的一些游戏,可能觉得很Low,但凡事都要一步一个脚印,游戏也是一路发展过来的,没有多少人生来就特别牛。笔者在这里感谢大家的支持。


http://www.kler.cn/a/463889.html

相关文章:

  • 【网络】ARP表、MAC表、路由表
  • iOS 11 中的 HEIF 图像格式 - 您需要了解的内容
  • 拟声 0.60.0 | 拟态风格音乐播放器,支持B站音乐免费播放
  • GitHub 及 GitHub Desktop 详细使用教程(通俗易懂)
  • 游戏引擎学习第69天
  • Java 代码编译和解析方法信息
  • 【2024年-6月-14日-开源社区openEuler实践记录】探索 test - tools:高效测试的开源宝库
  • 电子电器架构 --- 自动驾驶技术中的LiDAR
  • windows编译llama.cpp GPU版本
  • Java Web开发基础——Java Web项目中的MVC设计模式
  • Leetcode打卡:二叉树中的链表
  • C++进阶重点知识(一)|智能指针|右值|lambda|STL|正则表达式
  • 《深入浅出HTTPS​​​​​​​​​​​​​​​​​》读书笔记(23):密钥协商算法(续)
  • ChatGLM2-6B模型推理流程和模型架构详解
  • SpringBoot_第二天
  • 机器学习中回归预测模型中常用四个评价指标MBE、MAE、RMSE、R2解释
  • 【从零开始入门unity游戏开发之——C#篇38】C#预处理器指令
  • locate() 在MySQL中的用法
  • 关于部署异常的处理问题
  • 网络渗透测试实验四:CTF实践
  • Spring Boot 嵌套事务详解及失效解决方案
  • leetcode hot 100 杨辉三角
  • python-leetcode-轮转数组
  • JVM实战—G1垃圾回收器的原理和调优
  • java下载文件流,不生成中间文件。
  • deepFM模型pytorch实现