Learn OpenGL 07 摄像机
定义摄像机参数
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);//摄像机位置glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);//摄像机方向,指向z轴正方向 glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));//摄像机右方向glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);//上方向
构建View矩阵
这里可以参考Games101的MVP变换那一节
视图变换要做的内容就是把相机放到正确的位置和角度上,但其实变换矩阵都是对模型进行变换,因为如果对模型进行视图变换以后,相机也就在正确的位置上了。
这里就是相机移动到原点,g指向-z,t指向y等等。因为正着构建矩阵很困难,所以可以先求矩阵的逆变换。
分别将X(1,0,0,0),Y,Z轴带入可以发现都符合
也就是这样
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);//摄像机位置glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);//摄像机方向,指向z轴正方向 glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));//摄像机右方向glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);//上方向glm::mat4 view;view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
自由移动摄像机
需要在按键检测函数里加上对应按键发生的时候对应的函数
float cameraSpeed = 0.05f; // adjust accordinglyif (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)cameraPos += cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)cameraPos -= cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
转换欧拉角
对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。给定一个俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。
如果我们想象自己在xz平面上,看向y轴,我们可以基于第一个三角形计算来计算它的长度/y方向的强度(Strength)(我们往上或往下看多少)。从图中我们可以看到对于一个给定俯仰角的y值等于sin θ:
direction.y = sin(glm::radians(pitch)); // 注意我们先把角度转为弧度
这里我们只更新了y值,仔细观察x和z分量也被影响了。从三角形中我们可以看到它们的值等于:
direction.x = cos(glm::radians(pitch));
direction.z = cos(glm::radians(pitch));
看看我们是否能够为偏航角找到需要的分量:
就像俯仰角的三角形一样,我们可以看到x分量取决于cos(yaw)
的值,z值同样取决于偏航角的正弦值。把这个加到前面的值中,会得到基于俯仰角和偏航角的方向向量:
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
这样我们就有了一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。
鼠标输入与缩放
分别设置鼠标移动和滚轮滑动的回调函数就可以了
可以先设置这个函数隐藏光标
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//隐藏光标
设置两个回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos;lastX = xpos;lastY = ypos;float sensitivity = 0.03;xoffset *= sensitivity;yoffset *= sensitivity;yaw += xoffset;pitch += yoffset;if (pitch > 89.0f)pitch = 89.0f;if (pitch < -89.0f)pitch = -89.0f;glm::vec3 front;front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));front.y = sin(glm::radians(pitch));front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));cameraFront = glm::normalize(front);
}void scroll_callbacks(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;
}
要到前面注册回调函数
glfwSetScrollCallback(window, scroll_callbacks);//注册回调函数glfwSetCursorPosCallback(window, mouse_callback);
实现摄像机类
因为每个场景都一定会需要一个摄像机,所以将摄像机类分离出来是个很有必要的事情,方便以后重复使用。直接创建一个摄像机类,它会自动生成一个camera.h文件和camera.cpp文件
camera.h
#pragma once#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <GLFW/glfw3.h>// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {FORWARD,BACKWARD,LEFT,RIGHT
};// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 2.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;class Camera
{
public:// camera Attributesglm::vec3 Position; //摄影机位置glm::vec3 Front; //Forward 摄影机的“方向”(一个和朝向相反的向量)glm::vec3 Up; //摄影机的上方向glm::vec3 Right;glm::vec3 WorldUp; //世界的上方向// euler Anglesfloat Yaw;//偏航角float Pitch;//俯仰角// camera optionsfloat MovementSpeed;float MouseSensitivity;float Zoom;//FOV// constructor with vectorsCamera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH);Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch);glm::mat4 GetViewMatrix();void ProcessKeyboard(Camera_Movement direction, float deltaTime);//按键检测函数void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true);//鼠标移动检测函数void ProcessMouseScroll(float yoffset);//滚轮移动函数private:// calculates the front vector from the Camera's (updated) Euler Anglesvoid updateCameraVectors();//通过欧拉角更新摄像机位置};
Camera.cpp
#include "Camera.h"Camera::Camera(glm::vec3 position, glm::vec3 up, float yaw, float pitch): Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
{Position = position;WorldUp = up;Yaw = yaw;Pitch = pitch;updateCameraVectors();
}Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch): Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
{Position = glm::vec3(posX, posY, posZ);WorldUp = glm::vec3(upX, upY, upZ);Yaw = yaw;Pitch = pitch;updateCameraVectors();
}// returns the view matrix calculated using Euler Angles and the LookAt Matrix
glm::mat4 Camera::GetViewMatrix()
{return glm::lookAt(Position, Position + Front, Up);
}// processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
void Camera::ProcessKeyboard(Camera_Movement direction, float deltaTime)
{float velocity = MovementSpeed * deltaTime;if (direction == FORWARD)Position += Front * velocity;if (direction == BACKWARD)Position -= Front * velocity;if (direction == LEFT)Position -= Right * velocity;if (direction == RIGHT)Position += Right * velocity;
}// processes input received from a mouse input system. Expects the offset value in both the x and y direction.
void Camera::ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch )
{xoffset *= MouseSensitivity;yoffset *= MouseSensitivity;Yaw += xoffset;Pitch += yoffset;// make sure that when pitch is out of bounds, screen doesn't get flippedif (constrainPitch){ if (Pitch > 89.0f)Pitch = 89.0f;if (Pitch < -89.0f)Pitch = -89.0f;}//std::cout<<// update Front, Right and Up Vectors using the updated Euler anglesupdateCameraVectors();
}// processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
void Camera::ProcessMouseScroll(float yoffset)
{Zoom -= (float)yoffset;if (Zoom < 1.0f)Zoom = 1.0f;if (Zoom > 45.0f)Zoom = 45.0f;
}void Camera::updateCameraVectors()
{// calculate the new Front vectorglm::vec3 front;front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));front.y = sin(glm::radians(Pitch));front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));Front = glm::normalize(front);// also re-calculate the Right and Up vectorRight = glm::normalize(glm::cross(Front, WorldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.Up = glm::normalize(glm::cross(Right, Front));
}
大功告成,注意在main函数里对应的变量也需要修改,然后main.cpp里原来声明的定义相机的变量就可以删掉了
练习
-
看看你是否能够修改摄像机类,使得其能够变成一个真正的FPS摄像机(也就是说不能够随意飞行);你只能够呆在xz平面上:参考解答
-
只需要最后将y值永远设置在平面上就可以
void Camera::ProcessKeyboard(Camera_Movement direction, float deltaTime)
{float velocity = MovementSpeed * deltaTime;if (direction == FORWARD)Position += Front * velocity;if (direction == BACKWARD)Position -= Front * velocity;if (direction == LEFT)Position -= Right * velocity;if (direction == RIGHT)Position += Right * velocity;Position.y = 0.0f;}
-
试着创建你自己的LookAt函数,其中你需要手动创建一个我们在一开始讨论的观察矩阵。用你的函数实现来替换GLM的LookAt函数,看看它是否还能一样地工作:参考解答
根据这张图把矩阵构建出来即可
注意GLM规定了矩阵的第一个参数是列,第二个参数是行
旋转矩阵的第一行是右向量,第二行是上面的向量,第三行是方向向量。
依次计算出向量搭建出矩阵就可以了
// Custom implementation of the LookAt function
glm::mat4 Camera::calculate_lookAt_matrix(glm::vec3 position, glm::vec3 target, glm::vec3 worldUp)
{// 1. Position = known// 2. Calculate cameraDirectionglm::vec3 zaxis = glm::normalize(position - target);// 3. Get positive right axis vectorglm::vec3 xaxis = glm::normalize(glm::cross(glm::normalize(worldUp), zaxis));// 4. Calculate camera up vectorglm::vec3 yaxis = glm::cross(zaxis, xaxis);// Create translation and rotation matrix// In glm we access elements as mat[col][row] due to column-major layoutglm::mat4 translation = glm::mat4(1.0f); // Identity matrix by defaulttranslation[3][0] = -position.x; // Third column, first rowtranslation[3][1] = -position.y;translation[3][2] = -position.z;glm::mat4 rotation = glm::mat4(1.0f);rotation[0][0] = xaxis.x; // First column, first rowrotation[1][0] = xaxis.y;rotation[2][0] = xaxis.z;rotation[0][1] = yaxis.x; // First column, second rowrotation[1][1] = yaxis.y;rotation[2][1] = yaxis.z;rotation[0][2] = zaxis.x; // First column, third rowrotation[1][2] = zaxis.y;rotation[2][2] = zaxis.z;// Return lookAt matrix as combination of translation and rotation matrixreturn rotation * translation; // Remember to read from right to left (first translation then rotation)
}
相关文章:

