OpenGL原理与实践——核心模式(四):摄像机变换理论与应用

news2025/8/8 0:38:58

目录

变换是什么?对于OpenGL的摄像机又意味着什么?

MVP变换

投影变换

正交投影变换

透视投影变换 

摄像机/投影矩阵的应用

绘制单个立方体——源码及渲染结果

绘制多个立方体——源码及渲染结果

构建摄像机类——实现控制摄像机移动

摄像机坐标系的构成

摄像机坐标系的参数构建

摄像机关键参数提取(移动参数)

摄像机平移功能实现

构建摄像机类——实现控制摄像机旋转

摄像机front向量的xyz

鼠标跟踪控制方法分解 

摄像机旋转功能实现


变换是什么?对于OpenGL的摄像机又意味着什么?

答:就是将三维物体映射到摄像机屏幕的过程。

 在线性代数中,变换=矩阵,任何3D变化都可以通过矩阵来表示。 

MVP变换

那么问题来了,如何将三维物体映射到屏幕空间呢?

答:经过MVP变换。

这里不对MVP变换做过多的展开,详细理论可参考GAMES101的课程。

那么如何变换呢?如何获得物体在观察坐标系下的位置呢?

不失一般性,我们考虑一个简单的情况,即观察坐标系与世界坐标系平行:

  •  首先我们将观察坐标系平移变换到世界坐标系原点,那么会有一个对应的矩阵
  • 我们用同样的矩阵,对物体做一次变换,即可得到物体在观察坐标系下的位置

但更一般的情况是:摄像机空间与世界空间存在一定的角度:

那此时如何得到物体在摄像机空间的相对位置呢?

答:可以将摄像机移动到世界坐标系原点,然后将摄像机坐标系的三个正交基旋转至与世界坐标系重合。

但问题来了,如何获得这个旋转矩阵呢?这个矩阵并不好算。

但我们可以曲线救国,利用矩阵的逆。

既然直接求摄像机空间旋转至世界空间不好求,那我们可以把世界空间旋转至摄像机空间,并获得一个矩阵,然后再求这个矩阵的逆,即为摄像机空间旋转至世界空间的变换矩阵。

投影变换

投影分两种:

投影变换的目标是:将空间中的点P变换到摄像机平面坐标系内,并且做成NDC(标准设备坐标)的形式。NDC为了兼容不同窗口的宽高,让x与y的都同一到(-1,1)范围内,之后各自乘以宽高

正交投影变换

透视投影变换 

 

空间中的点投射到屏幕近剪裁平面的坐标。

过多的数学推导这里不做展示,直接给出结果:

 

 

摄像机/投影矩阵的应用

下载并载入glm第三方库:

类似之前实践的思路,我们需要更改和设置:

  • 设置glm::mat4 初始化view摄像机矩阵和proj投影矩阵
  • 设置好需要绘制的正方体六个面
  • 处理相应锚点并激活,这里不需要索引处理了
  • 在shader中添加设置矩阵的方法
  • 在render()中,利用glm设置相应矩阵,并开启深度测试

绘制单个立方体——源码及渲染结果

#include "Base.h"
#include "Shader.h"
#include "ffImage.h"

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void initModel();
void initShader(const char* _vertexPath, const char* _fragPath);
void initTexture();
void render();

unsigned int VBO = 0;
unsigned int VAO = 0;
unsigned int EBO = 0;
unsigned int _texture = 0;

Shader _shader;
ffImage* _pImage = nullptr;

glm::mat4 _viewMatrix(1.0f);
glm::mat4 _projMatrix(1.0f);
int _width = 800;
int _height = 600;

