Skip to content
  • 首页
  • 留言
  • 关于

Share the joys of programming and technology

OpenGL实现billboard效果(CPU)

OpenGL实现billboard效果(CPU)

2022年1月15日 liyanliang Comments 0 Comment
阅读次数: 865

Contents

  • 1 实现的原理
  • 2 关于布告板的计算
  • 3 顶点着色器 billboard.vs
  • 4 片段着色器 billboard.fs
  • 5 完整项目代码
  • 6 相关文章

实现的原理

无论怎么旋转视角,它都面向摄像机;但大小会随着远近而变化。

使物体的右方向始终与摄像机的有方向平行。布告板的顶点坐标根据相机的右方向自动计算,并且布告板始终面向摄像机。如下图所示:

关于布告板的计算

由用户确定布告板的大小和下端中点坐标:

  struct   Billboard
  {
      glm::vec2      _size; // 布告板大小
      glm::vec3      _pos;  // 布告板最下端中点的位置
  };

根据摄像机的右方向计算布告板的四个顶点:

          glm::vec3  faceDir =   camera.Right;
          /// 计算布告板的四个点,根据摄像机camera._right,可以理解为水平(x轴)
          glm::vec3  lb = billboard._pos - faceDir * billboard._size.x * 0.5f;
          glm::vec3  rb = billboard._pos + faceDir * billboard._size.x * 0.5f;
  ​
          /// 计算四个点的下面两个点,上面的点就是下面的,在垂直方向增加一个高度
          glm::vec3  lt = lb + glm::vec3(0, billboard._size.y, 0);
          glm::vec3  rt = rb + glm::vec3(0, billboard._size.y, 0);
  ​
          /// 绘制一个草需要两个三角形,六个点
          struct  ObjectVertex
          {
              glm::vec3      _pos;
              glm::vec2      _uv;
          };
          ObjectVertex transparentVertices[6] =   
          {
              {   lb, glm::vec2(0,1)},
              {   rb, glm::vec2(1,1)},
              {   rt, glm::vec2(1,0)},
  ​
              {   lb, glm::vec2(0,1)},
              {   rt, glm::vec2(1,0)},
              {   lt, glm::vec2(0,0)},
          };

顶点着色器 billboard.vs

  #version 330 core
  layout (location = 0) in vec3 aPos;
  layout (location = 1) in vec2 aTexCoords;
  ​
  out vec2 TexCoords;
  ​
  uniform mat4 model;
  uniform mat4 view;
  uniform mat4 projection;
  ​
  void main()
  {
      TexCoords = aTexCoords;
      gl_Position = projection * view * model * vec4(aPos, 1.0);
  }

片段着色器 billboard.fs

  #version 330 core
  out vec4 FragColor;
  ​
  in vec2 TexCoords;
  ​
  uniform sampler2D texture1;
  ​
  void main()
  {             
      vec4 texColor = texture(texture1, TexCoords);
      if(texColor.a < 0.1)
          discard;
      FragColor = texColor;
  }

