OpenGL learning records (seven)

References:
https://learnopengl.com/
https://learnopengl-cn.github.io/

Added first person camera system.

Create a camera class, the .h file is as follows:

#pragma once

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class Camera
{
    
    
public:
	Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup);
	Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup);

	//相机参数
	glm::vec3 Position;
	glm::vec3 Forward;
	glm::vec3 Right;
	glm::vec3 Up;
	glm::vec3 WorldUp;
	//欧拉角
	float Pitch;
	float Yaw;

	float SenseX = 0.01f; //移动系数
	float SenseY = 0.01f; //移动系数
	float SpeedZ = 0; //前后移动速度

	glm::mat4 GetViewMatrix(); //计算观察矩阵

	void ProcessMouseMovement(float deltaX, float deltaY); //鼠标控制的移动
	void UpdateCameraPos(); //更新相机位置,WS控制

private:
	void UpdateCameraVecors(); //更新参数
	
};

Overload the constructor, so that the state of a camera can be determined by the initial position, the target (the camera orientation can be obtained by subtracting the position from the position), and the upward vector in the world space, or the initial target can be replaced by pitch and yaw. Pull corners to construct. Then set the parameters that may be used, which are the position of the camera, the forward vector, the right vector
, the up vector, and the up vector in the world coordinate system, and the two Euler angles to be used to control the movement of the camera. Coefficients, and several methods are declared, which are to calculate the observation matrix, mouse-controlled movement, update camera position, and update parameters. By the way, a camera can be defined with the following information:

insert image description here
The .cpp file is as follows:

#include "Camera.h"

//构造,根据位置、初始目标、世界空间上方向向量
Camera::Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup)
{
    
    
	Position = position;
	WorldUp = worldup;
	Forward = glm::normalize(target - position); //目标位置减相机位置得到前向向量
	Right = glm::normalize(glm::cross(WorldUp, Forward)); //世界坐标系中上向量和自身前向量叉乘得到自身右向量
	Up = glm::normalize(glm::cross(Right,Forward)); //自身右向量和自身前向量叉乘得到自身前向量
}

//构造,根据位置、pitch和yaw两个欧拉角、世界空间上方向向量
Camera::Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup)
{
    
    
	Position = position;
	WorldUp = worldup;
	Pitch = pitch;
	Yaw = yaw;
	//根据pitch和yaw构造前向向量
	Forward.x = glm::cos(Pitch) * glm::sin(Yaw);
	Forward.y = glm::sin(Pitch);
	Forward.z = glm::cos(Pitch) * glm::cos(Yaw);
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	Up = glm::normalize(glm::cross(Right, Forward));
}

glm::mat4 Camera::GetViewMatrix()
{
    
    
	//LookAt函数需要一个位置、目标和上向量,创建观察矩阵
	return glm::lookAt(Position, Position + Forward, WorldUp);
}

void Camera::ProcessMouseMovement(float deltaX, float deltaY)
{
    
    
	//传入x、y的位移量,对相机自身Pitch、Yaw修改,并更新参数
	Pitch -= deltaY * SenseX;
	Yaw -= deltaX * SenseY;
	UpdateCameraVecors();
}

void Camera::UpdateCameraPos()
{
    
    
	//使Forward向量根据SpeedZ变化,达到前进后退的效果
	Position += Forward * SpeedZ * 0.1f;
}

void Camera::UpdateCameraVecors()
{
    
    
	//更新参数
	Forward.x = glm::cos(Pitch) * glm::sin(Yaw);
	Forward.y = glm::sin(Pitch);
	Forward.z = glm::cos(Pitch) * glm::cos(Yaw);
	Right = glm::normalize(glm::cross(WorldUp, Forward));
	Up = glm::normalize(glm::cross(Right, Forward));
}