int main() {
	//初始化OpenGL上下文环境,OpenGL是一个状态机,会保存当前状态下的渲染状态以及管线的状态
	glfwInit(); 
	//,3版本以上
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	//用OpenGL核心开发模式
	glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);

	//创建窗体
	GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGl Core", nullptr, nullptr);
	if (window == nullptr) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	//把当前上下文绑定至当前窗口
	glfwMakeContextCurrent(window);

	//通过glad绑定各种函数指针
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//视口:需要渲染的东西在哪里
	glViewport(0, 0, _width, _height);
	//当Frame大小变动,调用回调函数调整视口大小
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	initModel();
	initShader("vertexShader.glsl", "fragmentShader.glsl");
	initTexture();

	//防止窗口结束退出
	while (!glfwWindowShouldClose(window)) {
		processInput(window);

		render();

		//双缓冲
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	//结束,释放资源
	glfwTerminate();
	return 0;

}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window) {
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}
}

//渲染
void render() {
	//擦除画布,用定义的颜色填充
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	//眼睛(摄像机)位置,看向位置,摄像机上方位置
	_viewMatrix = glm::lookAt(
		glm::vec3(3.0f, 3.0f, 3.0f),
		glm::vec3(0.0f, 0.0f, 0.0f),
		glm::vec3(0.0f, 1.0f, 0.0f));
	//透视投影矩阵:视锥角,宽高比,近平面,远平面
	_projMatrix = glm::perspective(
		glm::radians(45.0f),
		(float)_width / (float)_height,
		0.1f,
		100.0f);

	glBindTexture(GL_TEXTURE_2D, _texture);
	_shader.start();
	_shader.setMatrix("_viewMatrix", _viewMatrix);
	_shader.setMatrix("_projMatrix", _projMatrix);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 36);
	_shader.end();
}

//构建模型数据:VBO,VAO
void initModel() {
	//float vertices[] = {
	//	//顶点信息				颜色信息			纹理信息
	//	0.5f, 0.5f, 0.0f,		1.0f,0.0f,0.0f,		1.0f,1.0f,
	//	0.5f, -0.5f, 0.0f,	 	0.0f,1.0f,0.0f,		1.0f,0.0f,
	//	-0.5f, -0.5f, 0.0f,		0.0f,0.0f,1.0f,		0.0f,0.0f,
	//	-0.5f, 0.5f, 0.0f,		0.0f,1.0f,0.0f,		0.0f,1.0f
	//};

	float vertices[] = {
	//第一个面,六个顶点两个三角形
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f
	};

	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	//之后的VBO便属于了VAO的管理范围
	glGenBuffers(1, &VBO);
	//绑定哪一种buffer, 
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(sizeof(float)*3));
	
	//打开锚点:激活
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

	//解绑
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}

//
void initShader(const char* _vertexPath, const char* _fragPath) {
	
	_shader.initShader(_vertexPath, _fragPath);
}

void initTexture() {
	_pImage = ffImage::readFromFile("res/wall.jpg");
	//纹理绑定
	glGenTextures(1, &_texture);
	glBindTexture(GL_TEXTURE_2D, _texture);
	//纹理属性设置
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	//读入图片数据
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _pImage->getWidth(), _pImage->getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE,_pImage->getData());
}
//vertexShader.glsl
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aUV;

out vec2 outUV;

uniform mat4 _modelMatrix;
uniform mat4 _viewMatrix;
uniform mat4 _projMatrix;


void main()
{
   gl_Position = _projMatrix * _viewMatrix * _modelMatrix * vec4(aPos.x, aPos.y, aPos.z, 1.0);
   outUV = aUV;
};
//fragmentShader.glsl
#version 330 core
out vec4 FragColor;

in vec2 outUV;

uniform sampler2D outTexture;

void main()
{
    FragColor = texture(outTexture, outUV) ;
};

 

绘制多个立方体——源码及渲染结果

这里绘制10个立方体,对每一个立方体先进行一个平移操作,并且进行旋转操作

