大家好,又见面了,我是你们的朋友全栈君。

前言:imgui 是一个开源的GUI框架,自带的例子里面直接集成了glfw+gl3w环境,本例使用的版本是imgui v1.61,下载地址:https://github.com/ocornut/imgui/tags


本节要点:

1. OpenGL图形渲染管线
2. OpenGL着色器语言(OpenGL Shading Language, GLSL)
3. 顶点数组对象:Vertex Array Object,VAO
4. 顶点缓冲对象:Vertex Buffer Object,VBO

运行效果:
现代OpenGL教程(一):绘制三角形(imgui+OpenGL3.3)


OpenGL图形渲染管线、shader和GLSL

现代OpenGL教程(一):绘制三角形(imgui+OpenGL3.3)

OpenGL的图形渲染管线的作用是将3D坐标转为能显示在屏幕上有色2D像素数组,主要由两部分组成:把3D坐标转换为2D坐标,把2D坐标转变为实际的有颜色的像素。上图的每一个阶段都能够被GPU的一种特定的小程序(即shader,着色器)并行执行,从而实现数据的快速处理。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的。现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)。GLSL必须在运行时编译,每次启动程序时,所有的着色器将重新编译。

定义一个三角形

static const GLfloat g_vertex_buffer_data[] = { 
   
   -1.0f, -1.0f, 0.0f,
   1.0f, -1.0f, 0.0f,
   0.0f,  1.0f, 0.0f,
};

顶点数组对象(VAO)

创建一个VAO,注意:这一步必须在其他OpenGL调用前完成。

GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);

顶点缓冲对象(VBO)

就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象。着色器和缓冲对象一样不能直接访问,我们仅拥有其ID,其真正的实现隐藏在驱动程序中。OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。

sizeof()
//定义顶点缓冲,并将顶点缓冲传给OpenGL
GLuint vertexbuffer;
glGenBuffers(1, &vertexbuffer);
// 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

现在已经把顶点数据储存在显卡的内存中,用vertexbuffer这个顶点缓冲对象来管理这些顶点数据。


完整代码

顶点着色器代码:SimpleVertexShader.vertexshader

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

void main(){ 
   
    gl_Position.xyz = vertexPosition_modelspace;
    gl_Position.w = 1.0;
}

片段着色器代码:SimpleFragmentShader.fragmentshader

#version 330 core

// Ouput data
out vec3 color;

void main()
{ 
   
	// Output color = red 
	color = vec3(1,0,0);
}

shader.hpp

#ifndef SHADER_HPP
#define SHADER_HPP

GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path);

#endif

shader.cpp

#include <stdio.h>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <sstream>
using namespace std;
#include <stdlib.h>
#include <string.h>
#include <GL/gl3w.h>
#include "shader.hpp"
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){ 

// Create the shaders
GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
// Read the Vertex Shader code from the file
std::string VertexShaderCode;
std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
if(VertexShaderStream.is_open()){ 

std::stringstream sstr;
sstr << VertexShaderStream.rdbuf();
VertexShaderCode = sstr.str();
VertexShaderStream.close();
}else{ 

printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !\n", vertex_file_path);
getchar();
return 0;
}
// Read the Fragment Shader code from the file
std::string FragmentShaderCode;
std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
if(FragmentShaderStream.is_open()){ 

std::stringstream sstr;
sstr << FragmentShaderStream.rdbuf();
FragmentShaderCode = sstr.str();
FragmentShaderStream.close();
}
GLint Result = GL_FALSE;
int InfoLogLength;
// Compile Vertex Shader
printf("Compiling shader : %s\n", vertex_file_path);
char const * VertexSourcePointer = VertexShaderCode.c_str();
glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
glCompileShader(VertexShaderID);
// Check Vertex Shader
glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){ 

std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
printf("%s\n", &VertexShaderErrorMessage[0]);
}
// Compile Fragment Shader
printf("Compiling shader : %s\n", fragment_file_path);
char const * FragmentSourcePointer = FragmentShaderCode.c_str();
glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
glCompileShader(FragmentShaderID);
// Check Fragment Shader
glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){ 

std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
printf("%s\n", &FragmentShaderErrorMessage[0]);
}
// Link the program
printf("Linking program\n");
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
glLinkProgram(ProgramID);
// Check the program
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){ 

std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
printf("%s\n", &ProgramErrorMessage[0]);
}
glDetachShader(ProgramID, VertexShaderID);
glDetachShader(ProgramID, FragmentShaderID);
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
return ProgramID;
}

