OpenGL变换矩阵和输入控制

news2025/1/5 7:09:09

        在前面的文章当中我们已经成功播放了动画,让我们的角色动了起来,这一切变得比较有意思了起来。不过我们发现,角色虽然说是动了起来,不过只是在不停地原地踏步而已,而且我们也没有办法通过键盘来控制这个角色来进行移动,现在整个项目处在一个只能看不能玩的处境。那么这篇文章,笔者将为大家介绍如何通过键盘输入来控制我们绘制的角色。

输入事件

        看过windows编程的朋友们应该是知道的,当我们的键盘上面的某一个按键被按下,键盘就会给系统发送一个信号,系统也会接受到这个信号,不过到底要怎么处理这个信号由具体的程序来决定。在这个案例中我们只处理键盘上的WASD按键的信号(不区分大小写),我们整个案例使用的试glfw框架,该框架给我们提供的一些接口让我们来处理这些信号。下面我们就来看一下代码

void InputProcess(GLFWwindow* window) {
    //dirX,dirY,sameDirect,moveDirect都是全局变量
    //当键盘上的 W 被按下时如何做出的处理
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		dirX = 0, dirY = 1;
    //当键盘上的 S 被按下时如何做出的处理
	else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		dirX = 0, dirY = -1;
    //当键盘上的 A 被按下时如何做出的处理
	else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		dirX = -1, dirY = 0;
    //当键盘上的 D 被按下时如何做出的处理
	else if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		dirX = 1, dirY = 0;
	else
		dirX = 0, dirY = 0;
    //当输入的方向和当前移动的方向不同时我们需要将整个图形进行一次翻转
	sameDirect = dirX * moveDirect < 0 ? false : true;
	if (dirX != 0)
		moveDirect = dirX < 0 ? -1 : 1;
	else
		moveDirect = moveDirect;
	
}

我们可以看到处理输入特定按键输入的函数是glfwGetKey()GLFW_PRESS表示这个按键被按下,这里我们规定沿着X轴的右边的方向是正方向,沿Y轴的上边的方向是正方向,至于整个函数的逻辑,相信并不难理解。至于还想了解更多的输入事件,比如说鼠标输入等,可以在编辑器上选中GLFW_KEY_S,然后按下F12键,就可以跳转到对应的程序文件,进行翻找查看,找到对应的宏定义之后依然可以使用glfwGetKey()进行处理(这里笔者同样建议读者学会去阅读源代码,对自己的编程能力会有较大的提升)。鼠标输入宏定义如下

#define GLFW_MOUSE_BUTTON_1         0
#define GLFW_MOUSE_BUTTON_2         1
#define GLFW_MOUSE_BUTTON_3         2
#define GLFW_MOUSE_BUTTON_4         3
#define GLFW_MOUSE_BUTTON_5         4
#define GLFW_MOUSE_BUTTON_6         5
#define GLFW_MOUSE_BUTTON_7         6
#define GLFW_MOUSE_BUTTON_8         7
#define GLFW_MOUSE_BUTTON_LAST      GLFW_MOUSE_BUTTON_8
#define GLFW_MOUSE_BUTTON_LEFT      GLFW_MOUSE_BUTTON_1
#define GLFW_MOUSE_BUTTON_RIGHT     GLFW_MOUSE_BUTTON_2
#define GLFW_MOUSE_BUTTON_MIDDLE    GLFW_MOUSE_BUTTON_3

上面就是输入事件,非常的简单,有了输入事件我们会序就可以控制自己的角色了,下面的矩阵变换才是重头戏,因为它设计到了线性代数的知识。

变换矩阵

        按照现代图形软件的设计思路来讲,图形要经过模型矩阵,观察矩阵,投影矩阵(model_matrix \rightarrow view_matrix \rightarrow project_matrix)。

模型矩阵

        可以理解为我们创建一个图形时各个顶点相对于世界原点的坐标,我们在最开始输入的坐标位置都是相对于世界原点坐标而言的,每个顶点的坐标就是一个就是一个模型矩阵,可能有些朋友们会比较疑惑,为什么不把整个图形作为一个模型矩阵了?这里请读者们记住一个原则,计算机图形当中的所有图形都是由顶点构成,我们通过设定对应的图元,以及绘制顺序,来告诉计算机这些顶点将要绘制什么样的图形。无论我们对模型矩阵进行任何平移,旋转,缩放的操作都是在对顶点进行操作。