//渲染
void render() {
	//擦除画布,用定义的颜色填充
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	glm::vec3 modelVecs[] = {
	glm::vec3(0.0f,  0.0f,  0.0f),
	glm::vec3(2.0f,  5.0f, -15.0f),
	glm::vec3(-1.5f, -2.2f, -2.5f),
	glm::vec3(-3.8f, -2.0f, -12.3f),
	glm::vec3(2.4f, -0.4f, -3.5f),
	glm::vec3(-1.7f,  3.0f, -7.5f),
	glm::vec3(1.3f, -2.0f, -2.5f),
	glm::vec3(1.5f,  2.0f, -2.5f),
	glm::vec3(1.5f,  0.2f, -1.5f),
	glm::vec3(-1.3f,  1.0f, -1.5f)
	};

	_viewMatrix = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
	_projMatrix = glm::perspective(glm::radians(45.0f), (float)_width / (float)_height, 0.1f, 100.0f);

	glBindTexture(GL_TEXTURE_2D, _texture);

	for (int i = 0; i < 10; i++)
	{
		glm::mat4 _modelMatrix(1.0f);//单位阵
		_modelMatrix = glm::translate(_modelMatrix, modelVecs[i]);//平移
		_modelMatrix = glm::rotate(_modelMatrix, glm::radians((float)glfwGetTime() * (i + 1) * 10), glm::vec3(0.0f, 1.0f, 0.0f));

		_shader.start();
		_shader.setMatrix("_modelMatrix", _modelMatrix);
		_shader.setMatrix("_viewMatrix", _viewMatrix);
		_shader.setMatrix("_projMatrix", _projMatrix);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		_shader.end();
	}
}

 

 

构建摄像机类——实现控制摄像机移动

需要完成的任务:

  • 接受鼠标以及键盘的信息,并做出反馈
  • 提供接口,控制摄像机上下左右观望、前后左右移动

摄像机坐标系的构成

  • 确定位置:Position
  • 确定朝向:与世界坐标系做连线,但相反的方向作为z轴
  • 确定右方:人为规定一个up向量,比如世界坐标的y轴,与摄像机空间z轴做叉乘,得到right向量
  • 确定上方:通过已求得的两个向量right和direction,叉乘获得up向量

摄像机坐标系的参数构建

 

 

摄像机关键参数提取(移动参数)

 

摄像机平移功能实现

摄像机类:

//Camera.h
#pragma once
#include "Base.h"

enum class CAMERA_MOVE{
	MOVE_LEFT,
	MOVE_RIGHT,
	MOVE_FRONT,
	MOVE_BACK
};

class Camera
{
private:
	glm::vec3	m_position;
	glm::vec3	m_front;
	glm::vec3	m_up;
	float		m_speed;

	glm::mat4	m_vMatrix;

public:
	Camera() {
		m_position = glm::vec3(1.0f);
		m_front= glm::vec3(1.0f);
		m_up = glm::vec3(1.0f);
		m_speed = 0.01f;

		m_vMatrix = glm::mat4(1.0f);
	}
	~Camera() {}

	void lookAt(glm::vec3 _pos, glm::vec3 _front, glm::vec3 _up);
	void update();
	glm::mat4 getMatrix() { return m_vMatrix; }
	void move(CAMERA_MOVE _mode);
	void setspeed(float _speed);
};


//Camera.cpp
#include "Camera.h"

void Camera::lookAt(glm::vec3 _pos, glm::vec3 _front, glm::vec3 _up)
{
	m_position = _pos;
	m_front = glm::normalize(_front);
	m_up = _up;
	m_vMatrix = glm::lookAt(m_position, m_position + m_front, m_up);
}

void Camera::update()
{
	m_vMatrix = glm::lookAt(m_position, m_position + m_front, m_up);
}

void Camera::move(CAMERA_MOVE _mode)
{
	switch (_mode)
	{
	case CAMERA_MOVE::MOVE_LEFT:
		m_position -= glm::normalize(glm::cross(m_front, m_up)) * m_speed;
		break;
	case CAMERA_MOVE::MOVE_RIGHT:
		m_position += glm::normalize(glm::cross(m_front, m_up)) * m_speed;
		break;
	case CAMERA_MOVE::MOVE_FRONT:
		m_position += m_speed * m_front;
		break;
	case CAMERA_MOVE::MOVE_BACK:
		m_position -= m_speed * m_front;
		break;
	default:
		break;
	}
}

void Camera::setspeed(float _speed)
{
	m_speed = _speed;
}

 main.cpp:

#include "Base.h"
#include "Shader.h"
#include "ffImage.h"
#include "Camera.h"

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void initModel();
void initShader(const char* _vertexPath, const char* _fragPath);
void initTexture();
void render();