完整代码实现

  //#include <glew/glew.h>
  #include <glad/glad.h>
  #include <GLFW/glfw3.h>
  #include <stb_image.h>
  ​
  #include <glm/glm.hpp>
  #include <glm/gtc/matrix_transform.hpp>
  #include <glm/gtc/type_ptr.hpp>
  ​
  #include <learnopengl/shader_m.h>
  #include <learnopengl/camera.h>
  #include <learnopengl/model.h>
  ​
  ​
  #include <iostream>
  ​
  void framebuffer_size_callback(GLFWwindow* window, int width, int height);
  void mouse_callback(GLFWwindow* window, double xpos, double ypos);
  void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
  void processInput(GLFWwindow *window);
  unsigned int loadTexture(const char *path);
  ​
  // settings
  const unsigned int SCR_WIDTH = 800;
  const unsigned int SCR_HEIGHT = 600;
  ​
  // camera
  Camera camera(glm::vec3(0.0f, 1.0f, 10.0f));
  float lastX = (float)SCR_WIDTH / 2.0;
  float lastY = (float)SCR_HEIGHT / 2.0;
  bool firstMouse = true;
  ​
  // timing
  float deltaTime = 0.0f;
  float lastFrame = 0.0f;
  ​
  // lighting
  glm::vec3 lightPos(10.0f, 5.0f, 10.0f);
  ​
  struct  ObjectVertex
  {
      glm::vec3      _pos;
      glm::vec2      _uv;
  };
  ​
  struct   Billboard
  {
      glm::vec2      _size; // 布告板大小
      glm::vec3      _pos;  // 布告板最下端中点的位置
  };
  ​
  int main()
  {
      // glfw: initialize and configure
      // ------------------------------
      glfwInit();
      glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
      glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
      glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  ​
  #ifdef __APPLE__
      glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  #endif
  ​
      // glfw window creation
      // --------------------
      GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
      if (window == NULL)
      {
          std::cout << "Failed to create GLFW window" << std::endl;
          glfwTerminate();
          return -1;
      }
      glfwMakeContextCurrent(window);
      glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
      glfwSetCursorPosCallback(window, mouse_callback);
      glfwSetScrollCallback(window, scroll_callback);
  ​
      // tell GLFW to capture our mouse
      //glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  ​
      // glad: load all OpenGL function pointers
      // ---------------------------------------
      if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
      {
          std::cout << "Failed to initialize GLAD" << std::endl;
          return -1;
      }
  ​
      // configure global opengl state
      // -----------------------------
      glEnable(GL_DEPTH_TEST);
  ​
      // build and compile shaders
      // -------------------------
      Shader lightingShader("lighting_maps.vs", "lighting_maps.fs");
      Shader billboardShader("billboard.vs", "billboard.fs");
  ​
      // set up vertex data (and buffer(s)) and configure vertex attributes
      // ------------------------------------------------------------------
      float planeVertices[] = {
          // positions           // normals           // texture Coords 
          5.0f,  0.0f,  5.0f,    0.0f,  0.0f,  1.0f,  2.0f, 0.0f,
          -5.0f, 0.0f,  5.0f,   0.0f,  0.0f,  1.0f,  0.0f, 0.0f,
          -5.0f, 0.0f, -5.0f,   0.0f,  0.0f,  1.0f,  0.0f, 2.0f,
  ​
          5.0f, 0.0f,  5.0f,    0.0f,  0.0f,  1.0f,  2.0f, 0.0f,
          -5.0f,0.0f, -5.0f,   0.0f,  0.0f,  1.0f,  0.0f, 2.0f,
          5.0f, 0.0f, -5.0f,    0.0f,  0.0f,  1.0f,  2.0f, 2.0f
      };
  ​
      // plane VAO
      unsigned int planeVAO, planeVBO;
      glGenVertexArrays(1, &planeVAO);
      glGenBuffers(1, &planeVBO);
      glBindVertexArray(planeVAO);
      glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
      glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
  ​
      glBindVertexArray(planeVAO);
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
      glEnableVertexAttribArray(0);
      glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
      glEnableVertexAttribArray(1);
      glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
      glEnableVertexAttribArray(2);
      // transparent VAO
      unsigned int transparentVAO, transparentVBO;
      glGenVertexArrays(1, &transparentVAO);
      glGenBuffers(1, &transparentVBO);
  ​
      // load textures
      // -------------
      unsigned int floorTexture = loadTexture("resources/textures/metal.png");
      unsigned int transparentTexture = loadTexture("resources/textures/grass.png");
  ​
      // transparent vegetation locations
      // --------------------------------
      vector<glm::vec3> vegetation;
      float height  =   0.0f;
      for (float x = -2 ; x < 2 ; x += 1)
      {
          for (float z = -2 ; z < 2 ; z += 1)
          {
              vegetation.push_back(glm::vec3(x,height,z));
          }
      }
  ​
      // shader configuration
      // --------------------
      lightingShader.use(); 
      lightingShader.setInt("material.diffuse", 0);
  ​
      billboardShader.use();
      billboardShader.setInt("texture1", 0);
  ​
      // 
      Billboard billboard;
      billboard._size = glm::vec2(1.0f, 1.0f); 
      billboard._pos = glm::vec3(0.0f,  0.0f,  0.0f);
  ​
      // render loop
      // -----------
      while (!glfwWindowShouldClose(window))
      {
          // per-frame time logic
          // --------------------
          float currentFrame = static_cast<float>(glfwGetTime());
          deltaTime = currentFrame - lastFrame;
          lastFrame = currentFrame;
  ​
          // input
          // -----
          processInput(window);
  ​
          // render
          // ------
          glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  ​
          // draw objects
          lightingShader.use();
          lightingShader.setVec3("light.position", lightPos);
          lightingShader.setVec3("viewPos", camera.Position);
  ​
          // light properties
          lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f); 
          lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);
          lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
  ​
          // material properties
          lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
          lightingShader.setFloat("material.shininess", 64.0f);
  ​
          // view/projection transformations
          glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
          glm::mat4 view = camera.GetViewMatrix();
          lightingShader.setMat4("projection", projection);
          lightingShader.setMat4("view", view);
  ​
          // world transformation
          glm::mat4 model = glm::mat4(1.0f);
          lightingShader.setMat4("model", model);
  ​
          // bind diffuse map
          glEnable(GL_TEXTURE_2D);
          glBindTexture(GL_TEXTURE_2D, floorTexture);
  ​
          // render the floor
          glBindVertexArray(planeVAO);
          glDrawArrays(GL_TRIANGLES, 0, 6);
          glBindVertexArray(0);
  ​
          // vegetation   
          billboardShader.use();
          glm::mat4 projection2 = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
          glm::mat4 view2 = camera.GetViewMatrix();
          glm::mat4 model2 = glm::mat4(1.0f);
          billboardShader.setMat4("projection", projection2);
          billboardShader.setMat4("view", view2);
          glm::vec3  faceDir =   camera.Right;
          /// 计算布告板的四个点,根据摄像机camera._right,可以理解为水平(x轴)
          glm::vec3  lb      =   billboard._pos - faceDir * billboard._size.x * 0.5f;
          glm::vec3  rb      =   billboard._pos + faceDir * billboard._size.x * 0.5f;
  ​
          /// 计算四个点的下面两个点,上面的点就是下面的,在垂直方向增加一个高度
          glm::vec3  lt = lb + glm::vec3(0, billboard._size.y, 0);
          glm::vec3  rt = rb + glm::vec3(0, billboard._size.y, 0);
  ​
          /// 绘制一个草需要两个三角形,六个点
          ObjectVertex transparentVertices[6] =   
          {
              {   lb, glm::vec2(0,1)},
              {   rb, glm::vec2(1,1)},
              {   rt, glm::vec2(1,0)},
  ​
              {   lb, glm::vec2(0,1)},
              {   rt, glm::vec2(1,0)},
              {   lt, glm::vec2(0,0)},
          };
  ​
          glBindVertexArray(transparentVAO);
          glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);
  ​
          glBufferData(GL_ARRAY_BUFFER, sizeof(transparentVertices), transparentVertices, GL_STATIC_DRAW);
          glEnableVertexAttribArray(0);
          glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
          glEnableVertexAttribArray(1);
          glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
          glBindVertexArray(0);
  ​
          glBindVertexArray(transparentVAO);  
          glEnable(GL_TEXTURE_2D);
          glBindTexture(GL_TEXTURE_2D, transparentTexture);
  ​
          for (unsigned int i = 0; i < vegetation.size(); i++)
          {
              model2 = glm::mat4(1.0f);
              model2 = glm::translate(model2, vegetation[i]);
              billboardShader.setMat4("model", model2);
              glDrawArrays(GL_TRIANGLES, 0, 6);
          }
  ​
          // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
          // -------------------------------------------------------------------------------
          glfwSwapBuffers(window);
          glfwPollEvents();
      }
  ​
      // optional: de-allocate all resources once they've outlived their purpose:
      // ------------------------------------------------------------------------
      glDeleteVertexArrays(1, &planeVAO);
      glDeleteBuffers(1, &planeVBO);
  ​
      glfwTerminate();
      return 0;
  }
  ​
  // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
  // ---------------------------------------------------------------------------------------------------------
  void processInput(GLFWwindow *window)
  {
      if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
          glfwSetWindowShouldClose(window, true);
  ​
      if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
          camera.ProcessKeyboard(FORWARD, deltaTime);
      if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
          camera.ProcessKeyboard(BACKWARD, deltaTime);
      if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
          camera.ProcessKeyboard(LEFT, deltaTime);
      if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
          camera.ProcessKeyboard(RIGHT, deltaTime);
  }
  ​
  // glfw: whenever the window size changed (by OS or user resize) this callback function executes
  // ---------------------------------------------------------------------------------------------
  void framebuffer_size_callback(GLFWwindow* window, int width, int height)
  {
      // make sure the viewport matches the new window dimensions; note that width and 
      // height will be significantly larger than specified on retina displays.
      glViewport(0, 0, width, height);
  }
  ​
  // glfw: whenever the mouse moves, this callback is called
  // -------------------------------------------------------
  void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
  {
      float xpos = static_cast<float>(xposIn);
      float ypos = static_cast<float>(yposIn);
      if (firstMouse)
      {
          lastX = xpos;
          lastY = ypos;
          firstMouse = false;
      }
  ​
      float xoffset = xpos - lastX;
      float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
  ​
      lastX = xpos;
      lastY = ypos;
  ​
      camera.ProcessMouseMovement(xoffset, yoffset);
  }
  ​
  // glfw: whenever the mouse scroll wheel scrolls, this callback is called
  // ----------------------------------------------------------------------
  void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
  {
      camera.ProcessMouseScroll(static_cast<float>(yoffset));
  }
  ​
  // utility function for loading a 2D texture from file
  // ---------------------------------------------------
  unsigned int loadTexture(char const * path)
  {
      unsigned int textureID;
      glGenTextures(1, &textureID);
  ​
      int width, height, nrComponents;
      unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
      if (data)
      {
          GLenum format;
          if (nrComponents == 1)
              format = GL_RED;
          else if (nrComponents == 3)
              format = GL_RGB;
          else if (nrComponents == 4)
              format = GL_RGBA;
  ​
          glBindTexture(GL_TEXTURE_2D, textureID);
          glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
          glGenerateMipmap(GL_TEXTURE_2D);
  ​
          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 
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  ​
          stbi_image_free(data);
      }
      else
      {
          std::cout << "Texture failed to load at path: " << path << std::endl;
          stbi_image_free(data);
      }
  ​
      return textureID;
  }
  ​

