{"id":442,"date":"2022-01-15T17:43:34","date_gmt":"2022-01-15T09:43:34","guid":{"rendered":"http:\/\/liyanliang.net\/?p=442"},"modified":"2022-01-15T17:46:13","modified_gmt":"2022-01-15T09:46:13","slug":"opengl-billboard-cpu","status":"publish","type":"post","link":"http:\/\/liyanliang.net\/index.php\/2022\/01\/15\/opengl-billboard-cpu\/","title":{"rendered":"OpenGL\u5b9e\u73b0billboard\u6548\u679c(CPU)"},"content":{"rendered":"\n<div id=\"toc_container\" class=\"no_bullets\"><p class=\"toc_title\">Contents<\/p><ul class=\"toc_list\"><li><a href=\"#i\"><span class=\"toc_number toc_depth_1\">1<\/span> \u5b9e\u73b0\u7684\u539f\u7406<\/a><\/li><li><a href=\"#i-2\"><span class=\"toc_number toc_depth_1\">2<\/span> \u5173\u4e8e\u5e03\u544a\u677f\u7684\u8ba1\u7b97<\/a><\/li><li><a href=\"#_billboardvs\"><span class=\"toc_number toc_depth_1\">3<\/span> \u9876\u70b9\u7740\u8272\u5668 billboard.vs<\/a><\/li><li><a href=\"#_billboardfs\"><span class=\"toc_number toc_depth_1\">4<\/span> \u7247\u6bb5\u7740\u8272\u5668 billboard.fs<\/a><\/li><li><a href=\"#i-3\"><span class=\"toc_number toc_depth_1\">5<\/span> \u5b8c\u6574\u9879\u76ee\u4ee3\u7801<\/a><\/li><\/ul><\/div>\n<h3 class=\"has-cyan-bluish-gray-background-color has-background wp-block-heading\"><span id=\"i\"> \u5b9e\u73b0\u7684\u539f\u7406 <\/span><\/h3>\n\n\n\n<p>\u65e0\u8bba\u600e\u4e48\u65cb\u8f6c\u89c6\u89d2\uff0c\u5b83\u90fd\u9762\u5411\u6444\u50cf\u673a\uff1b\u4f46\u5927\u5c0f\u4f1a\u968f\u7740\u8fdc\u8fd1\u800c\u53d8\u5316\u3002<\/p>\n\n\n\n<p>\u4f7f\u7269\u4f53\u7684\u53f3\u65b9\u5411\u59cb\u7ec8\u4e0e\u6444\u50cf\u673a\u7684\u6709\u65b9\u5411\u5e73\u884c\u3002\u5e03\u544a\u677f\u7684\u9876\u70b9\u5750\u6807\u6839\u636e\u76f8\u673a\u7684\u53f3\u65b9\u5411\u81ea\u52a8\u8ba1\u7b97\uff0c\u5e76\u4e14\u5e03\u544a\u677f\u59cb\u7ec8\u9762\u5411\u6444\u50cf\u673a\u3002\u5982\u4e0b\u56fe\u6240\u793a\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-image is-style-default\"><img decoding=\"async\" src=\"https:\/\/liyanliangpublic.oss-cn-hongkong.aliyuncs.com\/img\/20220114225543.png\" alt=\"\"\/><\/figure>\n\n\n\n<h3 class=\"has-cyan-bluish-gray-background-color has-background wp-block-heading\"><span id=\"i-2\">\u5173\u4e8e\u5e03\u544a\u677f\u7684\u8ba1\u7b97<\/span><\/h3>\n\n\n\n<p>\u7531\u7528\u6237\u786e\u5b9a\u5e03\u544a\u677f\u7684\u5927\u5c0f\u548c\u4e0b\u7aef\u4e2d\u70b9\u5750\u6807\uff1a<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">  struct   Billboard\n  {\n      glm::vec2      _size; \/\/ \u5e03\u544a\u677f\u5927\u5c0f\n      glm::vec3      _pos;  \/\/ \u5e03\u544a\u677f\u6700\u4e0b\u7aef\u4e2d\u70b9\u7684\u4f4d\u7f6e\n  };<\/pre>\n\n\n\n<p>\u6839\u636e\u6444\u50cf\u673a\u7684\u53f3\u65b9\u5411\u8ba1\u7b97\u5e03\u544a\u677f\u7684\u56db\u4e2a\u9876\u70b9\uff1a<\/p>\n\n\n\n<figure class=\"wp-block-image is-style-default\"><img decoding=\"async\" src=\"https:\/\/liyanliangpublic.oss-cn-hongkong.aliyuncs.com\/img\/20220114230451.png\" alt=\"\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image is-style-default\"><img decoding=\"async\" src=\"https:\/\/liyanliangpublic.oss-cn-hongkong.aliyuncs.com\/img\/20220115093323.png\" alt=\"\"\/><\/figure>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">          glm::vec3  faceDir =   camera.Right;\n          \/\/\/ \u8ba1\u7b97\u5e03\u544a\u677f\u7684\u56db\u4e2a\u70b9,\u6839\u636e\u6444\u50cf\u673acamera._right,\u53ef\u4ee5\u7406\u89e3\u4e3a\u6c34\u5e73(x\u8f74)\n          glm::vec3  lb = billboard._pos - faceDir * billboard._size.x * 0.5f;\n          glm::vec3  rb = billboard._pos + faceDir * billboard._size.x * 0.5f;\n  \u200b\n          \/\/\/ \u8ba1\u7b97\u56db\u4e2a\u70b9\u7684\u4e0b\u9762\u4e24\u4e2a\u70b9,\u4e0a\u9762\u7684\u70b9\u5c31\u662f\u4e0b\u9762\u7684,\u5728\u5782\u76f4\u65b9\u5411\u589e\u52a0\u4e00\u4e2a\u9ad8\u5ea6\n          glm::vec3  lt = lb + glm::vec3(0, billboard._size.y, 0);\n          glm::vec3  rt = rb + glm::vec3(0, billboard._size.y, 0);\n  \u200b\n          \/\/\/ \u7ed8\u5236\u4e00\u4e2a\u8349\u9700\u8981\u4e24\u4e2a\u4e09\u89d2\u5f62,\u516d\u4e2a\u70b9\n          struct  ObjectVertex\n          {\n              glm::vec3      _pos;\n              glm::vec2      _uv;\n          };\n          ObjectVertex transparentVertices[6] =   \n          {\n              {   lb, glm::vec2(0,1)},\n              {   rb, glm::vec2(1,1)},\n              {   rt, glm::vec2(1,0)},\n  \u200b\n              {   lb, glm::vec2(0,1)},\n              {   rt, glm::vec2(1,0)},\n              {   lt, glm::vec2(0,0)},\n          };<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span id=\"_billboardvs\">\u9876\u70b9\u7740\u8272\u5668 billboard.vs<\/span><\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">  #version 330 core\n  layout (location = 0) in vec3 aPos;\n  layout (location = 1) in vec2 aTexCoords;\n  \u200b\n  out vec2 TexCoords;\n  \u200b\n  uniform mat4 model;\n  uniform mat4 view;\n  uniform mat4 projection;\n  \u200b\n  void main()\n  {\n      TexCoords = aTexCoords;\n      gl_Position = projection * view * model * vec4(aPos, 1.0);\n  }<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span id=\"_billboardfs\">\u7247\u6bb5\u7740\u8272\u5668 billboard.fs<\/span><\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">  #version 330 core\n  out vec4 FragColor;\n  \u200b\n  in vec2 TexCoords;\n  \u200b\n  uniform sampler2D texture1;\n  \u200b\n  void main()\n  {             \n      vec4 texColor = texture(texture1, TexCoords);\n      if(texColor.a &lt; 0.1)\n          discard;\n      FragColor = texColor;\n  }<\/pre>\n\n\n\n<p class=\"has-cyan-bluish-gray-background-color has-background\">\u5b8c\u6574\u4ee3\u7801\u5b9e\u73b0<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">  \/\/#include &lt;glew\/glew.h>\n  #include &lt;glad\/glad.h>\n  #include &lt;GLFW\/glfw3.h>\n  #include &lt;stb_image.h>\n  \u200b\n  #include &lt;glm\/glm.hpp>\n  #include &lt;glm\/gtc\/matrix_transform.hpp>\n  #include &lt;glm\/gtc\/type_ptr.hpp>\n  \u200b\n  #include &lt;learnopengl\/shader_m.h>\n  #include &lt;learnopengl\/camera.h>\n  #include &lt;learnopengl\/model.h>\n  \u200b\n  \u200b\n  #include &lt;iostream>\n  \u200b\n  void framebuffer_size_callback(GLFWwindow* window, int width, int height);\n  void mouse_callback(GLFWwindow* window, double xpos, double ypos);\n  void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);\n  void processInput(GLFWwindow *window);\n  unsigned int loadTexture(const char *path);\n  \u200b\n  \/\/ settings\n  const unsigned int SCR_WIDTH = 800;\n  const unsigned int SCR_HEIGHT = 600;\n  \u200b\n  \/\/ camera\n  Camera camera(glm::vec3(0.0f, 1.0f, 10.0f));\n  float lastX = (float)SCR_WIDTH \/ 2.0;\n  float lastY = (float)SCR_HEIGHT \/ 2.0;\n  bool firstMouse = true;\n  \u200b\n  \/\/ timing\n  float deltaTime = 0.0f;\n  float lastFrame = 0.0f;\n  \u200b\n  \/\/ lighting\n  glm::vec3 lightPos(10.0f, 5.0f, 10.0f);\n  \u200b\n  struct  ObjectVertex\n  {\n      glm::vec3      _pos;\n      glm::vec2      _uv;\n  };\n  \u200b\n  struct   Billboard\n  {\n      glm::vec2      _size; \/\/ \u5e03\u544a\u677f\u5927\u5c0f\n      glm::vec3      _pos;  \/\/ \u5e03\u544a\u677f\u6700\u4e0b\u7aef\u4e2d\u70b9\u7684\u4f4d\u7f6e\n  };\n  \u200b\n  int main()\n  {\n      \/\/ glfw: initialize and configure\n      \/\/ ------------------------------\n      glfwInit();\n      glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n      glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n      glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n  \u200b\n  #ifdef __APPLE__\n      glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);\n  #endif\n  \u200b\n      \/\/ glfw window creation\n      \/\/ --------------------\n      GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, \"LearnOpenGL\", NULL, NULL);\n      if (window == NULL)\n      {\n          std::cout &lt;&lt; \"Failed to create GLFW window\" &lt;&lt; std::endl;\n          glfwTerminate();\n          return -1;\n      }\n      glfwMakeContextCurrent(window);\n      glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n      glfwSetCursorPosCallback(window, mouse_callback);\n      glfwSetScrollCallback(window, scroll_callback);\n  \u200b\n      \/\/ tell GLFW to capture our mouse\n      \/\/glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);\n  \u200b\n      \/\/ glad: load all OpenGL function pointers\n      \/\/ ---------------------------------------\n      if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))\n      {\n          std::cout &lt;&lt; \"Failed to initialize GLAD\" &lt;&lt; std::endl;\n          return -1;\n      }\n  \u200b\n      \/\/ configure global opengl state\n      \/\/ -----------------------------\n      glEnable(GL_DEPTH_TEST);\n  \u200b\n      \/\/ build and compile shaders\n      \/\/ -------------------------\n      Shader lightingShader(\"lighting_maps.vs\", \"lighting_maps.fs\");\n      Shader billboardShader(\"billboard.vs\", \"billboard.fs\");\n  \u200b\n      \/\/ set up vertex data (and buffer(s)) and configure vertex attributes\n      \/\/ ------------------------------------------------------------------\n      float planeVertices[] = {\n          \/\/ positions           \/\/ normals           \/\/ texture Coords \n          5.0f,  0.0f,  5.0f,    0.0f,  0.0f,  1.0f,  2.0f, 0.0f,\n          -5.0f, 0.0f,  5.0f,   0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n          -5.0f, 0.0f, -5.0f,   0.0f,  0.0f,  1.0f,  0.0f, 2.0f,\n  \u200b\n          5.0f, 0.0f,  5.0f,    0.0f,  0.0f,  1.0f,  2.0f, 0.0f,\n          -5.0f,0.0f, -5.0f,   0.0f,  0.0f,  1.0f,  0.0f, 2.0f,\n          5.0f, 0.0f, -5.0f,    0.0f,  0.0f,  1.0f,  2.0f, 2.0f\n      };\n  \u200b\n      \/\/ plane VAO\n      unsigned int planeVAO, planeVBO;\n      glGenVertexArrays(1, &amp;planeVAO);\n      glGenBuffers(1, &amp;planeVBO);\n      glBindVertexArray(planeVAO);\n      glBindBuffer(GL_ARRAY_BUFFER, planeVBO);\n      glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &amp;planeVertices, GL_STATIC_DRAW);\n  \u200b\n      glBindVertexArray(planeVAO);\n      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n      glEnableVertexAttribArray(0);\n      glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n      glEnableVertexAttribArray(1);\n      glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n      glEnableVertexAttribArray(2);\n      \/\/ transparent VAO\n      unsigned int transparentVAO, transparentVBO;\n      glGenVertexArrays(1, &amp;transparentVAO);\n      glGenBuffers(1, &amp;transparentVBO);\n  \u200b\n      \/\/ load textures\n      \/\/ -------------\n      unsigned int floorTexture = loadTexture(\"resources\/textures\/metal.png\");\n      unsigned int transparentTexture = loadTexture(\"resources\/textures\/grass.png\");\n  \u200b\n      \/\/ transparent vegetation locations\n      \/\/ --------------------------------\n      vector&lt;glm::vec3> vegetation;\n      float height  =   0.0f;\n      for (float x = -2 ; x &lt; 2 ; x += 1)\n      {\n          for (float z = -2 ; z &lt; 2 ; z += 1)\n          {\n              vegetation.push_back(glm::vec3(x,height,z));\n          }\n      }\n  \u200b\n      \/\/ shader configuration\n      \/\/ --------------------\n      lightingShader.use(); \n      lightingShader.setInt(\"material.diffuse\", 0);\n  \u200b\n      billboardShader.use();\n      billboardShader.setInt(\"texture1\", 0);\n  \u200b\n      \/\/ \n      Billboard billboard;\n      billboard._size = glm::vec2(1.0f, 1.0f); \n      billboard._pos = glm::vec3(0.0f,  0.0f,  0.0f);\n  \u200b\n      \/\/ render loop\n      \/\/ -----------\n      while (!glfwWindowShouldClose(window))\n      {\n          \/\/ per-frame time logic\n          \/\/ --------------------\n          float currentFrame = static_cast&lt;float>(glfwGetTime());\n          deltaTime = currentFrame - lastFrame;\n          lastFrame = currentFrame;\n  \u200b\n          \/\/ input\n          \/\/ -----\n          processInput(window);\n  \u200b\n          \/\/ render\n          \/\/ ------\n          glClearColor(0.1f, 0.1f, 0.1f, 1.0f);\n          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n  \u200b\n          \/\/ draw objects\n          lightingShader.use();\n          lightingShader.setVec3(\"light.position\", lightPos);\n          lightingShader.setVec3(\"viewPos\", camera.Position);\n  \u200b\n          \/\/ light properties\n          lightingShader.setVec3(\"light.ambient\", 0.2f, 0.2f, 0.2f); \n          lightingShader.setVec3(\"light.diffuse\", 0.5f, 0.5f, 0.5f);\n          lightingShader.setVec3(\"light.specular\", 1.0f, 1.0f, 1.0f);\n  \u200b\n          \/\/ material properties\n          lightingShader.setVec3(\"material.specular\", 0.5f, 0.5f, 0.5f);\n          lightingShader.setFloat(\"material.shininess\", 64.0f);\n  \u200b\n          \/\/ view\/projection transformations\n          glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH \/ (float)SCR_HEIGHT, 0.1f, 100.0f);\n          glm::mat4 view = camera.GetViewMatrix();\n          lightingShader.setMat4(\"projection\", projection);\n          lightingShader.setMat4(\"view\", view);\n  \u200b\n          \/\/ world transformation\n          glm::mat4 model = glm::mat4(1.0f);\n          lightingShader.setMat4(\"model\", model);\n  \u200b\n          \/\/ bind diffuse map\n          glEnable(GL_TEXTURE_2D);\n          glBindTexture(GL_TEXTURE_2D, floorTexture);\n  \u200b\n          \/\/ render the floor\n          glBindVertexArray(planeVAO);\n          glDrawArrays(GL_TRIANGLES, 0, 6);\n          glBindVertexArray(0);\n  \u200b\n          \/\/ vegetation   \n          billboardShader.use();\n          glm::mat4 projection2 = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH \/ (float)SCR_HEIGHT, 0.1f, 100.0f);\n          glm::mat4 view2 = camera.GetViewMatrix();\n          glm::mat4 model2 = glm::mat4(1.0f);\n          billboardShader.setMat4(\"projection\", projection2);\n          billboardShader.setMat4(\"view\", view2);\n          glm::vec3  faceDir =   camera.Right;\n          \/\/\/ \u8ba1\u7b97\u5e03\u544a\u677f\u7684\u56db\u4e2a\u70b9,\u6839\u636e\u6444\u50cf\u673acamera._right,\u53ef\u4ee5\u7406\u89e3\u4e3a\u6c34\u5e73(x\u8f74)\n          glm::vec3  lb      =   billboard._pos - faceDir * billboard._size.x * 0.5f;\n          glm::vec3  rb      =   billboard._pos + faceDir * billboard._size.x * 0.5f;\n  \u200b\n          \/\/\/ \u8ba1\u7b97\u56db\u4e2a\u70b9\u7684\u4e0b\u9762\u4e24\u4e2a\u70b9,\u4e0a\u9762\u7684\u70b9\u5c31\u662f\u4e0b\u9762\u7684,\u5728\u5782\u76f4\u65b9\u5411\u589e\u52a0\u4e00\u4e2a\u9ad8\u5ea6\n          glm::vec3  lt = lb + glm::vec3(0, billboard._size.y, 0);\n          glm::vec3  rt = rb + glm::vec3(0, billboard._size.y, 0);\n  \u200b\n          \/\/\/ \u7ed8\u5236\u4e00\u4e2a\u8349\u9700\u8981\u4e24\u4e2a\u4e09\u89d2\u5f62,\u516d\u4e2a\u70b9\n          ObjectVertex transparentVertices[6] =   \n          {\n              {   lb, glm::vec2(0,1)},\n              {   rb, glm::vec2(1,1)},\n              {   rt, glm::vec2(1,0)},\n  \u200b\n              {   lb, glm::vec2(0,1)},\n              {   rt, glm::vec2(1,0)},\n              {   lt, glm::vec2(0,0)},\n          };\n  \u200b\n          glBindVertexArray(transparentVAO);\n          glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);\n  \u200b\n          glBufferData(GL_ARRAY_BUFFER, sizeof(transparentVertices), transparentVertices, GL_STATIC_DRAW);\n          glEnableVertexAttribArray(0);\n          glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);\n          glEnableVertexAttribArray(1);\n          glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));\n          glBindVertexArray(0);\n  \u200b\n          glBindVertexArray(transparentVAO);  \n          glEnable(GL_TEXTURE_2D);\n          glBindTexture(GL_TEXTURE_2D, transparentTexture);\n  \u200b\n          for (unsigned int i = 0; i &lt; vegetation.size(); i++)\n          {\n              model2 = glm::mat4(1.0f);\n              model2 = glm::translate(model2, vegetation[i]);\n              billboardShader.setMat4(\"model\", model2);\n              glDrawArrays(GL_TRIANGLES, 0, 6);\n          }\n  \u200b\n          \/\/ glfw: swap buffers and poll IO events (keys pressed\/released, mouse moved etc.)\n          \/\/ -------------------------------------------------------------------------------\n          glfwSwapBuffers(window);\n          glfwPollEvents();\n      }\n  \u200b\n      \/\/ optional: de-allocate all resources once they've outlived their purpose:\n      \/\/ ------------------------------------------------------------------------\n      glDeleteVertexArrays(1, &amp;planeVAO);\n      glDeleteBuffers(1, &amp;planeVBO);\n  \u200b\n      glfwTerminate();\n      return 0;\n  }\n  \u200b\n  \/\/ process all input: query GLFW whether relevant keys are pressed\/released this frame and react accordingly\n  \/\/ ---------------------------------------------------------------------------------------------------------\n  void processInput(GLFWwindow *window)\n  {\n      if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)\n          glfwSetWindowShouldClose(window, true);\n  \u200b\n      if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)\n          camera.ProcessKeyboard(FORWARD, deltaTime);\n      if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)\n          camera.ProcessKeyboard(BACKWARD, deltaTime);\n      if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)\n          camera.ProcessKeyboard(LEFT, deltaTime);\n      if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)\n          camera.ProcessKeyboard(RIGHT, deltaTime);\n  }\n  \u200b\n  \/\/ glfw: whenever the window size changed (by OS or user resize) this callback function executes\n  \/\/ ---------------------------------------------------------------------------------------------\n  void framebuffer_size_callback(GLFWwindow* window, int width, int height)\n  {\n      \/\/ make sure the viewport matches the new window dimensions; note that width and \n      \/\/ height will be significantly larger than specified on retina displays.\n      glViewport(0, 0, width, height);\n  }\n  \u200b\n  \/\/ glfw: whenever the mouse moves, this callback is called\n  \/\/ -------------------------------------------------------\n  void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)\n  {\n      float xpos = static_cast&lt;float>(xposIn);\n      float ypos = static_cast&lt;float>(yposIn);\n      if (firstMouse)\n      {\n          lastX = xpos;\n          lastY = ypos;\n          firstMouse = false;\n      }\n  \u200b\n      float xoffset = xpos - lastX;\n      float yoffset = lastY - ypos; \/\/ reversed since y-coordinates go from bottom to top\n  \u200b\n      lastX = xpos;\n      lastY = ypos;\n  \u200b\n      camera.ProcessMouseMovement(xoffset, yoffset);\n  }\n  \u200b\n  \/\/ glfw: whenever the mouse scroll wheel scrolls, this callback is called\n  \/\/ ----------------------------------------------------------------------\n  void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)\n  {\n      camera.ProcessMouseScroll(static_cast&lt;float>(yoffset));\n  }\n  \u200b\n  \/\/ utility function for loading a 2D texture from file\n  \/\/ ---------------------------------------------------\n  unsigned int loadTexture(char const * path)\n  {\n      unsigned int textureID;\n      glGenTextures(1, &amp;textureID);\n  \u200b\n      int width, height, nrComponents;\n      unsigned char *data = stbi_load(path, &amp;width, &amp;height, &amp;nrComponents, 0);\n      if (data)\n      {\n          GLenum format;\n          if (nrComponents == 1)\n              format = GL_RED;\n          else if (nrComponents == 3)\n              format = GL_RGB;\n          else if (nrComponents == 4)\n              format = GL_RGBA;\n  \u200b\n          glBindTexture(GL_TEXTURE_2D, textureID);\n          glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);\n          glGenerateMipmap(GL_TEXTURE_2D);\n  \u200b\n          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); \/\/ for this tutorial: use GL_CLAMP_TO_EDGE to prevent semi-transparent borders. Due to interpolation it takes texels from next repeat \n          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);\n          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\n          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  \u200b\n          stbi_image_free(data);\n      }\n      else\n      {\n          std::cout &lt;&lt; \"Texture failed to load at path: \" &lt;&lt; path &lt;&lt; std::endl;\n          stbi_image_free(data);\n      }\n  \u200b\n      return textureID;\n  }\n  \u200b<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><span id=\"i-3\">\u5b8c\u6574\u9879\u76ee\u4ee3\u7801<\/span><\/h3>\n\n\n\n<p><a href=\"https:\/\/github.com\/mc-liyanliang\/OpenGL-Shader\/tree\/master\">https:\/\/github.com\/mc-liyanliang\/OpenGL-Shader\/tree\/master<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Contents1 \u5b9e\u73b0\u7684\u539f\u74062 \u5173\u4e8e\u5e03\u544a\u677f\u7684\u8ba1\u7b973 \u9876\u70b9\u7740\u8272\u5668 billboard.vs4 \u7247\u6bb5\u7740\u8272\u5668 billboard.fs5 \u5b8c\u6574\u9879\u76ee\u4ee3\u7801 \u5b9e\u73b0\u7684\u539f\u7406 \u65e0\u8bba\u600e\u4e48\u65cb\u8f6c\u89c6\u89d2\uff0c\u5b83\u90fd\u9762\u5411\u6444\u50cf\u673a\uff1b\u4f46\u5927\u5c0f\u4f1a\u968f\u7740\u8fdc\u8fd1\u800c\u53d8\u5316\u3002 \u4f7f\u7269\u4f53\u7684\u53f3\u65b9\u5411\u59cb\u7ec8\u4e0e\u6444\u50cf\u673a\u7684\u6709\u65b9\u5411\u5e73\u884c\u3002\u5e03\u544a\u677f\u7684\u9876\u70b9\u5750\u6807\u6839\u636e\u76f8\u673a\u7684\u53f3\u65b9\u5411\u81ea\u52a8\u8ba1\u7b97\uff0c\u5e76\u4e14\u5e03\u544a\u677f\u59cb\u7ec8\u9762\u5411\u6444\u50cf\u673a\u3002\u5982\u4e0b\u56fe\u6240\u793a\uff1a \u5173\u4e8e\u5e03\u544a\u677f\u7684\u8ba1\u7b97 \u7531\u7528\u6237\u786e\u5b9a\u5e03\u544a\u677f\u7684\u5927\u5c0f\u548c\u4e0b\u7aef\u4e2d\u70b9\u5750\u6807\uff1a \u6839\u636e\u6444\u50cf\u673a\u7684\u53f3\u65b9\u5411\u8ba1\u7b97\u5e03\u544a\u677f\u7684\u56db\u4e2a\u9876\u70b9\uff1a \u9876\u70b9\u7740\u8272\u5668 billboard.vs \u7247\u6bb5\u7740\u8272\u5668 billboard.fs \u5b8c\u6574\u4ee3\u7801\u5b9e\u73b0 \u5b8c\u6574\u9879\u76ee\u4ee3\u7801 https:\/\/github.com\/mc-liyanliang\/OpenGL-Shader\/tree\/master<\/p>\n","protected":false},"author":1,"featured_media":445,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14],"tags":[46],"class_list":["post-442","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-opengl","tag-billboard"],"_links":{"self":[{"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/posts\/442","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/comments?post=442"}],"version-history":[{"count":3,"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/posts\/442\/revisions"}],"predecessor-version":[{"id":447,"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/posts\/442\/revisions\/447"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/media\/445"}],"wp:attachment":[{"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/media?parent=442"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/categories?post=442"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/liyanliang.net\/index.php\/wp-json\/wp\/v2\/tags?post=442"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}