C++ OpenGL实现球形的绘制
作者:代码骑士
这篇文章主要主要为大家详细介绍了如何利用C++和OpenGL实现球形的绘制,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起动手尝试一下
1、封装Shader
(1)为什么要封装Shader
封装后,在应对存在对个着色器程序的渲染流程时,可以更方便使用不同的着色器程序,同时也可以降低代码冗余。
(2)如何使用
如下,传入参数分别为顶点着色器和片元着色器的路径,在封装了Shader类之后,我们就可以通过一行代码去创建一个新的着色器对象:
Shader shader("res/shader/task3.vs","res/shader/task3.fs");
假如我们在绘制时需要切换到某个着色器并使用它,我们仅需要一行代码:
shader.Use();
假如我们需要向着色器传入一种类型的值,我们也仅需要一行代码去解决它(name是着色器中的名称,value为你希望设置的值):
SetFloat(string &name,float value)
2、绘制球模型
(1)球面顶点遍历
//生成球的顶点 for(int y = 0; y <=Y_SEGMENTS; y++) { for(int x = 0; x <= X_SEGMENTS; x++) { float xSegment = (float)x/(float)X_SEGMENTS; float ySegment = (float)y/(float)Y_SEGMENTS; float xPos = std::cos(xSegment*2.0f*PI)*std::sin(ySement*PI); float yPos = std::cos(ySegment*PI); float zPos = std::sin(xSegment*2.0f*PI)*std::sin(ySement*PI); sphereVertices.push_back(x_Pos); sphereVertices.push_back(y_Pos); sphereVertices.push_back(z_Pos); } }
(2)构造三角形图元
//根据球面上每一点的坐标,去构造一个一个三角形顶点数组 for(int i=0; i<Y_SEGMENTS;i++) { for(int j=0; j<X_SEGMENTS;j++) { sphereIndices.push_back(i*(X_SEGMENTS+1)+j); sphereIndices.push_back((i+1)*(X_SEGMENTS+1)+j); sphereIndices.push_back((i+1)*(X_SEGMENTS+1)+j+1); sphereIndices.push_back(i*(X_SEGMENTS+1)+j); sphereIndices.push_back((i+1)*(X_SEGMENTS+1)+j+1); sphereIndices.push_back(i*(X_SEGMENTS+1)+j+1); } }
(3)开启线框模式
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);//使用线框模式绘制
(4)开启面剔除
//开启面剔除(只需要展示一个面,否则会有重合) glEnable(GL_CULL_FACE); glCUllFace(GL_BACK);
(5)最后
项目工程文件结构:
shader.h
/*** * 例程 绘制球体 (MAKE后运行时可删除ALL_BUILD,也可以将Task-sphere设为默认启动工程) * 步骤: * 1-初始化: GLFW窗口,GLAD。 * 2-计算球体顶点:通过数学方法计算球体的每个顶点坐标 * 2-数据处理: 通过球体顶点坐标构造三角形网格,生成并绑定VAO&VBO&EBO(准备在GPU中进行处理),设置顶点属性指针(本质上就是告诉OpenGL如何处理数据)。 * 3-着色器: 给出顶点和片段着色器,然后链接为着色器程序,渲染时使用着色器程序。 * 4-渲染: 使用画线模式画圆,开启面剔除,剔除背面,使用线框模式画球 * 5-结束: 清空缓冲,交换缓冲区检查触发事件后释放资源 */ #include <glad/glad.h> #include <GLFW/glfw3.h> #include <shader.h> #include <iostream> #include <math.h> #include <vector> const unsigned int screen_width = 780; const unsigned int screen_height = 780; const GLfloat PI = 3.14159265358979323846f; //将球横纵划分成50X50的网格 const int Y_SEGMENTS = 50; const int X_SEGMENTS = 50; int main() { // 初始化GLFW glfwInit(); // 初始化GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL版本为3.3,主次版本号均设为3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式(无需向后兼容性) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 如果使用的是Mac OS X系统,需加上这行 glfwWindowHint(GLFW_RESIZABLE, FALSE); // 不可改变窗口大小 // 创建窗口(宽、高、窗口名称) auto window = glfwCreateWindow(screen_width, screen_height, "Sphere", nullptr, nullptr); if (window == nullptr) { // 如果窗口创建失败,输出Failed to Create OpenGL Context std::cout << "Failed to Create OpenGL Context" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // 将窗口的上下文设置为当前线程的主上下文 // 初始化GLAD,加载OpenGL函数指针地址的函数 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } // 指定当前视口尺寸(前两个参数为左下角位置,后两个参数是渲染窗口宽、高) glViewport(0, 0, screen_width, screen_height); Shader shader("res/shader/task3.vs", "res/shader/task3.fs");//加载着色器 std::vector<float> sphereVertices; std::vector<int> sphereIndices; // 生成球的顶点 for (int y = 0; y <= Y_SEGMENTS; y++) { for (int x = 0; x <= X_SEGMENTS; x++) { float xSegment = (float)x / (float)X_SEGMENTS; float ySegment = (float)y / (float)Y_SEGMENTS; float xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI); float yPos = std::cos(ySegment * PI); float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI); sphereVertices.push_back(xPos); sphereVertices.push_back(yPos); sphereVertices.push_back(zPos); } } // 生成球的Indices for (int i = 0; i < Y_SEGMENTS; i++) { for (int j = 0; j < X_SEGMENTS; j++) { sphereIndices.push_back(i * (X_SEGMENTS+1) + j); sphereIndices.push_back((i + 1) * (X_SEGMENTS + 1) + j); sphereIndices.push_back((i + 1) * (X_SEGMENTS + 1) + j + 1); sphereIndices.push_back(i * (X_SEGMENTS + 1) + j); sphereIndices.push_back((i + 1) * (X_SEGMENTS + 1) + j + 1); sphereIndices.push_back(i * (X_SEGMENTS + 1) + j + 1); } } // 球 unsigned int VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); //生成并绑定球体的VAO和VBO glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // 将顶点数据绑定至当前默认的缓冲中 glBufferData(GL_ARRAY_BUFFER, sphereVertices.size() * sizeof(float), &sphereVertices[0], GL_STATIC_DRAW); GLuint element_buffer_object; //EBO glGenBuffers(1, &element_buffer_object); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_object); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sphereIndices.size() * sizeof(int), &sphereIndices[0], GL_STATIC_DRAW); // 设置顶点属性指针 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 解绑VAO和VBO glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // 渲染循环 while (!glfwWindowShouldClose(window)) { // 清空颜色缓冲 glClearColor(0.0f, 0.34f, 0.57f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); shader.Use(); //绘制球 //开启面剔除(只需要展示一个面,否则会有重合) glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glBindVertexArray(VAO); //使用线框模式绘制 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDrawElements(GL_TRIANGLES, X_SEGMENTS*Y_SEGMENTS * 6, GL_UNSIGNED_INT, 0); //点阵模式绘制 //glPointSize(5); //glDrawElements(GL_POINTS, X_SEGMENTS*Y_SEGMENTS*6, GL_UNSIGNED_INT, 0); //交换缓冲并且检查是否有触发事件(比如键盘输入、鼠标移动等) glfwSwapBuffers(window); glfwPollEvents(); } // 删除VAO和VBO,EBO glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &element_buffer_object); // 清理所有的资源并正确退出程序 glfwTerminate(); return 0; }
shader.cpp
#include "Shader.h" #include "fstream" #include "sstream" #include "iostream" Shader::Shader(const GLchar* vertex_shader_path, const GLchar* fragment_shader_path) { std::string vertex_shader_code; std::string fragment_shader_code; if (GetShaderFromFile(vertex_shader_path, fragment_shader_path, &vertex_shader_code, &fragment_shader_code)) { return; } if (LinkShader(vertex_shader_code.c_str(), fragment_shader_code.c_str())) { return; } } Shader::~Shader() { } void Shader::Use() { glUseProgram(ID); } void Shader::SetBool(const std::string &name, bool value) const { SetInt(name, (int)value); } void Shader::SetInt(const std::string &name, int value) const { glUniform1i(GetUniform(name), value); } void Shader::SetFloat(const std::string &name, float value) const { glUniform1f(GetUniform(name), value); } void Shader::SetVec2(const std::string &name, float x, float y) const { glUniform2f(GetUniform(name), x, y); } void Shader::SetVec2(const std::string &name, const glm::vec2 &value) const { SetVec2(name, value.x, value.y); } void Shader::SetVec3(const std::string &name, float x, float y, float z) const { glUniform3f(GetUniform(name), x, y, z); } void Shader::SetVec3(const std::string &name, const glm::vec3 &value) const { SetVec3(name, value.x, value.y, value.z); } void Shader::SetVec4(const std::string &name, float x, float y, float z, float w) const { glUniform4f(GetUniform(name), x, y, z, w); } void Shader::SetVec4(const std::string &name, const glm::vec4 &value) const { SetVec4(name, value.x, value.y, value.z, value.w); } void Shader::SetMat2(const std::string &name, const glm::mat2 &value) const { glUniformMatrix2fv(GetUniform(name), 1, GL_FALSE, &value[0][0]); } void Shader::SetMat3(const std::string &name, const glm::mat3 &value) const { glUniformMatrix3fv(GetUniform(name), 1, GL_FALSE, &value[0][0]); } void Shader::SetMat4(const std::string &name, const glm::mat4 &value) const { glUniformMatrix4fv(GetUniform(name), 1, GL_FALSE, &value[0][0]); } int Shader::GetShaderFromFile(const GLchar* vertex_shader_path, const GLchar* fragment_shader_path, std::string *vertex_shader_code, std::string *fragment_shader_code) { std::ifstream vertex_shader_file; std::ifstream fragment_shader_file; vertex_shader_file.exceptions(std::ifstream::badbit | std::ifstream::failbit); fragment_shader_file.exceptions(std::ifstream::badbit | std::ifstream::failbit); try { vertex_shader_file.open(vertex_shader_path); fragment_shader_file.open(fragment_shader_path); std::stringstream vertex_shader_stream, fragment_shader_stream; vertex_shader_stream << vertex_shader_file.rdbuf(); fragment_shader_stream << fragment_shader_file.rdbuf(); vertex_shader_file.close(); fragment_shader_file.close(); *vertex_shader_code = vertex_shader_stream.str(); *fragment_shader_code = fragment_shader_stream.str(); } catch (std::ifstream::failure e) { std::cout << "Load Shader File Error!" << std::endl; return -1; } return 0; } int Shader::LinkShader(const char* vertex_shader_code, const char* fragment_shader_code) { int vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex_shader, 1, &vertex_shader_code, NULL); glCompileShader(vertex_shader); CheckCompileErrors(vertex_shader, "VERTEX"); int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_shader, 1, &fragment_shader_code, NULL); glCompileShader(fragment_shader); CheckCompileErrors(fragment_shader, "FRAGMENT"); this->ID = glCreateProgram(); glAttachShader(ID, vertex_shader); glAttachShader(ID, fragment_shader); glLinkProgram(ID); CheckCompileErrors(ID, "PROGRAM"); glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); return 0; } int Shader::GetUniform(const std::string &name) const { int position = glGetUniformLocation(ID, name.c_str()); if (position == -1) { std::cout << "uniform " << name << " set failed!" << std::endl; } return position; } void Shader::CheckCompileErrors(GLuint shader, std::string type) { GLint success; GLchar infoLog[512]; if (type == "PROGRAM") { glGetProgramiv(shader, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shader, 512, NULL, infoLog); std::cout << "ERROR::PROGRAM_LINKING_ERROR!\n" << infoLog << std::endl; } } else { glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::" << type << "::COMPILATION_FAILED\n" << infoLog << std::endl; } } }
main.cpp
/*** * 例程 绘制球体 (MAKE后运行时可删除ALL_BUILD,也可以将Task-sphere设为默认启动工程) * 步骤: * 1-初始化: GLFW窗口,GLAD。 * 2-计算球体顶点:通过数学方法计算球体的每个顶点坐标 * 2-数据处理: 通过球体顶点坐标构造三角形网格,生成并绑定VAO&VBO&EBO(准备在GPU中进行处理),设置顶点属性指针(本质上就是告诉OpenGL如何处理数据)。 * 3-着色器: 给出顶点和片段着色器,然后链接为着色器程序,渲染时使用着色器程序。 * 4-渲染: 使用画线模式画圆,开启面剔除,剔除背面,使用线框模式画球 * 5-结束: 清空缓冲,交换缓冲区检查触发事件后释放资源 */ #include <glad/glad.h> #include <GLFW/glfw3.h> #include <shader.h> #include <iostream> #include <math.h> #include <vector> const unsigned int screen_width = 780; const unsigned int screen_height = 780; const GLfloat PI = 3.14159265358979323846f; //将球横纵划分成50X50的网格 const int Y_SEGMENTS = 50; const int X_SEGMENTS = 50; int main() { // 初始化GLFW glfwInit(); // 初始化GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL版本为3.3,主次版本号均设为3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式(无需向后兼容性) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 如果使用的是Mac OS X系统,需加上这行 glfwWindowHint(GLFW_RESIZABLE, FALSE); // 不可改变窗口大小 // 创建窗口(宽、高、窗口名称) auto window = glfwCreateWindow(screen_width, screen_height, "Sphere", nullptr, nullptr); if (window == nullptr) { // 如果窗口创建失败,输出Failed to Create OpenGL Context std::cout << "Failed to Create OpenGL Context" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // 将窗口的上下文设置为当前线程的主上下文 // 初始化GLAD,加载OpenGL函数指针地址的函数 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } // 指定当前视口尺寸(前两个参数为左下角位置,后两个参数是渲染窗口宽、高) glViewport(0, 0, screen_width, screen_height); Shader shader("res/shader/task3.vs", "res/shader/task3.fs");//加载着色器 std::vector<float> sphereVertices; std::vector<int> sphereIndices; // 生成球的顶点 for (int y = 0; y <= Y_SEGMENTS; y++) { for (int x = 0; x <= X_SEGMENTS; x++) { float xSegment = (float)x / (float)X_SEGMENTS; float ySegment = (float)y / (float)Y_SEGMENTS; float xPos = std::cos(xSegment * 2.0f * PI) * std::sin(ySegment * PI); float yPos = std::cos(ySegment * PI); float zPos = std::sin(xSegment * 2.0f * PI) * std::sin(ySegment * PI); sphereVertices.push_back(xPos); sphereVertices.push_back(yPos); sphereVertices.push_back(zPos); } } // 生成球的Indices for (int i = 0; i < Y_SEGMENTS; i++) { for (int j = 0; j < X_SEGMENTS; j++) { sphereIndices.push_back(i * (X_SEGMENTS+1) + j); sphereIndices.push_back((i + 1) * (X_SEGMENTS + 1) + j); sphereIndices.push_back((i + 1) * (X_SEGMENTS + 1) + j + 1); sphereIndices.push_back(i * (X_SEGMENTS + 1) + j); sphereIndices.push_back((i + 1) * (X_SEGMENTS + 1) + j + 1); sphereIndices.push_back(i * (X_SEGMENTS + 1) + j + 1); } } // 球 unsigned int VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); //生成并绑定球体的VAO和VBO glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // 将顶点数据绑定至当前默认的缓冲中 glBufferData(GL_ARRAY_BUFFER, sphereVertices.size() * sizeof(float), &sphereVertices[0], GL_STATIC_DRAW); GLuint element_buffer_object; //EBO glGenBuffers(1, &element_buffer_object); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_object); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sphereIndices.size() * sizeof(int), &sphereIndices[0], GL_STATIC_DRAW); // 设置顶点属性指针 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 解绑VAO和VBO glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // 渲染循环 while (!glfwWindowShouldClose(window)) { // 清空颜色缓冲 glClearColor(0.0f, 0.34f, 0.57f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); shader.Use(); //绘制球 //开启面剔除(只需要展示一个面,否则会有重合) glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glBindVertexArray(VAO); //使用线框模式绘制 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDrawElements(GL_TRIANGLES, X_SEGMENTS*Y_SEGMENTS * 6, GL_UNSIGNED_INT, 0); //点阵模式绘制 //glPointSize(5); //glDrawElements(GL_POINTS, X_SEGMENTS*Y_SEGMENTS*6, GL_UNSIGNED_INT, 0); //交换缓冲并且检查是否有触发事件(比如键盘输入、鼠标移动等) glfwSwapBuffers(window); glfwPollEvents(); } // 删除VAO和VBO,EBO glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &element_buffer_object); // 清理所有的资源并正确退出程序 glfwTerminate(); return 0; }
输出结果:
以上就是C++ OpenGL实现球形的绘制的详细内容,更多关于C++ OpenGL绘制球的资料请关注脚本之家其它相关文章!