平移矩阵

我们可以看到,右边的坐标与左边的平移矩阵相乘,各个分量就加上了平移的分量。可能有的朋友又有疑问了,为什么这个点的坐标有四个分量,三维的空间坐标不是应该只有三个向量吗?其实我们在数学上管第四个分量叫作齐次量,它的存在有更多数学意义,这里就不展开讲了,要不然就扯得实在太远了,它在这里作用就是就是给每个分量加上对应平移量,读者可以将第四个分量写作0试一下,两个矩阵相乘过后你就会发现这个平移矩阵没有起到任何得作用。所以我们一般把顶点的第四个分量写作1,向量的第四个分量写作0,以为向量的平移是没有数学意义的。

旋转矩阵

        旋转的话,我们一般会将让该物体绕着某一个轴进行旋转,就比如说\vec{v}绕Z轴旋转angle°得到向量\vec{k}(顶点的话也没有问题,因为第四个分量并没有参与到运算当中)

 旋转矩阵具体如下

 \theta就是想要旋转的角度我不过欧拉旋转阵,有个很致命的问题,那就是万象死锁问题,有兴趣去了解的话,大家可以自己去查阅一些相关的资料。(如果制作的是一个2D游戏的话不需要担心这个问题)

缩放矩阵

如果我们把缩放变量表示为(S1,S2,S3)我们可以为任意向量(x,y,z)定义一个缩放矩阵:

S1,S2,S3各个分量大于1,就是对相对应的分量进行放大,小于1就是缩小。

对模型进行变换的矩阵就这么三种,只是这三种就可以组合出我们想要的所有的变化,可能有很多读者并不相信,可这就是事实。数学就是这么神奇!

观察矩阵

        观察矩阵,也可以叫作相机矩阵。不过这个案例相机矩阵的效果不是很明显,因为我们的模型是平面的,就算移动相机也看不出什么效果,到时候笔者单独做一个案例来讲这个东西。所以在这个案例单中我们将它设置为一个单位阵E

投影矩阵

        简言之就是将三位空间当中的物体投影到我们屏幕上来。没错虽然我们做的案例是个二维平面的图形,但是他是实打实在3D空间当中。投影的方式有两种,正交投影和透视投影,透视投影就和我们生活中看到的物体同样的效果,满足近大远小的规律。正交投影将没有这种效果,它无论距离屏幕有多远,投影到屏幕上是一样的大小。

正交投影

        正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器:

         上面的平截头体定义了可见的坐标,它由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;如果w分量等于1.0,透视除法则不会改变这个坐标。

透视投影

        如果你曾经体验过实际生活给你带来的景象,你就会注意到离你越远的东西看起来更小。这个奇怪的效果称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样:

         正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-ww的范围之间(任何大于这个范围的坐标都会被裁剪掉)。OpenGL要求所有可见的坐标都落在-1.0到1.0范围内。

变换矩阵的实际应用

        相信很多读者都看过Games101的课,课上面老师只讲了原理,并没有告诉朋友具体的代码应该怎么写,要求观众自行学习。不过我们都知道理论向实践落地那是有一道巨大的鸿沟需要去跨越的,所以笔者将会用上面的知识来告诉大家如何让我们屏幕中的角色在屏幕上进行移动。

准备数学库

        OpenGL有一个专门的数学库glm,让我们去手写这些矩阵乘法非常的耗费时间,而且代码的效率不高,所以我们在案例当中glm这个三方库。

glm github官方网址https://github.com/g-truc/glm

如果上面这个网站打不开,可以到这个网站去找glm库https://gitee.com/HonyOrange_227/OpenGLProjectInitial

把这个项目下载或者克隆下来,在下面这个路径就可找到glm库了,该文章当中所有需要使用到的代码都在这个项目当中,甚至美术素材,那只跑动的青蛙也在这个项目当中。

将整个文件,复制自己项目下面就可以了,记得配置对应的路劲。

使用投影矩阵

        因为这个项目我们创建的都是2D平面元素,所以这里我打算使用正交投影。具体代码如下:

