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,但凡事都要一步一个脚印,游戏也是一路发展过来的,没有多少人生来就特别牛。笔者在这里感谢大家的支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/947679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

教程:从pycharm基于anaconda构建机器学习环境并运行第一个 Python 文件

1. 安装 PyCharm 访问 PyCharm 官方网站&#xff1a;https://www.jetbrains.com/pycharm/。下载社区版&#xff08;免费&#xff09;或专业版&#xff08;收费&#xff0c;提供更多功能&#xff09;。按照操作系统的安装指导安装 PyCharm。安装后打开 PyCharm&#xff0c;并根…

springcloud篇3-docker需熟练掌握的知识点

docker的原理请参考博文《Docker与Kubernetes》。 一、安装docker的指令 1.1 安装yum工具 yum install -y yum-utils \device-mapper-persistent-data \lvm2 --skip-broken补充&#xff1a;配置镜像源 注意&#xff1a; yum安装是在线联网下载安装&#xff0c;而很多的资源…

ceph文件系统

ceph文件系统&#xff1a;高度可扩展&#xff0c;分布式的存储文件系统&#xff0c;旨在提高性能&#xff0c;高可靠性和高可用的对 象存储&#xff0c;块存储&#xff0c;文件系统的存储。使用分布式的算法保证数据的高可用和一致性。 ceph的组件 1、MON&#xff1a;ceph m…

牛客网刷题 ——C语言初阶——BC117 小乐乐走台阶

1.题目 &#xff1a;BC117 小乐乐走台阶 牛客OJ题链接 描述 小乐乐上课需要走n阶台阶&#xff0c;因为他腿比较长&#xff0c;所以每次可以选择走一阶或者走两阶&#xff0c;那么他一共有多少种走法&#xff1f; 输入描述&#xff1a; 输入包含一个整数n (1 ≤ n ≤ 30) …

flux文生图 生成高质量图像

flux文生图 生成高质量图像 flyfish import torch from diffusers import FluxPipeline# 初始化 FluxPipeline model_path "/home/FLUX___1-dev" pipe FluxPipeline.from_pretrained(model_path, torch_dtypetorch.bfloat16) pipe.enable_model_cpu_offload() #…

设计模式 结构型 装饰器模式(Decorator Pattern)与 常见技术框架应用 解析

装饰器模式&#xff08;Decorator Pattern&#xff09;&#xff0c;又称为包装器模式&#xff08;Wrapper Pattern&#xff09;&#xff0c;是一种结构型设计模式。它允许在不改变原有对象结构的基础上&#xff0c;动态地给对象添加一些新的职责&#xff08;即增加其额外功能&a…

计算机毕业设计Python+Vue.js游戏推荐系统 Steam游戏推荐系统 Django Flask 游 戏可视化 游戏数据分析 游戏大数据 爬虫

2021年12月21日 姓名 专业 软件工程 班级 20-IBM-企Java2 题目 基于hadoopSpark的游戏推荐与可视化系统的设计与实现 指导教师 王文钧、王春娴 一、与本题目有关的国内外研究情况、题目研究的目的和意义、主要内容、本课题创新之处、拟解决的问题&#xff1a; 国内外…

[创业之路-222]:波士顿矩阵与GE矩阵在业务组合选中作用、优缺点比较

目录 一、波士顿矩阵 1、基本原理 2、各象限产品的定义及战略对策 3、应用 4、优点与局限性 二、技术成熟度模型与产品生命周期模型的配对 1、技术成熟度模型 2、产品生命周期模型 3、技术成熟度模型与产品生命周期模型的配对 三、产品生命周期与产品类型的对应关系 …

使用Python类库pandas操作Excel表格

Date: 2025.01.02 20:33:30 author: lijianzhan 简述&#xff1a;pandas 是处理 Excel 文件的强大工具&#xff0c;它提供了简单易用的接口来读取、操作和写入 Excel 数据。以下是使用 pandas 处理 Excel 文件的详细指南&#xff0c;包括常见操作和示例代码。 安装依赖,pandas …

Deepseek v3 的笔记

基本概述 Deepseek v3是Deepseek发布的旗舰模型&#xff0c;属于607B的混合专家&#xff08;MoE&#xff09;模型&#xff0c;其中活跃参数为37B。在当前的模型领域&#xff0c;它堪称最佳的开源模型&#xff0c;性能超越了Llama 3.1 405b、Qwen和Mistral等知名模型。根据基准…