unsigned int VBO = 0;
unsigned int VAO = 0;
unsigned int EBO = 0;
unsigned int _texture = 0;

Shader _shader;
ffImage* _pImage = nullptr;
Camera	_camera;

glm::mat4 _projMatrix(1.0f);
int _width = 800;
int _height = 600;

int main() {
	//初始化OpenGL上下文环境,OpenGL是一个状态机,会保存当前状态下的渲染状态以及管线的状态
	glfwInit(); 
	//,3版本以上
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	//用OpenGL核心开发模式
	glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);

	//创建窗体
	GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGl Core", nullptr, nullptr);
	if (window == nullptr) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	//把当前上下文绑定至当前窗口
	glfwMakeContextCurrent(window);

	//通过glad绑定各种函数指针
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//视口:需要渲染的东西在哪里
	glViewport(0, 0, _width, _height);
	//当Frame大小变动,调用回调函数调整视口大小
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	_camera.lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, 1.0f, 0.0f));
	_camera.setspeed(0.01f);

	initModel();
	initShader("vertexShader.glsl", "fragmentShader.glsl");
	initTexture();

	//防止窗口结束退出
	while (!glfwWindowShouldClose(window)) {
		processInput(window);

		render();

		//双缓冲
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	//结束,释放资源
	glfwTerminate();
	return 0;

}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
	glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window) {
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
		glfwSetWindowShouldClose(window, true);
	}
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
		_camera.move(CAMERA_MOVE::MOVE_FRONT);
	}
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
		_camera.move(CAMERA_MOVE::MOVE_BACK);
	}
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
		_camera.move(CAMERA_MOVE::MOVE_LEFT);
	}
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
		_camera.move(CAMERA_MOVE::MOVE_RIGHT);
	}
}

//渲染
void render() {
	//擦除画布,用定义的颜色填充
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	glm::vec3 modelVecs[] = {
	glm::vec3(0.0f,  0.0f,  0.0f),
	glm::vec3(2.0f,  5.0f, -15.0f),
	glm::vec3(-1.5f, -2.2f, -2.5f),
	glm::vec3(-3.8f, -2.0f, -12.3f),
	glm::vec3(2.4f, -0.4f, -3.5f),
	glm::vec3(-1.7f,  3.0f, -7.5f),
	glm::vec3(1.3f, -2.0f, -2.5f),
	glm::vec3(1.5f,  2.0f, -2.5f),
	glm::vec3(1.5f,  0.2f, -1.5f),
	glm::vec3(-1.3f,  1.0f, -1.5f)
	};

	_camera.update();
	_projMatrix = glm::perspective(glm::radians(45.0f), (float)_width / (float)_height, 0.1f, 100.0f);

	glBindTexture(GL_TEXTURE_2D, _texture);

	for (int i = 0; i < 10; i++)
	{
		glm::mat4 _modelMatrix(1.0f);//单位阵
		_modelMatrix = glm::translate(_modelMatrix, modelVecs[i]);//平移
		_modelMatrix = glm::rotate(_modelMatrix, glm::radians((float)glfwGetTime() * (i + 1) * 10), glm::vec3(0.0f, 1.0f, 0.0f));//绕y轴旋转

		_shader.start();
		_shader.setMatrix("_modelMatrix", _modelMatrix);
		_shader.setMatrix("_viewMatrix", _camera.getMatrix());
		_shader.setMatrix("_projMatrix", _projMatrix);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 36);
		_shader.end();
	}
}

