为什么输入网址打开的却是别的网站,淘宝采用了哪些网络营销方式,亚马逊卖家做自己网站,网站开发排期表在前面的文章当中我们已经成功播放了动画#xff0c;让我们的角色动了起来#xff0c;这一切变得比较有意思了起来。不过我们发现#xff0c;角色虽然说是动了起来#xff0c;不过只是在不停地原地踏步而已#xff0c;而且我们也没有办法通过键盘来控制这个角色来进行移动… 在前面的文章当中我们已经成功播放了动画让我们的角色动了起来这一切变得比较有意思了起来。不过我们发现角色虽然说是动了起来不过只是在不停地原地踏步而已而且我们也没有办法通过键盘来控制这个角色来进行移动现在整个项目处在一个只能看不能玩的处境。那么这篇文章笔者将为大家介绍如何通过键盘输入来控制我们绘制的角色。
输入事件 看过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平面元素所以这里我打算使用正交投影。具体代码如下
//原点到底部和顶部的距离依然设置是-11 左右两边给去窗口的长宽比
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;
好了结合前面的键盘输入我们可以让这只青蛙跑起来了 还是老样子把主函数的代码给大家帖在这里希望能帮助到大家。这一片和以前都不一样非常的难特别是数学这块。但是想要做出一款好的游戏数学就是避不开的话题就算是使用成熟的游戏引擎数学这一块依然不能完全避免希望大家能克服困难做出自己心仪的好游戏。
#includeglad/glad.h
#includeGLFW/glfw3.h#include imgui.h
#include imgui_impl_glfw.h
#include imgui_impl_opengl3.h#includeiostream
#includeglm/gtc/matrix_transform.hpp#includeFrameBuffer.h
#includeShader.h
#includeTexture.hstatic 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_castvoid*(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;}