Among them, the constructor receives the position, the initial target, and the direction vector in the world space. Position and WorldUp can be directly assigned, and the forward vector is obtained by subtracting the camera position from the target position. Regarding the calculation of its own right vector, the cross product of the up vector and its own front vector in the world coordinate system obtains a right vector perpendicular to the plane composed of these two vectors, which will not be affected by its own up direction vector, because its own up direction vector No matter how it is changed, it is still in the composed plane, and the obtained cross product vector is the same. Use this resulting vector as its right vector. Then the self-front vector is obtained according to the cross product of the self-right vector and the self-front vector. In another overloaded constructor, except for constructing the forward vector based on pitch and yaw, the rest of the steps are the same. In the GetViewMatrix() method, through the lookAt function provided by GLM, a camera position, a target position and a vector representing the up vector in the world space are passed in, and then GLM will create a LookAt matrix, which we can use as our observation matrix. The ProcessMouseMovement method receives the variation in two directions, and subtracts the variation from pitch and yaw to change the camera orientation. A coefficient is used to control the steering range, and then a wave of parameters is updated. The UpdateCameraPos method similarly makes the Forward vector change according to SpeedZ to achieve the effect of forward and backward. The UpdateCameraVecors method is used to update the parameters. Due to the possible changes of Pitch and Yaw, the values ​​of each parameter are recalculated.
After the camera class is written, check the input function in the main file and add the corresponding button response to change the value of SpeedZ:

//检查输入函数
void processInput(GLFWwindow* window)
{
    
    
	//按下ESC键时,将WindowShouldClose设为true,循环绘制将停止
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
	{
    
    
		glfwSetWindowShouldClose(window, true);
	}
	//按下W键时,向前移动相机
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
	{
    
    
		camera.SpeedZ = 1.0f;
	}
	//按下S键时,向后移动相机
	else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
	{
    
    
		camera.SpeedZ = -1.0f;
	}
	else
	{
    
    
		camera.SpeedZ = 0;
	}
}

We also need to tell GLFW that it should hide the cursor and capture it, using the following statement:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

After calling this function, no matter how we move the mouse, the cursor will not be displayed, nor will it leave the window.

In order to calculate the pitch and yaw angles, we need to make GLFW listen for mouse movement events. GLFW provides us with such a callback function, which needs to be input according to the parameters specified by it. Before that, declare the variables lastX and lastY to save the mouse input at the last moment, and a bool type variable to judge whether it is the first input. What the function body needs to do is to first judge whether it is the first input, and if so, set the X and Y values ​​at the previous moment to the current mouse values. The initial value of the mouse input variable at the moment is 0, which prevents the camera from teleporting due to the large gap between the two values ​​at the next moment. Next, define two float variables deltaX and deltaY, subtract the value of the mouse position from the current mouse value to obtain the movement amount of the mouse in the xy direction at this moment, and use these two movement amounts to set the camera ProcessMouseMovement method parameters, and then modify the camera's own parameters to achieve the effect of camera steering.

//上一时刻的鼠标输入
float lastX;
float lastY;
bool firstMouse = true; //是否为第一次输入
//GLFW监听鼠标移动事件回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    
    
	if (firstMouse == true)
	{
    
    
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}

	float deltaX, deltaY;
	deltaX = xpos - lastX;
	deltaY = ypos - lastY;

	lastX = xpos;
	lastY = ypos;
	camera.ProcessMouseMovement(deltaX, deltaY);
}

The rest is to create a camera object and call the camera UpdateCameraPos method at the end of the loop drawing phase to change the Forward vector in the camera to realize the function of moving the camera back and forth. The variables that affect this movement have been set in the key detection phase. The method of using the created camera object is mainly used to set the observation matrix. The observation matrix is ​​established through viewMat = camera.GetViewMatrix(), which in turn affects the display of the object on the screen. Therefore, this method is also called in the loop drawing phase.

The current main file code is as follows:

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Camera.h"

//顶点数据
//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,   1.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
};

//顶点索引
unsigned int indices[] = {
    
    
	0, 1, 2,   //第一个三角形使用的顶点
	2, 3, 0    //第二个三角形使用的顶点
};

//立方体位置
glm::vec3 cubePositions[] = {
    
    
  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)
};

//上一时刻的鼠标输入
float lastX;
float lastY;
bool firstMouse = true; //是否为第一次输入