//原点到底部和顶部的距离依然设置是-1,1 左右两边给去窗口的长宽比
glm::mat4 projection = glm::ortho(-viewPortSize.x/ viewPortSize.y, viewPortSize.x / viewPortSize.y, -1.0f, 1.0f);
//观察矩阵为单位阵
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 viewprojection = projection * view;

有的朋友注意到最后一行代码,笔者将两个矩阵乘了起来,没错从模型矩阵从观察矩阵到投影矩阵的方式就是矩阵相乘,可能读者还要问,为什么是project*view 不是 view*project 。这是因为OpenGL的矩阵都是列主序的矩阵,前面的数学公式当中我们可以看到,顶点坐标阵都是在变换矩阵的右边,大家要是感觉这很疑惑的话可以这样记,那个矩阵在公式上距离顶点阵最近它就最先发挥作用,这样是不是感觉好多了。

        有了这个矩阵过后,我们需要将它上传到着色器上,所以我们需要就着色器进行一些修改。具体修改内容如下:

TextureShader.glsl

layout(location = 1) out vec2 v_TexCoord;
// 增加该用户变量
uniform mat4 u_ViewProject;

void main()
{
	gl_Position = u_ViewProject * position;	
	v_TexCoord = texCoord;
};

Shader.h

//增加此函数
void UploadUniformat4(const std::string& name, glm::mat4& transform);

Shader.cpp

void Shader::UploadUniformat4(const std::string& name, glm::mat4& transform) {
	int location = glGetUniformLocation(m_ShaderID, name.c_str());
	glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(transform));
}

使用正交投影阵

ImGui::Begin("ViewPort");
viewPortSize = ImGui::GetContentRegionAvail();
if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {

    pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);
	glViewport(0, 0, viewPortSize.x, viewPortSize.y);
	glm::mat4 projection = glm::ortho(-viewPortSize.x/ viewPortSize.y, viewPortSize.x / viewPortSize.y, -1.0f, 1.0f);
	glm::mat4 view = glm::mat4(1.0f);
	glm::mat4 viewprojection = projection * view;
	pShader->UploadUniformat4("u_ViewProject", viewprojection);
}

可能有的朋友就还是不解,没有投影矩阵,我们不是一样能看到东西吗,这个投影阵存在的意义是什么。如果还没有使用投影阵的朋友可以去拖动一下自己窗口,你的青蛙会更跟窗口一起变宽变窄。就像下面情况一样

大家还记不记得,我们创建的青蛙长宽是一样的,接近一个正方形才对,我们在拖动窗口的时候,世界坐标的单位长度发生了变换(但是坐标对应的数值不会改变),所以青蛙就会一会被拉长,一会又被压扁,这种情况是不允许的,使用投影矩阵后就不会出现这样的状况。

使用变换矩阵

        变换矩阵,就是让青蛙移动起来的关键,这次我们让青蛙跟着我们的按键进行左右移动,使用到的变换矩阵如下

glm::mat4 transform =  glm::translate(glm::mat4(1.0f), glm::vec3(xPosition, yPosition, 0.0f))
* glm::rotate(glm::mat4(1.0f), glm::radians(rotateAngle), glm::vec3(0.0f, 1.0f, 0.0f)) 
* glm::scale(glm::mat4(1.0f), glm::vec3(0.5f, 0.5f, 0.5f));

第一个就是移动矩阵,第二个圆转矩阵(当青蛙向左边跑的时候,我们需要把青蛙转个向),第三个就是缩放矩阵。这里我们要注意的是平移移动要最后进行,看前面的公式我们也知道,旋转,缩放操作都是相对于世界中心原点来进行操作的,如果我先进行平移的话,得到的就不是我们想要的结果了。至于为什么平移矩阵写在第一个位置,我想我前说投影矩阵和观察矩阵时应该是给大家解释过了这是为什么了。

具体使用代码如下

glm::mat4 transform =  glm::translate(glm::mat4(1.0f), glm::vec3(xPosition, yPosition, 0.0f))
* glm::rotate(glm::mat4(1.0f), glm::radians(rotateAngle), glm::vec3(0.0f, 1.0f, 0.0f)) 
* glm::scale(glm::mat4(1.0f), glm::vec3(0.5f, 0.5f, 0.5f));
//原模型各个顶点的坐标	
glm::vec4 p1 = glm::vec4(-0.5f, -0.5f, 0.0f,1.0f);
glm::vec4 p2 = glm::vec4(0.5f, -0.5f, 0.0f, 1.0f);
glm::vec4 p3 = glm::vec4(0.5f, 0.5f, 0.0f, 1.0f);
glm::vec4 p4 = glm::vec4(-0.5f, 0.5f, 0.0f,1.0f);