Learn OpenGL 07 摄像机
定义摄像机参数 glm::vec3 cameraPos glm::vec3(0.0f, 0.0f, 3.0f);//摄像机位置glm::vec3 cameraTarget glm::vec3(0.0f, 0.0f, 0.0f);glm::vec3 cameraDirection glm::normalize(cameraPos - cameraTarget);//摄像机方向,指向z轴正方向 glm::vec3 up glm::vec…...

Linux系统部署火狐浏览器结合内网穿透实现公网访问
目录 前言 1. 部署Firefox 2. 本地访问Firefox 3. Linux安装Cpolar 4. 配置Firefox公网地址 5. 远程访问Firefox 6. 固定Firefox公网地址 7. 固定地址访问Firefox 结语 前言 作者简介: 懒大王敲代码,计算机专业应届生 今天给大家聊聊Linux系统…...

Elastic Stack--05--聚合、映射mapping
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1.聚合(aggregations)基本概念桶(bucket)度量(metrics) 案例 11. 接下来按price字段进行分组:2. 若想对所…...

【嵌入式——QT】Model/View
【嵌入式——QT】Model/View 基本原理数据模型视图组件代理Model/View结构的一些概念QFileSystemModelQStringListModelQStandardItemModel自定义代理 基本原理 GUI应用程序的一个很重要的功能是由用户在界面上编辑和修改数据,典型的如数据库应用程序,数…...
向量化编程书籍推荐
文章目录 1. 书籍清单 1. 书籍清单 《Linear Algebra and Its Applications》 by Gilbert Strang 这本书是线性代数的经典教材,线性代数是向量化编程的基础。它涵盖了向量、矩阵、线性变换等内容,对理解向量化编程的数学概念非常有帮助《NumPy Beginner…...
算法D43 | 动态规划5 | 1049. 最后一块石头的重量 II 494. 目标和 474.一和零
1049. 最后一块石头的重量 II 本题就和 昨天的 416. 分割等和子集 很像了,可以尝试先自己思考做一做。 视频讲解:动态规划之背包问题,这个背包最多能装多少?LeetCode:1049.最后一块石头的重量II_哔哩哔哩_bilibili 代…...