//构建模型数据:VBO,VAO
void initModel() {
	//float vertices[] = {
	//	//顶点信息				颜色信息			纹理信息
	//	0.5f, 0.5f, 0.0f,		1.0f,0.0f,0.0f,		1.0f,1.0f,
	//	0.5f, -0.5f, 0.0f,	 	0.0f,1.0f,0.0f,		1.0f,0.0f,
	//	-0.5f, -0.5f, 0.0f,		0.0f,0.0f,1.0f,		0.0f,0.0f,
	//	-0.5f, 0.5f, 0.0f,		0.0f,1.0f,0.0f,		0.0f,1.0f
	//};

	float vertices[] = {
	//第一个面,六个顶点两个三角形
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	 0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
	-0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
	-0.5f,  0.5f, -0.5f,  0.0f, 1.0f
	};

	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	//之后的VBO便属于了VAO的管理范围
	glGenBuffers(1, &VBO);
	//绑定哪一种buffer, 
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(sizeof(float)*3));
	
	//打开锚点:激活
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

	//解绑
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}

//
void initShader(const char* _vertexPath, const char* _fragPath) {
	
	_shader.initShader(_vertexPath, _fragPath);
}

void initTexture() {
	_pImage = ffImage::readFromFile("res/wall.jpg");
	//纹理绑定
	glGenTextures(1, &_texture);
	glBindTexture(GL_TEXTURE_2D, _texture);
	//纹理属性设置
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	//读入图片数据
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _pImage->getWidth(), _pImage->getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE,_pImage->getData());
}

结果:

 

构建摄像机类——实现控制摄像机旋转

摄像机front向量的xyz

front.y:

front.x 和 front.z:

计算方式:

鼠标跟踪控制方法分解 

摄像机旋转功能实现

  • 在主函数中设置鼠标模式,同时绑定回调函数
  • 在摄像机类中添加相应属性及方法
  • 根据之前的理论,编写代码计算个方向向量,并归一化
  • 在鼠标移动时,计算pitch和yaw角度的变化
//Camera.cpp
#include "Camera.h"

void Camera::lookAt(glm::vec3 _pos, glm::vec3 _front, glm::vec3 _up)
{
	m_position = _pos;
	m_front = glm::normalize(_front);
	m_up = _up;
	m_vMatrix = glm::lookAt(m_position, m_position + m_front, m_up);
}

void Camera::update()
{
	m_vMatrix = glm::lookAt(m_position, m_position + m_front, m_up);
}

void Camera::move(CAMERA_MOVE _mode)
{
	switch (_mode)
	{
	case CAMERA_MOVE::MOVE_LEFT:
		m_position -= glm::normalize(glm::cross(m_front, m_up)) * m_speed;
		break;
	case CAMERA_MOVE::MOVE_RIGHT:
		m_position += glm::normalize(glm::cross(m_front, m_up)) * m_speed;
		break;
	case CAMERA_MOVE::MOVE_FRONT:
		m_position += m_speed * m_front;
		break;
	case CAMERA_MOVE::MOVE_BACK:
		m_position -= m_speed * m_front;
		break;
	default:
		break;
	}
}

void Camera::setspeed(float _speed)
{
	m_speed = _speed;
}

void Camera::pitch(float _yOffset)
{
	m_pitch += _yOffset * m_sensitivity;
	if (m_pitch >= 89.0f) {
		m_pitch = 89.0f;
	}
	if (m_pitch <= -89.0f) {
		m_pitch = -89.0f;
	}
	m_front.y = sin(glm::radians(m_pitch));
	m_front.x = cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
	m_front.z = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));

	m_front = glm::normalize(m_front);
	update();
}

void Camera::yaw(float _xOffset)
{
	m_yaw += _xOffset * m_sensitivity;

	m_front.y = sin(glm::radians(m_pitch));
	m_front.x = cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
	m_front.z = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));

	m_front = glm::normalize(m_front);
	update();
}

void Camera::setSensitivity(float _s)
{
	m_sensitivity = _s;
}

void Camera::onMouseMove(double _xpos, double _ypos)
{
	if (m_firseMove) {
		m_xpos = m_xpos;
		m_ypos = m_ypos;
		m_firseMove = false;
		return;
	}
	float _xOffset = _xpos - m_xpos;
	float _yOffset = -(_ypos - m_ypos);

	m_xpos = _xpos;
	m_ypos = _ypos;

	pitch(_yOffset);
	yaw(_xOffset);
}

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

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

相关文章

全屋智能--智慧家庭新风向