//相机类
//Camera camera(glm::vec3(0, 0, 3.0f), glm::vec3(0, 0, 0), glm::vec3(0, 1.0f, 0));
Camera camera(glm::vec3(0, 0, 3.0f), glm::radians(-15.0f), glm::radians(180.0f), glm::vec3(0, 1.0, 0));

//检查输入函数
void processInput(GLFWwindow* window)
{
    
    
	//按下ESC键时,将WindowShouldClose设为true,循环绘制将停止
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
	{
    
    
		glfwSetWindowShouldClose(window, true);
	}
	//按下W键时,向前移动相机
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
	{
    
    
		camera.SpeedZ = 1.0f;
	}
	//按下S键时,向后移动相机
	else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
	{
    
    
		camera.SpeedZ = -1.0f;
	}
	else
	{
    
    
		camera.SpeedZ = 0;
	}
}

//视口改变时的回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    
    
	glViewport(0, 0, width, height); //OpenGL渲染窗口的尺寸大小
}

//GLFW监听鼠标移动事件回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    
    
	if (firstMouse == true)
	{
    
    
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}

	float deltaX, deltaY;
	deltaX = xpos - lastX;
	deltaY = ypos - lastY;

	lastX = xpos;
	lastY = ypos;
	camera.ProcessMouseMovement(deltaX, deltaY);
}

