计算机图形学 | 实验七:完成摄像机类的创建

news2024/12/23 16:03:45

计算机图形学 | 实验七:完成摄像机类的创建

  • 计算机图形学 | 实验七:完成摄像机类的创建
    • 摄像机/观察空间
    • Look At 矩阵
    • 自由移动
    • 视角移动
    • 鼠标输入
    • 缩放

华中科技大学《计算机图形学》课程

MOOC地址:计算机图形学(HUST)

计算机图形学 | 实验七:完成摄像机类的创建

摄像机/观察空间

摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。

我们在构造摄像机类的时候。着重强调4个重要的向量:

  • 摄像机位置:摄像机位置简单来说就是世界空间中一个指向摄像机位置的向量;
  • 摄像机方向:指的是摄像机指向哪个方向。一般我们让摄像机指向场景原点:(0, 0, 0);
  • 右轴:代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(比如(0,1,0))。接下来把上向量和第二步得到的方向向量进行叉乘。得到的结果即为右向量;
  • 上轴:有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把右向量和方向向量进行叉乘得到上轴。

Look At 矩阵

LookAt矩阵就像它的名字表达的那样:它会创建一个看着(Look at)给定目标的观察矩阵。具体形式如下:

在这里插入图片描述

其中R是右向量,U是上向量,D是方向向量P是摄像机位置向量。GLM已经提供了这些支持。我们要做的只是定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量)。接着GLM就会创建一个LookAt矩阵,我们可以把它当作我们的观察矩阵。

glm::mat4 Camera::GetViewMatrix()
{
	return glm::lookAt(Position, Position + Forward, Up);
}

自由移动

现在我们为GLFW的键盘输入定义一个processInput函数,我们来新添加几个需要检查的按键命令。使用GLFW的glfwGetKey函数,它需要一个窗口以及一个按键作为输入。这个函数将会返回这个按键是否正在被按下。我们使用WSAD来模拟摄像机的前后左右运动。

//键盘输入
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);

	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		camera.ProcessKeyboard(FORWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		camera.ProcessKeyboard(BACKWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		camera.ProcessKeyboard(LEFT, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		camera.ProcessKeyboard(RIGHT, deltaTime);
}

目前我们的移动速度是个常量。理论上没什么问题,但是实际情况下根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。

图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

float deltaTime = 0.0f; // 当前帧与上一帧的时间差 
float lastFrame = 0.0f; // 上一帧的时间
//每一帧计算出新的deltaTime 
float currentFrame = glfwGetTime(); 
deltaTime = currentFrame - lastFrame; 
lastFrame = currentFrame; //从而控制移动速度 
float cameraSpeed = 2.5f * deltaTime;

视角移动

为了能够改变视角,我们需要根据鼠标的输入改变cameraFront向量,这里我们将用到数学里的欧拉角的相关知识。

欧拉角是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),如下图所示:

在这里插入图片描述

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

对于摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会在代码中加入滚转角。

鼠标输入

偏航角和俯仰角是通过鼠标移动获得的,水平的移动影响偏航角,竖直的移动影响俯仰角。通过储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置不上一帧的位置相差多少。

首先我们要告诉GLFW,它应该隐藏光标,并捕捉(Capture)它。捕捉光标表示的是,如果焦点在你的程序上,光标应该停留在窗口中(除非程序失去焦点戒者退出)。我们可以用GLFW为我们提供的方法来配置:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

让GLFW监听鼠标移动事件。(和键盘输入相似)使用的函数如下:

//鼠标移动响应
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;

	camera.ProcessMouseMovement(xoffset, yoffset);
}

这里我们还引入了一个量sensitivity(灵敏度)来控制鼠标移动对视角影响的幅度。

float sensitivity = 0.1f; 
xoffset *= sensitivity; 
yoffset *= sensitivity;

其次我们需要添加一些限制,防止摄像机发生奇怪的移动(对于俯仰角,要让用户不能看向高于89度的地方,因为在90度时视角会发生逆转,所以我们把89度作为极限)。

if (pitch > 89.0f) pitch = 89.0f; 
if (pitch < -89.0f) pitch = -89.0f;

如果你现在运行代码,你会发现在窗口第一次获取焦点的时候摄像机会突然跳一下。想一想原因是什么?