Python多分类Logistic回归详解与实践

在机器学习中&#xff0c;Logistic回归是一种基本但非常有效的分类算法。它不仅可以用于二分类问题&#xff0c;还可以扩展应用于多分类问题。本文将详细介绍如何使用Python实现一个多分类的Logistic回归模型&#xff0c;并给出详细的代码示例。 一、Logistic回归简介 Logist…

前端,npm install安装依赖卡在sill idealTree buildDeps(设置淘宝依赖)

输入npm i后&#xff0c;一直卡在sill idealTree buildDeps&#xff0c;一动不动 cnpm可以安装成功&#xff0c;但使用cnpm不会生成package-lock.json文件 设置淘宝依赖&#xff0c;依然卡住&#xff0c;挂梯子也不行 解决方法&#xff1a; // 取消ssl验证 set strict-ssl …

装饰者模式

1、定义 装饰者模式&#xff1a;在不必改变原类和使用继承的情况下&#xff0c;动态地扩展一个对象的功能。它是通过创建一个包装对象&#xff0c;也就是装饰来包裹真实的对象 2、实际应用 星巴克的咖啡系统项目&#xff1a; 星巴克要求的各种下单功能&#xff1a;大杯原味、大…

招银网路Java后端一面,难度有点大!

这是一位武汉理工大学同学的招银网络一面面经,同样附带超详细的参考答案。大家可以用来查漏补缺,针对性地补短板。 招银网络一面还是比较简单的,基本都是一些比较重要且高频的常规八股,项目问的不多。到了二面的时候, 会开始主要考察你的项目。 1、自我介绍 自我介绍一般…

C++之设计模式

设计模式 简介单例模式饿汉模式懒汉模式 工厂模式简单工厂模式工厂方法模式抽象工厂模式 建造者模式代理模式 简介 设计模式是前辈们对代码开发经验的总结&#xff0c;是解决特定问题的⼀系列套路它不是语法规定&#xff0c;而是⼀套⽤来提高代码可复用性、可维护性、可读性、…

云效流水线使用Node构建部署前端web项目

云效流水线实现自动化部署 背景新建流水线配置流水线运行流水线总结 背景 先来看看没有配置云效流水线之前的部署流程&#xff1a; 而且宝塔会经常要求重新登录&#xff0c;麻烦的很 网上博客分享了不少的配置流程&#xff0c;这一篇博客的亮点就是不仅给出了npm命令构建&…

pycharm如何拉取一个git项目,然后,修改后再上传到自建的项目中?

以chattts为例 https://github.com/2noise/ChatTTS.git 1.建一个虚拟环境&#xff0c;用于项目使用 2.pychar&#xff4d;新建工程 &#xff13;.忽略 提示 勾选&#xff0c;新建远程仓库 设置账号和密码 设置git路径&#xff0c;一般是正确的&#xff0c;点测试即可 &…

(五)开机自启动以及scp工具文件传输小问题

文章目录 程序开机自启动先制作一个可执行程序第一种 通过命令行实现程序开机自启动第二种 通过 Linux 系统镜像实现程序开机自启动 scp工具文件传输小问题 程序开机自启动 原因&#xff1a;做成产品后&#xff0c;用户直接开机使用&#xff0c;总不能在开机执行程序后才可以使…

供需平台信息发布付费查看小程序系统开发方案

供需平台信息发布付费查看小程序系统主要是为了满足个人及企业用户的供需信息发布与匹配需求。 一、目标用户群体 个人用户&#xff1a;寻找兼职工作、二手物品交换、本地服务&#xff08;如家政、维修&#xff09;等。 小微企业&#xff1a;推广产品和服务&#xff0c;寻找合…

中建海龙:科技助力福城南产业片区绿色建筑发展

在快速发展的城市化进程中&#xff0c;绿色建筑以其环保、节能、可持续的特点日益受到重视。作为建筑工业化领域的领军企业&#xff0c;中建海龙科技有限公司&#xff08;简称“中建海龙”&#xff09;凭借其卓越的科技实力和创新举措&#xff0c;在推动绿色建筑发展方面做出了…