int main()
{
    
    
	glfwInit(); //初始化GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //告诉GLFW要使用OpenGL的版本号
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //主版本号、次版本号都为3,即3.3版本
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //告诉GLFW使用核心模式(Core-profile)

	//打开 GLFW Window
	GLFWwindow* window = glfwCreateWindow(1600, 1200, "My OpenGL Game", nullptr, nullptr);
	if (window == nullptr) //若窗口创建失败,打印错误信息,终止GLFW并return -1
	{
    
    
		printf("Open window failed.");
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window); //创建OpenGL上下文
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //用户改变窗口大小的时候,视口调用回调函数
	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); //隐藏光标
	glfwSetCursorPosCallback(window, mouse_callback);

	//初始化GLEW
	glewExperimental = true;
	if (glewInit() != GLEW_OK) //若GLEW初始化失败,打印错误信息并终止GLFW窗口
	{
    
    
		printf("Init GLEW failed.");
		glfwTerminate();
		return -1;
	}

	glEnable(GL_DEPTH_TEST);


	Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");

	//创建VAO(顶点数组对象)
	unsigned int VAO;
	glGenVertexArrays(1, &VAO); //生成一个顶点数组对象
	glBindVertexArray(VAO); //绑定VAO

	//创建VBO(顶点缓冲对象)
	unsigned int VBO;
	glGenBuffers(1, &VBO); //生成缓冲区对象。第一个参数是要生成的缓冲对象的数量,第二个是要输入用来存储缓冲对象名称的数组,由于只需创建一个VBO,因此不需要用数组形式
	glBindBuffer(GL_ARRAY_BUFFER, VBO); //把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上,GL_ARRAY_BUFFER是一种顶点缓冲对象的缓冲类型。OpenGL允许同时绑定多个缓冲,只要它们是不同的缓冲类型。
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //把定义的数据复制到当前绑定缓冲的函数。参数1:目标缓冲类型,参数2:指定传输数据的大小(以字节为单位),参数3:我们希望发送的实际数据,参数4:指定显卡如何管理给定的数据,GL_STATIC_DRAW表示数据不会或几乎不会改变。

	//创建EBO(元素缓冲对象/索引缓冲对象)
	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);


	//位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); //告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)。参数1:要配置的顶点属性(layout(location = 0),故0),参数2:指定顶点属性的大小(vec3,故3),参数3:指定数据的类型,参数4:是否希望数据被标准化,参数5:在连续的顶点属性组之间的间隔,参数6:表示位置数据在缓冲中起始位置的偏移量(Offset)。
	glEnableVertexAttribArray(0); //以顶点属性位置值作为参数,启用顶点属性,由于前面声明了layout(location = 0),故为0

	//颜色属性
	//glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	//glEnableVertexAttribArray(1);

	//UV属性
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(2);

	//使用纹理单元0绑定TexBufferA
	unsigned int TexBufferA;
	glGenTextures(1, &TexBufferA);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TexBufferA);

	int width, height, nrChannel;
	stbi_set_flip_vertically_on_load(true); //翻转图像y轴

	//加载并生成第一张纹理
	unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannel, 0);
	if (data)
	{
    
    
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
    
    
		std::cout << "图片加载失败" << std::endl;
	}
	stbi_image_free(data); //释放

	//使用纹理单元3绑定TexBufferB
	unsigned int TexBufferB;
	glGenTextures(1, &TexBufferB);
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, TexBufferB);

	//加载并生成第二张纹理
	unsigned char* data2 = stbi_load("awesomeface1.png", &width, &height, &nrChannel, 0);
	if (data2)
	{
    
    
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
    
    
		std::cout << "图片加载失败" << std::endl;
	}
	stbi_image_free(data2);

	//变换矩阵
	glm::mat4 trans;
	//trans = glm::translate(trans, glm::vec3(0.3f, 0.3f, 0.2f)); //位移
	//trans = glm::rotate(trans, glm::radians(45.0f), glm::vec3(0, 0, 1.0f)); //旋转
	//trans = glm::scale(trans, glm::vec3(0.5f, 0.5f, 0.5f)); //缩放

	//Model矩阵
	glm::mat4 modelMat;
	modelMat = glm::rotate(modelMat, glm::radians(-55.0f), glm::vec3(1.0f, 0, 0));

	//View矩阵
	glm::mat4 viewMat;
	//viewMat = glm::translate(viewMat, glm::vec3(0, 0, -3.0f));

	//Projection矩阵
	glm::mat4 projMat;
	projMat = glm::perspective(glm::radians(45.0f), 1600.0f / 1200.0f, 0.1f, 100.0f);

	//让程序在手动关闭之前不断绘制图像
	while (!glfwWindowShouldClose(window))
	{
    
    
		//检测输入
		processInput(window);

		//渲染指令
		glClearColor(0.f, 0.5f, 0.5f, 1.0f); //设置清空屏幕所用的颜色
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空屏幕的颜色缓冲区和深度缓冲区

		//绑定纹理到对应的纹理单元
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, TexBufferA);
		glActiveTexture(GL_TEXTURE3);
		glBindTexture(GL_TEXTURE_2D, TexBufferB);

		glBindVertexArray(VAO); //绘制物体的时候就拿出相应的VAO,绑定它
		//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //绑定EBO

		myShader->use();

		viewMat = camera.GetViewMatrix();

		//循环绘制10个物体
		for (int i = 0; i < 10; i++)
		{
    
    
			//每个物体的模型矩阵
			glm::mat4 modelMat2;
			modelMat2 = glm::translate(modelMat2, cubePositions[i]);
			float angle = 20.0f * i;
			modelMat2 = glm::rotate(modelMat2, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
			

			//告诉OpenGL每个着色器采样器属于哪个纹理单元
			glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 0);
			glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 3);

			//glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans)); //把矩阵数据发送给着色器
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat2)); //把矩阵数据发送给着色器
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));


			glDrawArrays(GL_TRIANGLES, 0, 36);
			//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //绘制,参数1:绘制模式(三角形),参数2:绘制顶点数(两个三角形6个顶点),参数3:索引的类型,参数4:指定EBO中的偏移量。
		}

		


		//检查并调用事件,交换缓冲区
		glfwSwapBuffers(window); //交换颜色缓冲区,前缓冲区保存最终输出的图像,后缓冲区负责绘制渲染指令,当渲染指令执行完毕后,交换前后缓冲区,使完整图像呈现出来,避免逐像素绘制图案时的割裂感
		glfwPollEvents(); //检查触发事件,如键盘输入、鼠标移动等
		camera.UpdateCameraPos();
	}


	glfwTerminate(); //关闭GLFW并退出
	return 0;
}

Guess you like

Origin blog.csdn.net/weixin_47260762/article/details/128223955