答:在你的鼠标移动迚窗口的那一刻,鼠标回调函数就会被调用,这时候的xpos和ypos会等亍鼠标刚刚迚入屏幕的那个位置。这通常是一个距离屏幕中心很进的地方,因而产生一个很大的偏移量,所以就会跳了。我们可以简单的使用一个bool变量检验我们是否是第一次获取鼠标输入,如果是,那么我们先把鼠标的刜始位置更新为xpos和ypos值,这样就能解决这个问题;接下来的鼠标移动就会使用刚迚入的鼠标位置坐标来计算偏移量了。

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

缩放

作为我们摄像机系统的一个附加内容,我们还会来实现一个缩放(Zoom)接口。在乊前的教程中我们说视野(Field of View)戒fov定义了我们可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。我们会使用鼠标的滚轮来放大,和鼠标移动视角一样,我们规定鼠标滚轮事件实现视野的方法和缩小。

//鼠标滚轮响应
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; 
}

其中,45.0f是默认设置的视野值。

同样之前记得用GLFW相应函数注册监控滚轮事件:

glfwSetScrollCallback(window, scroll_callback);

这样我们的摄像机类就完成了,运行程序后你可以通过WASD和鼠标控制你的视角。

运行结果:

在这里插入图片描述

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

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

相关文章

【自然语言处理】自然语言处理 --- NLP入门指南

文章目录 一、什么是NLP二、NLP任务类型三、NLP的预处理英文 NLP 语料预处理的 6 个步骤中文 NLP 语料预处理的 4 个步骤第1步&#xff1a;收集您的数据---语料库第2步&#xff1a;清理数据 --- 文本清洗第3步&#xff1a;分词第4步&#xff1a;标准化第5步&#xff1a;特征提取…

花式玩转二叉树层序遍历——实现二叉树Z字输出

文章目录 题目介绍二叉树层序遍历——队列实现Java完整代码 分析Java完整代码实现总结 题目介绍 这个题目是在做一个测试里面遇到的&#xff0c;大致描述如下&#xff1a; 现在有一棵二叉树&#xff0c;需要实现如图所示的交叉来回遍历&#xff1a; 即相较于普通的层序遍历&a…

基于目标级联法的微网群多主体分布式优化调度(已更新)

目录 一、主要内容 1.1 上层微网群模型 1.2 下层微网模型 二、部分程序 三、实现效果 四、下载链接 一、主要内容 本文复现《基于目标级联法的微网群多主体分布式优化调度》文献的目标级联部分&#xff0c; 建立微网群系统的两级递阶优化调度模型: 上层是微网群能量调度中…

Jvm --java虚拟机(下)

目录 执行引擎 什么是执行引擎&#xff1f; 什么是解释器&#xff1f;什么是 JIT 编译器&#xff1f; 为什么 Java 是半编译半解释型语言&#xff1f; JIT 编译器执行效率高为什么还需要解释器&#xff1f; 垃圾回收 垃圾回收概述 什么是垃圾&#xff1f; 为什么需要GC&a…

Redis持久化--RDB

一. RDB是什么 在指定的时间间隔内将内存中的数据集快照写入磁盘&#xff0c; 也就 Snapshot 快照&#xff0c;恢复时将快照文件读到内存二. RDB持久化的流程 解读&#xff1a; redis 客户端执行 bgsave 命令或者自动触发 bgsave 命令&#xff1b;主进程判断当前是否已经存在…

【开源之夏 2023】欢迎报名 SOFAStack 社区项目!

开源之夏是由“开源软件供应链点亮计划”发起并长期支持的一项暑期开源活动&#xff0c;旨在鼓励在校学生积极参与开源软件的开发维护&#xff0c;促进优秀开源软件社区的蓬勃发展&#xff0c;培养和发掘更多优秀的开发者。 活动联合国内外各大开源社区&#xff0c;针对重要开…

荔枝派Zero(全志V3S)驱动开发之RGB LCD屏幕显示bmp图片

文章目录 前言一、如何在 linux 下驱动 LCD1、什么是 Framebuffer 设备2、如何确保 Framebuffer 设备已存在3、Frame_buffer 设备结构体<1>、fb_info 详解<2>、struct fb_fix_screeninfo 详解<3>、struct fb_var_screeninfo 详解 4、设备树中有关 framebuffe…

使用 Appium 进行 WPF 自动化

文章目录 关于1 环境准备2 集成单元测试3 新增基本测试代码4 测试 WPF 程序5 启动测试 关于 参考链接&#xff1a;Get Your WPF Apps Automated With Appium Appium官网&#xff1a;http://appium.io/docs/en/2.0/quickstart 1 环境准备 一、下载 Windows Application Driv…