p1 = transform * p1;
p2 = transform * p2;
p3 = transform * p3;
p4 = transform * p4;

//写入顶点缓冲区当中,在对应位置进行绘制
positions[0] = p1.x, positions[1] = p1.y, positions[2] = p1.z, positions[3] = p1.w;
positions[6] = p2.x, positions[7] = p2.y, positions[8] = p2.z, positions[9] = p2.w;
positions[12] = p3.x, positions[13] = p3.y, positions[14] = p3.z, positions[15] = p3.w;
positions[18] = p4.x, positions[19] = p4.y, positions[20] = p4.z, positions[21] = p4.w;

好了结合前面的键盘输入我们可以让这只青蛙跑起来了

 还是老样子,把主函数的代码给大家帖在这里,希望能帮助到大家。这一片和以前都不一样,非常的难,特别是数学这块。但是想要做出一款好的游戏,数学就是避不开的话题,就算是使用成熟的游戏引擎,数学这一块依然不能完全避免,希望大家能克服困难,做出自己心仪的好游戏。

#include<glad/glad.h>
#include<GLFW/glfw3.h>

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

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

#include"FrameBuffer.h"
#include"Shader.h"
#include"Texture.h"

static int dirX = 0,dirY = 0;
static int moveDirect = 1;
static bool sameDirect = true;

void InputProcess(GLFWwindow* window);