设计模式—桥接模式
定义: 桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interfce)模式。 本章代码:小麻雀icknn/设计模式练习 - Gitee.com 结构: 抽象化(Abstraction)角色:…...
伊萨卡训练代码
我们建议创建并激活 conda 环境,以确保在下面安装正确的软件包版本的干净环境。 # Optional but recommended: conda create -n ithaca python3.9 conda activate ithaca 克隆此存储库并进入其根目录。通过以下方式安装完整的 ithaca 依赖项(包括训练&am…...
视频产品介绍:AS-VCVR-N多协议视频接入网关
目 录 一、产品概述 (一)非标设备接入 (二)信令流转换 (三)媒体流转发 二、网关特性 三、技术参数 一、产品概述 视频接入网关服务是终端用户与视频源的传输枢纽,实现把前端不同…...

大型网站架构演化总结
本文图解大型网站架构演化。 目录 1、单一应用服务阶段 2、应用与数据服务分离阶段 3、利用缓存提高性能阶段 4、应用服务集群阶段 5、数据库读写分离阶段 6、反向代理与CDN加速阶段 7、分布式数据库阶段 8、 NoSQL与搜索引擎阶段 9、业务拆分阶段 10、分布式服务阶…...

5G智能制造纺织工厂数字孪生可视化平台,推进纺织行业数字化转型
5G智能制造纺织工厂数字孪生可视化平台,推进纺织行业数字化转型。纺织工业作为传统制造业的重要组成部分,面临着转型升级的紧迫需求。随着5G技术的快速发展,智能制造成为纺织工业转型升级的重要方向。数字孪生可视化平台作为智能制造的核心技…...