JVM 虚拟机栈

虚拟机栈概述 背景: 由于跨平台性的设计&#xff0c;Java 的指令都是根据栈来设计的。不同平台 CPU 架构不同&#xff0c;所以不能设计为基于寄存器的优点是跨平台, 指令集小&#xff0c;编译器容易实现&#xff0c;缺点是性能下降&#xff0c;实现同样的功能需要更多的指令 …

CVE-2023-21839 Weblogic RCE

前言 刷B站的时候给我推的一个WebLogic的比较新的漏洞&#xff0c;可以通过此漏洞直接达到RCE进行getShell的效果&#xff0c;于是就简单复现和分析一下&#xff0c;做个记录。 视频链接 漏洞简单分析 此漏洞是属于WebLogic的JNDI注入漏洞&#xff0c;漏洞造成的原因是Weblo…

《创新者的基因》读书笔记

本书是企业创新管理的研究成果&#xff0c;针对个人和企业如何培养、提升商业创新能力给出了行动指南&#xff0c;每一种能力都提供了很多训练小技巧&#xff0c;在此不一一列举&#xff0c;只写自己的读书笔记、对策和思考。 破坏性创新者的基因 发问&#xff1a;目的是提出…

Java字符串详解:概念、特点与常见的使用场景

Java字符串是开发中经常使用到的一种数据类型&#xff0c;使用它可以处理文本、URL、文件路径等多种类型的数据。本文将对Java字符串的概念、特点以及常见使用场景进行详细解释。本文将分为以下几个部分&#xff1a; String的概念和特点Java字符串常量池字符串的不可变性使用e…

LeetCode - 3. 无重复字符的最长子串

写在前面&#xff1a; 题目链接&#xff1a;LeetCode - 3. 无重复字符的最长子串 题目难度&#xff1a;中等 编程语言&#xff1a;C 一、题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释…

【RabbitMQ】SpringAMQP

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…

快速了解C语言的基本元素

C语言是一种编程语言&#xff0c;和其它语言一样&#xff0c;也定义了自己的语法和词汇。学习C语言&#xff0c;首先要学习C语言的词汇&#xff0c;再学习C语言的语法规则&#xff0c;然后由词汇构成语句&#xff0c;由语句构成源程序&#xff0c;源程序也称为源代码或代码&…

ORB-SLAM2的稠密重建实战(1) — 流程与函数功能说明

目录 0 系统整体流程 1 输出信息 2 初始化文件 3 初始化并运行追踪线程Tracking Step1&#xff1a;地图初始化 Step2&#xff1a;初始化成功&#xff08;mbOnlyTracking&#xff09; Step3&#xff1a;局部地图跟踪TrackLocalMap() Step4&#xff1a;跟踪成功 Step5&a…

【EKF】卡尔曼滤波的二维应用实例

前言 在上期&#xff0c;使用一个简单的一维应用实例来加深了卡尔曼滤波的印象后&#xff0c;使用一个二维的例子来看一下卡尔曼的效果。使用一个自由落体的例子来说明&#xff0c;假设一个物体在重力作用下&#xff0c;速度由0开始做自由落体运动&#xff0c;有观测装置对该物…

自动化部署编译部署【.net core】

自动化部署编译部署【.net core】 github 自动化编译部署 .NET 程序&#xff0c;程序有两个服务&#xff0c;一个是api&#xff0c;一个是admin. 需要部署到两台机器上(测试和正式)&#xff0c;所以采用两个Action来处理 项目目录结构 root ├── Config │ ├── deploy …

【Linux】进程间通信 —— 共享内存

文章目录 &#x1f4d5; 共享内存的原理&#x1f4d5; 代码实现 & 深入理解共享内存shmget() 函数shmctl() 、shmdt()、shmat()特点 &#x1f4d5; 源代码comm.hppserver.ccclient.cc &#x1f4d5; 共享内存的原理 我们知道&#xff0c;如果想实现进程间通信&#xff0c;…

Linux Shell 实现一键部署subversion

subversion SVN是subversion的缩写&#xff0c;是一个开放源代码的版本控制系统&#xff0c;通过采用分支管理系统的高效管理&#xff0c;简而言之就是用于多个人共同开发同一个项目&#xff0c;实现共享资源&#xff0c;实现最终集中式的管理。 TortoiseSVN TortoiseSVN 是…