int main() {
	glfwInit();

	GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);

	glfwMakeContextCurrent(window);
	glfwSwapInterval(1); // Enable vsync

	// Setup Dear ImGui context
	IMGUI_CHECKVERSION();
	ImGui::CreateContext();
	ImGuiIO& io = ImGui::GetIO(); (void)io;
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
	io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;         // Enable Docking
	io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;       // Enable Multi-Viewport / Platform Windows
	//io.ConfigViewportsNoAutoMerge = true;
	//io.ConfigViewportsNoTaskBarIcon = true;
	
	// Setup Dear ImGui style
	ImGui::StyleColorsDark();
	//ImGui::StyleColorsLight();

	// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
	ImGuiStyle& style = ImGui::GetStyle();
	if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
	{
		style.WindowRounding = 0.0f;
		style.Colors[ImGuiCol_WindowBg].w = 1.0f;
	}

	// Setup Platform/Renderer backends
	ImGui_ImplGlfw_InitForOpenGL(window, true);
	ImGui_ImplOpenGL3_Init("#version 130");

	//需要初始化GLAD
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	float positions[] = {
		-0.5f,	-0.5f,	0.0f,1.0f,	0.0f,0.0f,
		0.5f,	-0.5f,	0.0f,1.0f,	1.0f,0.0f,
		0.5f,	0.5f,	0.0f,1.0f,	1.0f,1.0f,
		-0.5f,	0.5f,	0.0f,1.0f,	0.0f,1.0f
	};

	unsigned int vertexIndex[] = {
		0,1,2,
		2,3,0
	};

	GLuint buffer = 0;
	GLuint indexBuffer = 0;

	glCreateBuffers(1, &buffer);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(float), NULL);

	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (const void*)(4*sizeof(float)));

	glCreateBuffers(1, &indexBuffer);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertexIndex), vertexIndex, GL_STATIC_DRAW);

	bool show_demo_window = true;
	ImVec2 viewPortSize(640,480);
	float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};

	FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);
	Texture *pTexture = new Texture("assets/Textures/Run.png");
	Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");
	pShader->Bind();
	pShader->UploadUniform1i("u_Texture", 0);
	pShader->UBind();

	float textureWidth = pTexture->GetWidth();
	float unitActorWidth = 32.0f / textureWidth;
	int actorIndex = 0;
	float LastFrameTime = 0.0f;

	float moveSpeed = 0.015f;
	float stepTime = 0.0f, detalTime = 0.0f;

	float xPosition = 0.0f, yPosition = 0.0f;
	float rotateAngle = 0.0f;

	while (!glfwWindowShouldClose(window)) {
		float time = (float)glfwGetTime();
		detalTime = (time - stepTime) * 100.0f;
		stepTime = time;

		if (time - LastFrameTime > 0.05f) {
			LastFrameTime = time;
			actorIndex += 1;
			actorIndex = actorIndex % 11;
		}

		InputProcess(window);
		xPosition += dirX * moveSpeed * detalTime;
		yPosition += dirY * moveSpeed * detalTime;
		if (!sameDirect)
			rotateAngle += 180.0f;

		rotateAngle = rotateAngle == 360.0f ? 0.0f : rotateAngle;

		glm::mat4 transform =  glm::translate(glm::mat4(1.0f), glm::vec3(xPosition, yPosition, 0.0f))*glm::rotate(glm::mat4(1.0f), glm::radians(rotateAngle), glm::vec3(0.0f, 1.0f, 0.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(0.5f, 0.5f, 0.5f));
		
		glm::vec4 p1 = glm::vec4(-0.5f, -0.5f, 0.0f,1.0f);
		glm::vec4 p2 = glm::vec4(0.5f, -0.5f, 0.0f, 1.0f);
		glm::vec4 p3 = glm::vec4(0.5f, 0.5f, 0.0f, 1.0f);
		glm::vec4 p4 = glm::vec4(-0.5f, 0.5f, 0.0f,1.0f);

		p1 = transform * p1;
		p2 = transform * p2;
		p3 = transform * p3;
		p4 = transform * p4;

		positions[0] = p1.x, positions[1] = p1.y, positions[2] = p1.z, positions[3] = p1.w;
		positions[6] = p2.x, positions[7] = p2.y, positions[8] = p2.z, positions[9] = p2.w;
		positions[12] = p3.x, positions[13] = p3.y, positions[14] = p3.z, positions[15] = p3.w;
		positions[18] = p4.x, positions[19] = p4.y, positions[20] = p4.z, positions[21] = p4.w;
		
		positions[4] = unitActorWidth * actorIndex;
		positions[10] = unitActorWidth * (actorIndex + 1);
		positions[16] = unitActorWidth * (actorIndex + 1);
		positions[22] = unitActorWidth * actorIndex;

		glBindBuffer(GL_ARRAY_BUFFER, buffer);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), positions);

		pShader->Bind();
		pFrameBuffer->Bind();
		pTexture->Bind(0);
		glClear(GL_COLOR_BUFFER_BIT);
		glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_INT, NULL);
		pFrameBuffer->UBind();

		// Start the Dear ImGui frame
		ImGui_ImplOpenGL3_NewFrame();
		ImGui_ImplGlfw_NewFrame();
		ImGui::NewFrame();

		ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());

		ImGui::Begin("ViewPort");
		viewPortSize = ImGui::GetContentRegionAvail();
		if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {

			pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);
			glViewport(0, 0, viewPortSize.x, viewPortSize.y);
			glm::mat4 projection = glm::ortho(-viewPortSize.x/ viewPortSize.y, viewPortSize.x / viewPortSize.y, -1.0f, 1.0f);
			glm::mat4 view = glm::mat4(1.0f);
			glm::mat4 viewprojection = projection * view;
			pShader->UploadUniformat4("u_ViewProject", viewprojection);
		}
		uint32_t textureID = pFrameBuffer->GetColorAttachment();
		ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });
		ImGui::End();

		ImGui::Begin("ColorEditor");
		ImGui::ColorEdit4("##colorEditor", colorEditor);
		ImGui::End();

		/*if(show_demo_window)
			ImGui::ShowDemoWindow(&show_demo_window);*/

		// Rendering
		ImGui::Render();
		
		ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

		if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
		{
			GLFWwindow* backup_current_context = glfwGetCurrentContext();
			ImGui::UpdatePlatformWindows();
			ImGui::RenderPlatformWindowsDefault();
			glfwMakeContextCurrent(backup_current_context);
		}

		glfwSwapBuffers(window);

		glfwPollEvents();
	}

	// Cleanup
	ImGui_ImplOpenGL3_Shutdown();
	ImGui_ImplGlfw_Shutdown();
	ImGui::DestroyContext();

	delete pFrameBuffer;
	delete pShader;
	delete pTexture;

	glfwDestroyWindow(window);
	glfwTerminate();
}