主程序代码:main.cpp

#include "imgui.h"
#include "imgui_impl_glfw_gl3.h"
#include <stdio.h>
#include <GL/gl3w.h> // 使用gl3w,glad也行,注意要在项目工程中添加gl3w.c(或者glad.c/使用glad)
#include <GLFW/glfw3.h>
#include <iostream>
#include <common/shader.hpp>
void window_size_callback(GLFWwindow *window, int width, int height);
// 设置窗口大小
const unsigned int Window_width = 1600;
const unsigned int Window_height = 1200;
int main()
{ 

// 实例化GLFW窗口
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//下面这条语句是为了适应苹果系统
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// 创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到。
// 此外增加 if (window == NULL) 判断窗口是否创建成功
GLFWwindow *window = glfwCreateWindow(Window_width, Window_height, "ImGui Triangle", NULL, NULL);
if (window == NULL)
{ 

std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, window_size_callback);
glfwSwapInterval(1);
//初始化gl3w
gl3wInit();
//创建并绑定ImGui
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
(void)io;
ImGui_ImplGlfwGL3_Init(window, true);
ImGui::StyleColorsDark();
//初始化各种数据
bool ImGui = true;
bool the_same_color = false;
bool draw_trangle_without_render = false;
bool draw_trangle = false;
bool bonus_draw_line = false;
bool bonus_draw_another_trangle = false;
unsigned int VBO, VAO, EBO;
bool show_demo_window = true;
//创建一个VAO,并将它设为当前对象
GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
//绑定顶点数组对象
glBindVertexArray(VertexArrayID);
// 加载shader文件,创建并编译GLSL程序
GLuint programID = LoadShaders("SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader");
ImVec4 v1 = ImVec4(-0.25f, -0.25f, 0.0f, 1.00f);
ImVec4 v2 = ImVec4(0.25f, -0.25f, 0.0f, 1.00f);
ImVec4 v3 = ImVec4(0.0f, 0.25f, 0.0f, 1.00f);
//定义顶点缓冲,并将顶点缓冲传给OpenGL
GLuint vertexbuffer;
// 渲染循环
while (!glfwWindowShouldClose(window))
{ 

GLfloat g_vertex_buffer_data[] = { 

v1.x,
v1.y,
v1.z,
v2.x,
v2.y,
v2.z,
v3.x,
v3.y,
v3.z,
};
glGenBuffers(1, &vertexbuffer);
// 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
// 创建ImGui
glfwPollEvents();
ImGui_ImplGlfwGL3_NewFrame();
ImGui::Begin("change vertex", &ImGui, ImGuiWindowFlags_MenuBar);
ImGui::SliderFloat("", &v3.y, -1.0f, 1.0f, "v3.y = %.3f");
ImGui::End();
if (show_demo_window)
{ 

ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); // Normally user code doesn't need/want to call this because positions are saved in .ini file anyway. Here we just want to make the demo initial state a bit more friendly!
ImGui::ShowDemoWindow(&show_demo_window);
}
// 渲染窗口颜色
int view_width, view_height;
glfwGetFramebufferSize(window, &view_width, &view_height);
glViewport(0, 0, view_width, view_height);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui::Render();
ImGui_ImplGlfwGL3_RenderDrawData(ImGui::GetDrawData());
glUseProgram(programID);
// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
//设定顶点属性指针
glVertexAttribPointer(
0,        // attribute 0. No particular reason for 0, but must match the layout in the shader.
3,        // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0,        // stride
(void *)0 // array buffer offset
);
// 画三角形
glDrawArrays(GL_TRIANGLES, 0, 3); // 3 indices starting at 0 -> 1 triangle
glDisableVertexAttribArray(0);
// 双缓冲。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。
glfwSwapBuffers(window);
}
// 释放VAO、VBO、EBO资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
// 释放ImGui资源
ImGui_ImplGlfwGL3_Shutdown();
ImGui::DestroyContext();
// 清除所有申请的glfw资源
glfwTerminate();
return 0;
}
void window_size_callback(GLFWwindow *window, int width, int height)
{ 

glViewport(0, 0, width, height);
}

参考资料:
1.OpenGL–使用ImGui渲染三角形 – Pan_Chengyuan的博客 – CSDN博客
2.第二课:绘制第一个三角形 – opengl-tutorial
3.你好,三角形 – LearnOpenGL CN