全屋智能是指综合物联网、云计算和人工智能等技术&#xff0c;对该空间场所内的家居设备进行系统化集中管理&#xff0c;并赋予其人与场景交互能力&#xff0c;成为用户看不见的生活管家。全屋智能的核心价值在于自主感知、自主决策、自主控制、自主反馈的生命力。 全屋智能目前…

图床云存储项目课程随堂笔记

这个项目属实重量级&#xff0c;第一遍学习的过程比较懵&#xff0c;只是记录一下随堂笔记。后面理解了项目后端代码流程&#xff0c;再细写几篇博客归纳。 基本单机环境配置 大的项目你可能连配置都配不清楚。 新手必须要心细&#xff0c;因为你错了一步&#xff0c;想要纠…

网站被大量cc攻击导致打不开怎么解决

家好&#xff0c;今天小蚁君给大家分享一个昨天接入我们防护的客户&#xff0c;说下这个客户特点&#xff0c; 网站业务&#xff0c;由于源服务器是在阿里云&#xff0c;防护阈值很低&#xff0c;基本上是无防御的&#xff0c;随便压测一下就死&#xff0c;通过朋友介绍过来&am…

44个微服务架构设计模式你还不学吗,大佬都汇总好了,只能说太详细了!

前言 微服务架构已成为现代应用程序开发的事实上的选择。虽然它解决了某些问题&#xff0c;但它不是灵丹妙药。它有几个缺点&#xff0c;在使用这种架构时&#xff0c;必须解决许多问题。这就需要学习这些问题中的常见模式并用可重用的解决方案来解决它们。因此&#xff0c;需…

若依框架的使用

目录 1.为什么使用若依框架 2.环境搭建 2.1下载代码 2.2 系统配置 1.为什么使用若依框架 若依是一个快速开发框架&#xff0c;基于springboot&#xff0c;集成了mybatis&#xff0c;spring security&#xff0c;redis等常用的框架&#xff0c;支持多语言&#xff0c;已经实…

“闭关修炼”这么久,吃透这些“微服务”笔记,足够面试涨10K

01 SpringBoot 1.1 面试相关 &#xff08;1&#xff09;SpringBoot高频&#xff1a;35个知识点&#xff08;附解析&#xff09; Spring Boot、Spring MVC 和 Spring 有什么区别&#xff1f; 什么是自动配置&#xff1f; 什么是 Spring Boot Stater &#xff1f; 你能否举一…

Java代码审计——URL 跳转漏洞

目录 &#xff08;一&#xff09;URL 跳转漏洞简介 &#xff08;二&#xff09;URL 重定向 1&#xff0e;通过 ModelAndView 方式 2&#xff0e;通过返回 String 方式 3&#xff0e;使用 sendRedirect 方式 4&#xff0e;使用 RedirectAttributes 方式 5&#xff0e;通过…

elastic 概述

引言 1.1 海量数据 在海量数据中执行搜索功能时&#xff0c;如果使用MySQL&#xff0c;效率太低。 1.2 全文检索 在海量数据中执行搜索功能时&#xff0c;如果使用MySQL&#xff0c;效率太低。 1.3 高亮显示 将搜索关键字&#xff0c;以红色的字体展示。 ES概述 ES 也是一个n…

加锁和解锁-ReentrantLock详解-AQS-并发编程(Java)

文章目录1 AQS1.1 概念1.2 两种锁机制1.3 公平锁和非公平锁1.3 锁竞争1.4 条件变量2 ReentrantLock2.1 简介2 加锁2.1 加锁成功2.2 加锁失败2.2.1 tryAcquire()2.2.2 addWaiter()2.2.3 acquireQueued()2.2.3.1 主方法2.2.3.2 shouldParkAfterFailedAcquire()2.2.3.3 parkAndChe…

LeetCode 84.柱状图中最大的矩形

今天还是分享一道才刷过的题目&#xff0c; 柱状图中最大的矩形&#xff0c;这道题根上一篇我分享的接雨水类似&#xff0c;都是可以用双指针&#xff0c;动态规划(双指针加备忘录)&#xff0c;单调栈来算 这道题的话三种方法都写了&#xff0c;双指针会超时&#xff0c;优化一…

