OpenGL变换矩阵和输入控制
在前面的文章当中我们已经成功播放了动画,让我们的角色动了起来,这一切变得比较有意思了起来。不过我们发现,角色虽然说是动了起来,不过只是在不停地原地踏步而已,而且我们也没有办法通过键盘来控制这个角色来进行移动,现在整个项目处在一个只能看不能玩的处境。那么这篇文章,笔者将为大家介绍如何通过键盘输入来控制我们绘制的角色。
输入事件
看过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;elsedirX = 0, dirY = 0;//当输入的方向和当前移动的方向不同时我们需要将整个图形进行一次翻转sameDirect = dirX * moveDirect < 0 ? false : true;if (dirX != 0)moveDirect = dirX < 0 ? -1 : 1;elsemoveDirect = 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 view_matrix
project_matrix)。
模型矩阵
可以理解为我们创建一个图形时各个顶点相对于世界原点的坐标,我们在最开始输入的坐标位置都是相对于世界原点坐标而言的,每个顶点的坐标就是一个就是一个模型矩阵,可能有些朋友们会比较疑惑,为什么不把整个图形作为一个模型矩阵了?这里请读者们记住一个原则,计算机图形当中的所有图形都是由顶点构成,我们通过设定对应的图元,以及绘制顺序,来告诉计算机这些顶点将要绘制什么样的图形。无论我们对模型矩阵进行任何平移,旋转,缩放的操作都是在对顶点进行操作。
平移矩阵
我们可以看到,右边的坐标与左边的平移矩阵相乘,各个分量就加上了平移的分量。可能有的朋友又有疑问了,为什么这个点的坐标有四个分量,三维的空间坐标不是应该只有三个向量吗?其实我们在数学上管第四个分量叫作齐次量,它的存在有更多数学意义,这里就不展开讲了,要不然就扯得实在太远了,它在这里作用就是就是给每个分量加上对应平移量,读者可以将第四个分量写作0试一下,两个矩阵相乘过后你就会发现这个平移矩阵没有起到任何得作用。所以我们一般把顶点的第四个分量写作1,向量的第四个分量写作0,以为向量的平移是没有数学意义的。
旋转矩阵
旋转的话,我们一般会将让该物体绕着某一个轴进行旋转,就比如说绕Z轴旋转angle°得到向量
(顶点的话也没有问题,因为第四个分量并没有参与到运算当中)
旋转矩阵具体如下
就是想要旋转的角度我不过欧拉旋转阵,有个很致命的问题,那就是万象死锁问题,有兴趣去了解的话,大家可以自己去查阅一些相关的资料。(如果制作的是一个2D游戏的话不需要担心这个问题)
缩放矩阵
如果我们把缩放变量表示为(S1,S2,S3)我们可以为任意向量(x,y,z)定义一个缩放矩阵:
S1,S2,S3各个分量大于1,就是对相对应的分量进行放大,小于1就是缩小。
对模型进行变换的矩阵就这么三种,只是这三种就可以组合出我们想要的所有的变化,可能有很多读者并不相信,可这就是事实。数学就是这么神奇!
观察矩阵
观察矩阵,也可以叫作相机矩阵。不过这个案例相机矩阵的效果不是很明显,因为我们的模型是平面的,就算移动相机也看不出什么效果,到时候笔者单独做一个案例来讲这个东西。所以在这个案例单中我们将它设置为一个单位阵E。
投影矩阵
简言之就是将三位空间当中的物体投影到我们屏幕上来。没错虽然我们做的案例是个二维平面的图形,但是他是实打实在3D空间当中。投影的方式有两种,正交投影和透视投影,透视投影就和我们生活中看到的物体同样的效果,满足近大远小的规律。正交投影将没有这种效果,它无论距离屏幕有多远,投影到屏幕上是一样的大小。
正交投影
正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器:
上面的平截头体定义了可见的坐标,它由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;如果w分量等于1.0,透视除法则不会改变这个坐标。
透视投影
如果你曾经体验过实际生活给你带来的景象,你就会注意到离你越远的东西看起来更小。这个奇怪的效果称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样:
正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉)。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 contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controlsio.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows//io.ConfigViewportsNoAutoMerge = true;//io.ConfigViewportsNoTaskBarIcon = true;// Setup Dear ImGui styleImGui::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 backendsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 130");//需要初始化GLADif (!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 frameImGui_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);*/// RenderingImGui::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();}// CleanupImGui_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;elsedirX = 0, dirY = 0;sameDirect = dirX * moveDirect < 0 ? false : true;if (dirX != 0)moveDirect = dirX < 0 ? -1 : 1;elsemoveDirect = moveDirect;}
相关文章:
OpenGL变换矩阵和输入控制
在前面的文章当中我们已经成功播放了动画,让我们的角色动了起来,这一切变得比较有意思了起来。不过我们发现,角色虽然说是动了起来,不过只是在不停地原地踏步而已,而且我们也没有办法通过键盘来控制这个角色来进行移动…...
51单片机学习笔记——找不到REG52.H头文件,点亮一个LED
创建工程 将STC型号导入keil并使用 STC可以从官网下载,也可我这的网盘: 链接:https://pan.baidu.com/s/1bO85DPN3IFaXGhiKSwyOrA?pwd7f4h 提取码:7f4h 打开STC,选择“keil仿真设置”,选择“添加型号和头…...
07 基于OpenAMP的核间通信方案
引言 ZYNQ7020有两个CPU核心,这两个核心可以采用SMP或AMP方式进行调度,当采用AMP方式进行调度时核0和核1可以运行不同的操作系统,如核0运行Linux系统,提供有些复杂的用户交互工作,核1运行实时操作系统,对设…...
Ubuntu升级ssh版本到9.8
方案一:实测只有8.9有漏洞不推荐 1、更新软件包列表 sudo apt update 2、查找可用版本 apt-cache policy openssh-server 3、 选择版本 sudo apt install openssh-server1:9.8p1-<具体版本号> 4、 重启 sudo systemctl restart ssh 5、验证版本 /usr/sbin/ss…...
git设置项目远程仓库指向github的一个仓库
要将你的Git项目设置为指向GitHub上的远程仓库,你需要执行以下步骤: 创建GitHub仓库: 登录到你的GitHub账户。点击右上角的 “” 号,选择 “New repository” 创建一个新的仓库。填写仓库的名称,可以添加描述ÿ…...
【实战示例】面向对象的需求建模
前言 博主准备写一个以面向对象为核心思想的软件需求建模、领域建模的系列,总结一整套可落地的DDD的打法,前面几篇文章论述了如何进行面向对象的需求建模,本文将以一个简单的购物商城的需求来演示如何进行面向对象的需求建模。 面向对象的需…...
平方数的判断不用sqrt()函数
//判断一个数是不是平方数,13…(2*m-1)m*mn #include<stdio.h> int main(){ int n; scanf("%d",&n); int i; for(i1;n>0;i2){ nn-1; } if(n0){ printf("YES!\n"); …...
node.js之---回调函数
什么是回调函数? 为什么会有回调函数? 回调函数的特性 回调函数的应用场景 怎么解决回调地狱 什么是回调函数? 回调函数是一个函数,他作为参数传递给另外一个函数,并且会在另外一个函数执行完毕之后被调用&#…...
浏览器http缓存问题
一、什么是浏览器缓存 浏览器将请求过的资源(html、js、css、img)等,根据缓存机制,拷贝一份副本存储在浏览器的内存或者磁盘上。如果下一次请求的url相同时则根据缓存机制决定是读取内存或者磁盘上的数据还是去服务器请求资源文件…...
编写一个简单的引导加载程序(bootloader)
编写一个简单的引导加载程序(bootloader)通常用于嵌入式系统或自定义操作系统。这里,我将为你提供一个基于x86架构的简单汇编语言 bootloader 示例。这个 bootloader 将会在启动时打印一条消息到屏幕上。 使用 NASM 汇编器来编写这个 bootlo…...
Three.js 字体
在 Three.js 中,我们可以通过 FontLoader 加载字体,并结合 TextGeometry 创建 3D 文本。加载字体是因为字体文件包含了字体的几何信息,例如字体的形状、大小、粗细等,而 TextGeometry 则是根据字体信息生成 3D 文本的几何体。 在…...
Jenkins 构建流水线
在 Linux 系统上安装 Jenkins 服务,以及配置自动化构建项目 前置准备环境:docker、docker-compose、jdk、maven 一、环境搭建 1. Jenkins 安装 (1)拉取镜像 # 安装镜像包,默认安装最新版本 docker pull jenkins/jen…...
ES 磁盘使用率检查及处理方法
文章目录 1. 检查原因2. 检查方法3. 处理方法3.1 清理数据3.2 再次检查磁盘使用率 1. 检查原因 磁盘使用率在 85%以下,ES 可正常运行,达到 85%及以上会影响 PEIM 数据存储。 在 ES 磁盘分配分片控制策略中,为了保护数据节点的安全࿰…...
【回溯】LeetCode经典题目总结:组合、排列、子集、分割、N皇后、单词搜索
回溯 组合问题组合总和全排列子集分割回文串N皇后电话号码的字母组合单词搜索括号生成 组合问题 给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 树形结构࿱…...
uniapp开发小程序内嵌h5页面,video视频两边有细小黑色边框
1.问题如图 2.原因分析 是否为设置上述属性呢? 设置了,但是仍然有黑边。经过选中页面元素分析后,判断video元素本身就有这种特点,就是视频资源无法完全铺满元素容器。 3.解决方案...
Ubuntu meson使用
一 下载pip3 ,使用pip3下载 meson sudo apt install python3 sudo apt install python3-pip二 下载 nanjia sudo apt-get install ninja-build三 测试 meson 使用 1 同一个目录下创建两个文件 main.c #include<stdio.h> int main() {printf("meson t…...
实用技巧:关于 AD修改原理图库如何同步更新到有原理图 的解决方法
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/144738332 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV…...
算法排序算法
文章目录 快速排序[leetcode 215数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/)分析题解快速排序 桶排序[leetcode 347 前K个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/)分析题解 快速排序 leetcode 215数组…...
植物大战僵尸杂交版3.0.2版本
更新内容 植物大战僵尸杂交版3.0.2版本的更新内容如下: • 修复BUG: • 游戏内贴图错乱的BUG。 • 无尽模式卡死的BUG。 • 卡牌模仿者的一系列BUG。 • 干扰车可能同时出现多辆的BUG。 • 冒险模式部分关卡无法过关的BUG。 • 新增内容…...
Kafka数据迁移全解析:同集群和跨集群
文章目录 一、同集群迁移二、跨集群迁移 Kafka两种迁移场景,分别是同集群数据迁移、跨集群数据迁移。 一、同集群迁移 应用场景: broker 迁移 主要使用的场景是broker 上线,下线,或者扩容等.基于同一套zookeeper的操作。 实践: 将需要新添加…...
自动化测试模型(一)
8.8.1 自动化测试模型概述 在自动化测试运用于测试工作的过程中,测试人员根据不同自动化测试工具、测试框架等所进行的测试活动进行了抽象,总结出线性测试、模块化驱动测试、数据驱动测试和关键字驱动测试这4种自动化测试模型。 线性测试 首先&#…...
selenium(三)
总结 一、web基础 html、dom对象、javascript基本语法二、元素定位: find_element(定位方式) 八大定位方式:id、name、class、tag_name、class_name、link_text、partial_link_text、xpath、cssxpath://标签名[属性名值 and/or 属性名值]//标签名[tex…...
7.若依参数设置、通知公告、日志管理
参数设置 对系统中的参数进行动态维护。 关闭验证码校验功能 打开页面注册功能 需要修改前端页面代码 通知公告 促进组织内部信息传递 若依只提供了一个半成品,只实现了管理员可以添加通知公告。 日志管理 追踪用户行为和系统运行状况。 登录日志 和操作日志…...
vsftpd虚拟用户及其权限配置
目录 一、应用场景二、配置过程1、安装软件2、新建本地用户3、修改vsftpd的配置文件4、新建虚拟用户目录5、配置虚拟用户(1)创建虚拟用户列表文件(2)生成虚拟用户数据库(3)配置pam认证(4&#…...
Android使用辅助服务AccessibilityService实现自动化任务
Android 辅助服务(AccessibilityService)旨在帮助具有视觉、身体或年龄相关限制的用户更轻松地使用 Android 设备和应用。通过辅助服务,可以将一些人工操作自动化,从而解放用户的双手。 因此我们可以使用它来实现一些自动化任务&a…...
brupsuite的基础用法常用模块(1)
proxy模块: Options: 设置代理端口,默认为8080端口,若8080端口被占用可在该界面更改代理端口. HTTP history: 拦截的历史请求,右键可做更多操作,很多操作与其他模块有关。(清除历史的话右键选择clear p…...
基础的基础之 pillow与opencv相比的特点与优缺点比较
Pillow 和 OpenCV 都是人工智能图像处理的必不可少的常用库,但它们有各自的特点和适用场景。 以下是它们的主要特点、优缺点以及适用场景的对比: 1. Pillow(Python Imaging Library) Pillow 是一个轻量级的图像处理库࿰…...
代码随想录算法训练营第51期第32天 | 理论基础、509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
理论基础 动态规划:dp,每一个状态都是由上个状态推导出来的,因为我是先写完三道题再看理论的,所以有点感概; 确定dp数组(dp table)以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举…...
街景主观感知1:街景图片两两对比程序
目录 1、为什么不用place pluse 2.0数据集?2、街景主观感知两两对比程序2.1 总框架2.2 两两对比程序流程2.2.1 准备数据集(即街景图片)2.2.2 数据集采用组合的思想构建排列前的两两对比csv2.2.3 两两对比程序使用 3、其他数据处理/程序/指导&…...
vue在action中调用action的函数
以下是在 Vue2 中一个 Vuex 的 action 中调用另一个 action 中的函数的解决思路: 首先,确保你已经在 Vue 项目中正确配置了 Vuex 状态管理。在 Vuex 的 actions 对象中定义不同的 action 函数。当需要在一个 action 中调用另一个 action 时,…...
探索 Java 静态变量(static)的奥秘
目录 一、静态变量初印象 (一)什么是静态变量 (二)静态变量的特点 二、静态变量的使用场景 (一)共享数据 (二)全局常量 (三)工具类中的变量 三、静态…...
Excel for Finance 07 `FV PV` 函数
Excel 的 FV 函数用于计算一笔投资在未来的价值,基于固定的利率和定期付款。这是一个金融函数,常用来分析储蓄计划、贷款、或投资的增长。 语法: FV(rate, nper, pmt, [pv], [type])参数说明: rate(必需)&…...
【GUI-PyQt5】简介
1. 简介 GUI:带图形的用户接口程序,也就是桌面应用。 2. 分类 2.1 基本窗口控件 QMainWindowQwidgetQlabelQLineEdit菜单工具栏 2.2 高级组件 QTableViewQListView容器多线程 2.3 布局管理 QBoxLayoutQGridLayoutQFormLayout嵌套布局 2.4 信号与…...
CMSeasy;大米CMS漏洞复现
一、越权漏洞 pikachu-Over permission 水平越权 ⽔平越权:指攻击者尝试访问与他拥有相同权限的⽤户资源。 登录lucy 查看lucy个人信息 在lucy页面修改usernamelili 可以跳转lili的个人信息页面 pikachu-Over permission 垂直越权 垂直越权:通过低权…...
【CSS in Depth 2 精译_093】16.2:CSS 变换在动效中的应用(上)—— 图标的放大和过渡效果的设置
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第五部分 添加动效 ✔️【第 16 章 变换】 ✔️ 16.1 旋转、平移、缩放与倾斜 16.1.1 变换原点的更改16.1.2 多重变换的设置16.1.3 单个变换属性的设置 16.2 变换在动效中的应用 ✔️ 16.2.1 放大图…...
代码随想录算法【Day5】
DAY5 1.熟悉哈希表的数据结构:数组、map和set,使用方法、使用场景 2.哈希表应用场景:解决给你一个元素,判断它在集合里是否出现过。 242.有效的字母异位词 本题用数组解决的。 class Solution { public:bool isAnagram(strin…...
工程师如何平衡工作和生活?
在快节奏的社会环境中,工程师面临着高强度的工作压力和长时间的工作需求,要做到工作和生活的平衡,确实不容易。然而,通过一些策略和改变工作方式,可以有效缓解压力,找到平衡点。以下是一些建议:…...
若依数据权限控制
效果 新建用户 表结构 sys_role_dept 这张表的存在。是为了实现数据权限自定义的功能 service层 mapper层 流程...
C++ 设计模式:抽象工厂(Abstract Factory)
链接:C 设计模式 链接:C 设计模式 - 工厂方法 链接:C 设计模式 - 原型模式 链接:C 设计模式 - 建造者模式 抽象工厂(Abstract Factory)是一种创建型设计模式,它提供一个接口,用于创…...
嵌入式系统 第十讲 字符设备和驱动程序设计
• 字符设备是Linux三大设备之一(另外两种是块设备,网 络设备)。 • 字符设备就是采用字节流形式通讯的I/O设备,绝大部分 设备都是字符设备。 • 常见的字符设备包括鼠标、键盘、显示器、串口等等。 • 10.1 字符设备驱动框架 …...
Cod---Class of Device for Bluetooth device
0 Preface/Foreword 蓝牙sink在广播时候,需要告知自己的设备类型,这样蓝牙source可以方便准备相应协议。 从可视化来看,cod(设备类型),比如Android手机,在蓝牙可配对列表中,左边那…...
踏踏实实练SQLday2-3连续12345
3连续12345 连续12345 -- hive的连续开窗函数知识点:排序开窗函数--4种 (如果第2 和 3 名一样大)3种 序号排序 - rank() 12245 - 紧缩dense_rank() 12234 - row_number() 123451种百分比排序 - percent_rank() - 算百分比,超过了多少人top20排行榜 连续1…...
脱离电路图编程
SM0.0常开始终吸合 SM0.1(特殊中继) 常开:闭合一次再断开 常闭:断开一次再闭合 上述是依据电路图编程...
spring的@Transactional事务原理理解
目录 Transactional 普通例子代码和测试输出编程式事务事务代理实现和TransactionAspectSupport重要类复习Spring的事务传播机制有哪些实际工作中用到的事务处理 Transactional事务原理理解 Transactional 普通例子代码和测试输出 Transactional(rollbackFor Exception.class…...
机器学习之线性回归算法预测数据
机器学习之线性回归算法预测数据 目录 机器学习之线性回归算法预测数据线性回归算法概念理解算法导入线性回归模型参数理解误差项分析 LinearRegression理解参数理解返回值方法基本格式 预测一元线性回归数据问题及理解可视化数据理解数据预测 预测二元线性回归数据问题及理解数…...
Markdown如何填充前景色、背景色
Markdown如何填充前景色、背景色 前言语法详解前景色背景色 > 快来试试吧😊 Markdown填充前景色、背景色讲解👈点击这里也可查看 前言 颜色可以使普通文字表达出更深刻的含义,比如红色用于醒目与警示,绿色用于表达良好与正常等…...
SpringCloudAlibaba实战入门之路由网关Gateway断言(十二)
上一节课中我们初步讲解了网关的基本概念、基本功能,并且带大家实战体验了一下网关的初步效果,这节课我们继续学习关于网关的一些更高级有用功能,比如本篇文章的断言。 一、网关主要组成部分 上图中是核心的流程图,最主要的就是Route、Predicates 和 Filters 作用于特定路…...
Spring Boot Actuator、Spring Boot Actuator使用、Spring Boot Actuator 监控、Spring程序监控
SpringBoot四大核心之actuator——程序监控器 1. 添加 Spring Boot Actuator 依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 2. 配置 …...
【机器学习】机器学习的基本分类-半监督学习(Semi-supervised Learning)
半监督学习是一种介于监督学习和无监督学习之间的机器学习方法。它利用少量的标注数据(有监督数据)和大量的未标注数据(无监督数据)来进行模型训练,从而在标注数据不足的情况下,提升模型的性能。 半监督学习…...
RAG挑战及其解决方案:实践中的应对策略
RAG挑战及其解决方案:实践中的应对策略 在将检索增强生成(RAG)技术应用于生产环境的过程中,我发现了许多在原型设计阶段不易察觉的挑战。RAG结合了生成模型和检索机制,以调用外部知识,从而提高语言模型的质…...