完整项目代码

https://github.com/mc-liyanliang/OpenGL-Shader/tree/master

相关文章

  • 使用OpenXML SDK往PPT中指定的幻灯片插入表格使用OpenXML SDK往PPT中指定的幻灯片插入表格
  • 给不规则的多边形闭合区域填充颜色给不规则的多边形闭合区域填充颜色
  • LearnOpenGL脑图汇总LearnOpenGL脑图汇总
  • OpenGL雾化效果实现-每像素雾化OpenGL雾化效果实现-每像素雾化
  • ODA的基本操作-平移、旋转、矩阵变换ODA的基本操作-平移、旋转、矩阵变换
  • ObjectARX开发视频教程(C++)-创建模态对话框绘制直线ObjectARX开发视频教程(C++)-创建模态对话框绘制直线

OpenGL
BillBoard

Post navigation

PREVIOUS
算法:寻找异常数字
NEXT
OpenGL雾化效果实现-每像素雾化

发表回复 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注

近期文章

  • ANR崩溃日志查看方法
  • 通过数学方法来计算short类型的变量w的低八位x和高八位
  • 3dTiles数据解析
  • Games101和Games202脑图汇总
  • LearnOpenGL脑图汇总
  • IBL计算总结
  • C++实现一个简单的语言解释器
  • OpenGL-法线贴图(Normal Mapping)
  • OpenGL-卡通着色(Cartoon)
  • OpenGL几何着色器实现贝塞尔曲线
  • WinDbg检查内存泄漏
  • OpenGL雾化效果实现-每像素雾化
  • OpenGL实现billboard效果(CPU)
  • 算法:寻找异常数字
  • OpenGL 几何着色器的应用
  • Midas XD-构件详图开发
  • Midas XD-选筋助手开发
  • Civil Designer开发-检测规范自动生成控制截面
  • Civil Designer开发-公路桥梁承载能力检算评定
  • Midas W-满堂支架快速建模助手开发