pdf生成:puppeteer

一、Puppeteer Puppeteer是Google Chrome团队出品的一款无界面Chrome工具&#xff0c;它提供了丰富的API&#xff0c;让开发者像鼠标一样控制浏览器的各种行为。Puppeteer是一个Node库&#xff0c;提供发了一个高级API来通过DevTools协议控制Chromium或Chrome。Puppeteer默认以…

SDN实战团技术分享(三十八):DPDK助力NFV与云计算

DPDK最初动机很简单&#xff0c;网络处理器的软件解决方案&#xff0c;证明IA多核处理器能够支撑高性能数据包处理。 什么是DPDK&#xff1f;对于用户来说&#xff0c;它可能是一个出色的包数据处理性能加速软件库&#xff1b;对于开发者来说&#xff0c;它可能是一个实践包处…

配置鼠标右键edit with notepad

注&#xff1a;notepad为一个轻量级的代码文本编辑器&#xff0c;还可以安装代码对比工具等&#xff0c;大大提供办公效率&#xff0c;十分方便。安装后&#xff0c;可能右键无法直接用notepad打开&#xff0c;需要在软件中&#xff0c;选择文件打开&#xff0c;本文介绍如果设…

内网Windows Git Server部署

疫情下&#xff0c;公司与家用电脑切换&#xff0c;导致代码更新接不上&#xff0c;最最最重要公司代码不能上传外网&#xff0c;因此内网 Git Server部署这稿子。 Server部署主要安装【Java、Git、TortoiseGit、Gitblit】前三为傻瓜安装&#xff0c;重点为Gitblit配置修改及部…

Flutter高仿微信-第36篇-单聊-语音通话

Flutter高仿微信系列共59篇&#xff0c;从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。 详情请查看 效果图&#xff1a; 目前市场上第三方音频接口的价格高的吓人 语音通话价格&#xff1a; 5元/千分钟…

大规模ddos攻击事件,ddos攻击会暴露ip吗

1月4日&#xff0c;谷歌云安全可靠性工程师Damian Menscher在推特上表示&#xff0c;根据CVE-2021-22205漏洞利用报告&#xff0c;有攻击者正在利用 GitLab 托管服务器上的安全漏洞来构建僵尸网络&#xff0c;并发起规模惊人的分布式拒绝服务攻击&#xff08;DDoS&#xff09;其…

产品新人必看:入职前的准备及快速适应产品工作

​我第一次做分享&#xff0c;没有什么经验&#xff0c;不知道能不能讲好。 我先自我介绍一下&#xff0c;我是从UED转产品的&#xff0c;我现在这家公司已经快干了一年了&#xff0c;我是去年的5月份入职的。 求职期间陪学也帮了我很多忙&#xff0c;我基本上是全天有什么问…

线上课和线下课各自优缺点,PLC工程师进阶上位机应该知道

先说线上课优点&#xff1a; 价格较低&#xff0c; 视频学习&#xff0c;可以随时随地学习&#xff0c;不用辞职学习&#xff0c;降低职业风险 缺点&#xff1a; 没有学习氛围&#xff0c;对于自制能力差的同学&#xff0c;很难坚持下去 没有老师指点&#xff0c;一旦遇到问题很…

利用Amber热力学积分计算相对自由能变化

上周四&#xff0c;何博士为大家在北鲲云的直播间分享了Amber热力学积分计算相对自由能变化&#xff08;直播回放可在视频号&#xff1a;北鲲云-直播回放中查看&#xff09;。 直播结束后有很多小伙伴来向我们要PPT资料&#xff0c;这里何博士也为大家准备了文字版本的教程。将…

企业实战!基于Harbor搭建企业镜像仓库

企业实战&#xff01;基于Harbor搭建企业镜像仓库 虽然Docker官方提供了Docker Hub作为公共的Registry服务器&#xff0c;给到用户进行镜像的保存和管理工作。但对于企业而言&#xff0c;考虑到安全性和网络效率等原因&#xff0c;通常会搭建私有的Registry服务器&#xff0c;用…