void InputProcess(GLFWwindow* window) {
	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		dirX = 0, dirY = 1;
	else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		dirX = 0, dirY = -1;
	else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		dirX = -1, dirY = 0;
	else if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		dirX = 1, dirY = 0;
	else
		dirX = 0, dirY = 0;

	sameDirect = dirX * moveDirect < 0 ? false : true;
	if (dirX != 0)
		moveDirect = dirX < 0 ? -1 : 1;
	else
		moveDirect = moveDirect;
	
}

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

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

相关文章

【ArcGIS Pro】完整的nc文件整理表格模型构建流程及工具练习数据分享

学术科研啥的要用到很多数据&#xff0c;nc文件融合了时间空间数据是科研重要文件。之前分享过怎样将nc文件处理成栅格后整理成表格。小编的读者还是有跑不通整个流程的&#xff0c;再来做一篇总结篇&#xff0c;也分享下练习数据跟工具&#xff0c;如果还是弄不了的&#xff0…

linux-21 目录管理(一)mkdir命令,创建空目录

对linux而言&#xff0c;对一个系统管理来讲&#xff0c;最关键的还是文件管理。那所以我们接下来就来看看如何实现文件管理。当然&#xff0c;在文件管理之前&#xff0c;我们说过&#xff0c;文件通常都放在目录下&#xff0c;对吧&#xff1f;所以先了解目录&#xff0c;可能…

vulnhub jangow靶机

1.扫描靶机IP arp-scan -l如果扫不到靶机的话根据以下配置 启动时点击第二个 按回车 继续选择第二个 按e进入编辑 删除"recovery nomodeset" 在末尾添加"quiet splash rw init/bin/bash" Ctrlx 启动进入如下界面 passwd修改root密码 重启电脑登录root修…

惠普HP proliant DL380 G6服务器使用

惠普HP proliant DL380 G6服务器使用经历 前言 HP ProLiant DL380 G6是一款机架式服务器&#xff0c;标配1个Xeon E5504处理器。 已被列入“高耗能老旧通信设备淘汰指导目录” 配置 基本类别 类别 机架式 结构 2U 内存 内存类型 DDRIII 内存大小 4GB&#xff08;单条插槽…

java 斐波那契查找,涵盖原理、算法分析、实现细节、优缺点、应用场景等

一、定义 斐波那契查找&#xff08;Fibonacci Search&#xff09;是一种基于斐波那契数列的查找算法&#xff0c;适用于已排序的数组。它利用斐波那契数列的性质来减少比较次数&#xff0c;并且能够在某些条件下比二分查找更快。 更多优质资源推荐&#xff1a; http://sj.yso…

内网学习:工作组用户与权限

目录 一、本地用户组介绍本地工作组介绍用户与组的关系 二、四种用户类型及权限比较本地系统最高权限&#xff08;System账户&#xff09;特性Administrator与System账户的区别 本地最高管理员&#xff08;Administrator用户&#xff09;特性 本地普通管理员特性 本地普通用户特…

图片转成oled使用的字模数据

目录 oled尺寸 如何生成用到的图片 图片转字模 1.首先用Img2Lcd转成bmp单色图片 2.然后用PCtoLCD2002把单色图片转字模 oled尺寸 我使用0.96寸oled模块&#xff0c;对应着的分辨率是128*64&#xff0c;对应着宽高像素比128*64。所以不是随意一张图片就能用的&#xff0c;…

PTPVT 插值说明

文章目录 PTPVT 插值说明 PTPVT 插值说明PVT Hermite插值PVT 三次多项式插值PT 插值Sin轨迹测试结果PVT Hermite插值结果PVT 三次多项式插值结果PT 插值结果 用户轨迹测试结果PVT Hermite插值结果PT 插值结果 PTPVT 插值说明 PT模式&#xff1a; 位置-时间路径插值算法。 PVT模…

Mac安装多个版本node、java、python 等开发软件环境,安装、卸载、升级多个数据库

安装多个版本node、java、python 等开发软件环境 使用nvm&#xff08;Node.js Version Manager&#xff09;来管理多个Node.js版本。 使用jenv来管理多个Java版本。 使用pyenv来管理多个Python版本。 以下是安装和使用这些版本管理器的基本步骤&#xff1a; 1. 安装多个版本…

【深度学习】卷积网络代码实战ResNet