全站热点

  • C++编写的情人节小程序 (2,083)
  • 提取最小封闭区域 (1,696)
  • Modern OpenGL绘制圆柱体 (1,607)
  • OpenGL开发环境搭建-GLFW与GLAD配置 超详细 (1,465)
  • 截面特性计算程序-附源码 (1,290)
  • OpenGL绘制旋转立方体 (1,110)
  • 判断一个点是否在闭合区域内 (1,032)
  • WordPress分页插件 – WP-PageNavi的使用(替换现有脚本) (948)
  • OpenGL实现billboard效果(CPU) (865)
  • Midas W-满堂支架快速建模助手开发 (837)
  • 从DLL中动态加载一个函数:LoadLibrary和GetProcAddress的使用 (748)
  • Midas XD [错误] 右侧挡土墙的最下端深度必须小于地基的最下端深度 (709)
  • 两跨连续梁影响线绘制-附源码 (686)
  • 土木想往土木软件开发方向发展,应该如何准备 (680)
  • OpenGL几何着色器实现贝塞尔曲线 (664)
  • 通过Spy++抓取窗口以查询对话框id (613)
  • 使用ODA数据库出现 “ODA_ASSUME”: 找不到标识符的错误 (547)
  • #pragma message 编译时提示信息 (527)
  • OpenGL雾化效果实现-每像素雾化 (508)
  • midas XD2020的开发 (476)