仿牛客网项目---Elasticsearch分布式搜索引擎
1.什么是ElasticSearch分布式搜索引擎? Elasticsearch是一个开源的分布式搜索引擎,提供实时的、高可用性的搜索和分析解决方案。它支持快速索引和搜索大规模数据,具有分布式架构、RESTful API、基于JSON的查询语言等功能,适用于各…...

macbook pro 2018 安装 arch linux 双系统
文章目录 友情提醒关于我的 mac在 mac 上需要提前做的事情复制 wifi 驱动 在 linux 上的操作还原 wifi 驱动连接 wifi 网络磁盘分区制作文件系统挂载分区 使用 archinstall 来安装 arch linux遗留问题 友情提醒 安装 archl linux 的时候,mac 的键盘是没法用的&#…...

虚拟机安装CentOS教学,超详细一步安装到底!
首先将Centos的镜像文件进行下载,随后再进行安装配置: https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.8.2003/isos/x86_64/CentOS-7-x86_64-DVD-2003.iso 1.打开VMware,新建虚拟机,选择典型安装,点击下一步 2.选择稍…...

“2024杭州智慧城市及安防展会”将于4月在杭州博览中心盛大召开
2024杭州国际智慧城市及安防展览会,将于4月24日在杭州国际博览中心盛大开幕。这场备受瞩目的盛会,不仅汇集了全球智慧城市与安防领域的顶尖企业,更是展示最新技术、交流创新理念的重要平台。近日,从组委会传来消息,展会…...

【C++庖丁解牛】模拟实现STL的string容器(最后附源码)
📙 作者简介 :RO-BERRY 📗 学习方向:致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持 目录 1.vs和g下string结构…...

不要在代码中随便使用try...catch了
前言 📫 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步! 🍅 个人主页:南木元元 目录 背景 js中的try...catch try...catch运行机制 js的事件循环机制 try...c…...

网络编程(3/6)
使用C语言完成数据库的增删改 #include<myhead.h> int do_add(sqlite3 *ppDb) {int numb;char name[50];int salary;printf("请输入员工信息:工号、姓名、薪水\n");scanf("%d %s %d",&numb,name,&salary);char sql[128];char *e…...

(day 2)JavaScript学习笔记(基础之变量、常量和注释)
概述 这是我的学习笔记,记录了JavaScript的学习过程,我是有一些Python基础的,因此在学习的过程中不自觉的把JavaScript的代码跟Python代码做对比,以便加深印象。我本人学习软件开发纯属个人兴趣,大学所学的专业也非软件…...

Spring Boot中全局异常处理器
文章目录 1.Spring Boot中两种异常处理方式2.为什么需要全局异常处理呢?3. 全局异常处理器测试4.ControllerAdvice 详解5.ExceptionHandler 详解 1.Spring Boot中两种异常处理方式 要想解决测试中存在的问题,我们需要对程序中可能出现的异常进行捕获&am…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...