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

news2025/1/4 18:11:57

        前面的一篇文章笔者向大家介绍了模型变化矩阵,投影矩阵。现在只剩下最后一个观察矩阵没有和大家讲了。此片文章就为大家介绍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.coloradmin.cn/o/2269212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.CSS字体属性

3.1字体系列 CSS使用font-family属性定义文本的字体系列。 p{font-family:"微软雅黑"} div{font-family:Arial,"Microsoft Yahei",微软雅黑} 3.2字体大小 css使用font-size属性定义字体大小 p{ font-size:20px; } px(像素)大小是我们网页的最常用的单…

Spring-kafka快速Demo示例

使用Spring-Kafka快速发送/接受Kafka消息示例代码&#xff0c;项目结构是最基础的SpringBoot结构&#xff0c;提前安装好Kafka&#xff0c;确保Kafka已经正确启动 pom.xml&#xff0c;根据个人情况更换springboot、java版本等 <?xml version"1.0" encoding&qu…

R语言统计分析——自助法BOOTSTRAP(1)

参考资料&#xff1a;R语言实战【第2版】 所谓自助法&#xff0c;即从初始样本重复随机替换抽样&#xff0c;生成一个或一系列待检验统计量的经验分布。无需假设一个特定的理论分布&#xff0c;便可生成统计量的置信区间&#xff0c;并能检验统计假设。 举个例子&#xff1a; 我…

yolo数据集格式(txt)转coco格式,方便mmyolo转标签格式

近期使用mmyolo过程中发现工具自带的yolo2coco.py在转换完数据集格式后&#xff0c;可视化标签的时候会有标签错乱情况&#xff0c;具体原因也没找到&#xff0c;肯定是转换过程代码有问题&#xff0c;于是重新做一份代码直接从yolo数据集转化为coco的json格式。 代码如下&…

sonarqube 安装及使用

一、官网参考地址 相关版本下载地址 配置全局变量 .bash_profileexport SONAR_HOME=/Users/jd/soft/sonar-scanner-6.2.1.4610 export PATH=$PATH:$SONAR_HOME/bin export SQ_HOST=http://127.0.0.1:9000/ export SQ_TOKEN=squ_dbb1913e095a92a727a918a9ba6b1af94b007748二、…

Kafka中的Topic和Partition有什么关系?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka中的Topic和Partition有什么关系&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka中的Topic和Partition有什么关系&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Apache Kafka 中&#…

Lua语言入门 - Lua常量

在Lua中&#xff0c;虽然没有直接的常量关键字&#xff08;如C中的const&#xff09;&#xff0c;但你可以通过一些编程技巧和约定来实现类似常量的行为。以下是几种常见的方法&#xff1a; 1. 使用全局变量并命名规范 你可以定义一个全局变量&#xff0c;并通过命名约定来表示…

【brew安装失败】DNS 查询 raw.githubusercontent.com 返回的是 0.0.0.0

从你提供的 nslookup 输出看&#xff0c;DNS 查询 raw.githubusercontent.com 返回的是 0.0.0.0&#xff0c;这通常意味着无法解析该域名或该域名被某些 DNS 屏蔽了。这种情况通常有几个可能的原因&#xff1a; 可能的原因和解决方法 本地 DNS 问题&#xff1a; 有可能是你的本…

【竞技宝】LOL:IG新赛季分组被质疑

北京时间2024年12月31日&#xff0c;今天已经2024年的最后一天&#xff0c;在进入一月之后&#xff0c;英雄联盟将迎来全新的2025赛季。而目前新赛季第一阶段的抽签结果已经全部出炉&#xff0c;其中人气最高的IG战队在本次抽签中抽到了“绝世好签”引来了网友们的质疑。 首先介…

Unity Excel转Json编辑器工具

功能说明&#xff1a;根据 .xlsx 文件生成对应的 JSON 文件&#xff0c;并自动创建脚本 注意事项 Excel 读取依赖 本功能依赖 EPPlus 库&#xff0c;只能读取 .xlsx 文件。请确保将该脚本放置在 Assets 目录下的 Editor 文件夹中。同时&#xff0c;在 Editor 下再创建一个 Exc…