分类

  • C# (3)
  • C++ (19)
  • GIS (1)
  • MFC (3)
  • ObjectARX (2)
  • OpenGL (11)
  • Revit开发 (1)
  • 学习笔记 (2)
  • 岩土 (2)
  • 算法 (1)
  • 结构设计 (7)
  • 职场生涯 (1)
  • 计算几何 (3)

归档

  • 2024 年 12 月 (1)
  • 2024 年 10 月 (1)
  • 2024 年 9 月 (1)
  • 2023 年 3 月 (2)
  • 2022 年 10 月 (1)
  • 2022 年 3 月 (1)
  • 2022 年 2 月 (1)
  • 2022 年 1 月 (5)
  • 2021 年 11 月 (7)
  • 2021 年 6 月 (3)
  • 2021 年 5 月 (2)
  • 2021 年 3 月 (2)
  • 2021 年 2 月 (8)
  • 2021 年 1 月 (18)

标签

3dtiles anr Bezier Curves BillBoard C++ CDN CivilDesigner DLL EasyX fog glTF MFC Midas W Midas XD NormalMapping ObjectARX ODA OpenGL OpenXML Open XML PBR revit WinDbg 基坑设计 影响线 截面特性 桥梁 桥梁检测 桥梁设计 算法 计算几何 设计模式

书签

  • 李燕良的CSDN
  • 崔济东的博客
  • C++爱好者博客
  • 陈学伟的博客
  • 贾苏的博客
  • 陈睦锋的博客
  • 孙勇的博客

统计

  • 0
  • 204
  • 103
  • 388
  • 145
  • 268,907
  • 77,790

实时访问地域

© 2025   liyanliang.net Copyright. All Rights Reserved.