ResNet (Residual Network) 是由微软研究院的何凯明等人在2015年提出的一种深度卷积神经网络结构。ResNet的设计目标是解决深层网络训练中的梯度消失和梯度爆炸问题&#xff0c;进一步提高网络的表现。下面是一个ResNet模型实现&#xff0c;使用PyTorch框架来展示如何实现基本的…

添砖java第四更@(+)@

今天的学习内容主要是围绕着实体类来进行的&#xff0c;就是说在java里面我们常常会把数据存放和方法分别存放在不同的类里面。 首先就是关于实体类是什么&#xff0c;实体类就是只提供了get方法,set方法,和默认构造器的类。 接着就是熟悉java与别的语言的不同之处就在于它是…

算法题(19):多数元素

审题&#xff1a; 数组不为空且一定存在众数。需要返回众数的数值 思路&#xff1a; 方法一&#xff1a;哈希映射 先用哈希映射去存储对应数据出现的次数&#xff0c;然后遍历找到众数并输出 当然也可以在第一次映射的过程中就维护一个出现次数最多的数据&#xff0c;这样子就可…

电子商务网站的三层架构的理解和实践

在电子商务领域&#xff0c;网站架构的设计对于系统的稳定性、可扩展性和用户体验至关重要。其中&#xff0c;三层架构作为一种经典的设计模式&#xff0c;被广泛应用于各类电子商务网站中。本文将从理论、理解和实践三个方面&#xff0c;详细探讨电子商务网站的三层架构。 一、…

LVS 负载均衡原理 | 配置示例

注&#xff1a;本文为 “ LVS 负载均衡原理 | 配置” 相关文章合辑。 部分内容已过时&#xff0c;可以看看原理实现。 使用 LVS 实现负载均衡原理及安装配置详解 posted on 2017-02-12 14:35 肖邦 linux 负载均衡集群是 load balance 集群的简写&#xff0c;翻译成中文就是负…

JavaScript甘特图 dhtmlx-gantt

背景 需求是在后台中&#xff0c;需要用甘特图去展示管理任务相关视图&#xff0c;并且不用依赖vue&#xff0c;兼容JavaScript原生开发。最终使用dhtmlx-gantt&#xff0c;一个半开源的库&#xff0c;基础功能免费&#xff0c;更多功能付费。 甘特图需求如图&#xff1a; 调…

领域驱动设计第一篇-DP主题

一&#xff1a;领域驱动设计概述 领域驱动设计。Domain-Driven Design 可以理解为基于领域的工程设计。 1&#xff1a;什么是领域&#xff1f; 初步理解领域&#xff1a;业务问题的范畴。 领域可大可小&#xff0c;对应着大小业务问题的边界。业务上要做的几个事&#xff0…

EMNLP'24 最佳论文解读 | 大语言模型的预训练数据检测:基于散度的校准方法

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 点击 阅读原文 观看作者讲解回放&#xff01; 作者简介 张伟超&#xff0c;中国科学院计算所网络数据科学与技术重点实验室三年级直博生 内容简介 近年来&#xff0c;大语言模型&#xff08;LLMs&#xff09;的…

IntelliJ IDEA 远程调试

IntelliJ IDEA 远程调试 在平时开发 JAVA 程序时&#xff0c;在遇到比较棘手的 Bug 或者是线上线下结果不一致的情况下&#xff0c;我们会通过打 Log 或者 Debug 的方式去定位并解决问题&#xff0c;两种方式各有利弊&#xff0c;今天就简要介绍下如何通过远程 Debug 的情况下…

【Webug】攻防实战详情

世界上只有一种真正的英雄主义&#xff0c;那就是认清了生活的真相后&#xff0c;仍然热爱她 显错注入 首先整体浏览网站 注入点&#xff1a; control/sqlinject/manifest_error.php?id1 判断注入类型 输入: and 11 正常, 再输入: and 12 还正常, 排除数字型 输入单引号:…

SpringMVC核心、两种视图解析方法、过滤器拦截器 “ / “ 的意义

SpringMVC的执行流程 1. Spring MVC 的视图解析机制 Spring MVC 的核心职责之一是将数据绑定到视图并呈现给用户。它通过 视图解析器&#xff08;View Resolver&#xff09; 来将逻辑视图名称解析为具体的视图文件&#xff08;如 HTML、JSP&#xff09;。 核心流程 Controlle…