【竞技宝】CS2:HLTV2024职业选手排名TOP15-xantares

北京时间2024年12月30日&#xff0c;HLTV年度选手排名正在持续公布中&#xff0c;今日凌晨正式公布了今年的TOP15选手为EternalFire战队的xantares选手。 选手简介 xantares是一名来自于土耳其的CS职业选手&#xff0c;出生于1995年&#xff0c;今年已经29岁。早在2012年&…

智能商业分析 Quick BI

Quick BI 是阿里云提供的一款智能商业分析&#xff08;BI&#xff09;工具&#xff0c;旨在帮助企业快速获取业务洞察、优化决策过程、提升数据分析效率。通过强大的数据可视化和分析功能&#xff0c;Quick BI 能够帮助用户轻松连接多种数据源、创建多维度的报表和仪表盘&#…

uniapp - 小程序实现摄像头拍照 + 水印绘制 + 反转摄像头 + 拍之前显示时间+地点 + 图片上传到阿里云服务器

前言 uniapp&#xff0c;碰到新需求&#xff0c;反转摄像头&#xff0c;需要在打卡的时候对上传图片加上水印&#xff0c;拍照前就显示当前时间日期地点&#xff0c;拍摄后在呈现刚才拍摄的图加上水印&#xff0c;最好还需要将图片上传到阿里云。 声明 水印部分代码是借鉴的…

2024年12月31日Github流行趋势

项目名称&#xff1a;free-programming-books 项目地址url&#xff1a;https://github.com/EbookFoundation/free-programming-books项目语言&#xff1a;HTML历史star数&#xff1a;344575今日star数&#xff1a;432项目维护者&#xff1a;vhf, eshellman, davorpa, MHM5000, …

基于SpringBoot+Vue实现停车场管理系统

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

Java学习路线:Servlet(一)认识和创建Servlet

目录 创建Servlet WebServlet Servlet的生命周期 认识和使用HttpServlet Servlet是JavaEE的一个标准&#xff0c;他就像JDBC一样&#xff0c;由官方定义了一系列接口&#xff0c;而具体的实现由我们自己编写&#xff0c;最后交给Web服务器如Tomcat来运行我们编写的Servlet…

公路边坡安全监测中智能化+定制化+全面守护的应用方案

面对公路边坡的安全挑战&#xff0c;我们如何精准施策&#xff0c;有效应对风险&#xff1f;特别是在强降雨等极端天气下&#xff0c;如何防范滑坡、崩塌、路面塌陷等灾害&#xff0c;确保行车安全&#xff1f;国信华源公路边坡安全监测解决方案&#xff0c;以智能化、定制化为…

机器人对物体重定向操作的发展简述

物体重定向操作的发展简述 前言1、手内重定向和外部重定向2、重定向原语3、重定向状态转换网络4、连续任意姿态的重定向5、利用其他环境约束重定向总结Reference 前言 对于一些特殊的任务&#xff08;如装配和打包&#xff09;&#xff0c;对物体放置的位姿由明确的要求&#…

【AndroidAPP】权限被拒绝:[android.permission.READ_EXTERNAL_STORAGE],USB设备访问权限系统报错

一、问题原因 1.安卓安全性变更 Android 12 的安全性变更&#xff0c;Google 引入了更严格的 PendingIntent 安全管理&#xff0c;强制要求开发者明确指定 PendingIntent 的可变性&#xff08;Mutable&#xff09;或不可变性&#xff08;Immutable&#xff09;。 但是&#xf…

windows系统安装完Anaconda之后怎么激活自己的虚拟环境并打开jupyter

1.在win主菜单中找到Anaconda安装文件夹并打开终端 文件夹内有所有安装后的Anaconda的应用软件和终端窗口启动窗口 点击Anaconda Prompt&#xff08;Anaconda&#xff09;就会打开类似cmd的命令终端窗口&#xff0c;默认打开的路径是用户名下的路径 2.激活虚拟环境 使用命令…