学习来源
重要概念
顶点数组对象(VAO)
顶点缓冲对象(VBO)
索引缓冲对象(EBO)
3D坐标转为2D像素的处理过程是由OpenGL的图形渲染管线管理的,它分为两部分工作。
标准化设备坐标(NDC),将输入的顶点定义在-1.0~1.0之间,接着通过视口变换将标准化设备坐标变为屏幕空间坐标。
顶点着色器会在GPU上创建内存存储这些顶点数据并配置OpenGL如何解释这些内存并指定其如何发送给显卡。通过**顶点缓冲对象(VBO)**来管理这个内存,它会在显存种存储大量顶点。使用VBO的好处是能一次性的发送大批数据到显卡上。
着色器是使用一种为GLSL的类c语言写成的,GLSL是为图形计算量身定制的。
设置顶点属性指针将其绑定到顶点数组对象VAO上,VAO上会存储以下内容:
之后再打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。
索引绘制:通过设置顶点,以索引的方式来绘制图形,这就避免了重复存储顶点。
总结以上流程理解并绘制如下:
顶点输入:
float vertices[] = {
-1.0f, -1.0f, 0.0f, // 左
1.0f, -1.0f, 0.0f, // 右
0.0f, 1.0f, 0.0f // 上
};
编写着色器代码
//顶点着色器
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"out vec4 vertexColor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
" vertexColor = vec4(1.0f, 1.0f, 0.8f, 0.8f);\n"
"}\0";
//片段着色器
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec4 vertexColor;\n"
"void main()\n"
"{\n"
" FragColor = vertexColor;\n"
"}\n\0";
编译着色器代码
// 编译顶点着色器
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 错误检查
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 编译片段着色器
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// 错误检查
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
链接着色器
// 创建一个程序对象
int shaderProgram = glCreateProgram();
// 将编译好的着色器附着到程序上
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 链接错误检查
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 链接完成,删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
配置生成VAO
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(VAO);
// 复制顶点数组到缓冲中供openGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置顶点属性指针, 告知openGL如何解析这些顶点数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
绘制三角形:
//渲染
glClearColor(0.0f, 0.8f, 0.8f, 1.0f);//设置清空p屏幕所用的颜色
glClear(GL_COLOR_BUFFER_BIT);//使用当前状态来获取应该清除为的颜色
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
生成效果:
在输入顶点中添加颜色属性:
float vertices[] = {
//位置 // 颜色
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // 左
1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 右
0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f // 上
};
添加配置顶点颜色属性
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
效果:
现象解释:
这是在片段着色器中进行的片段插值(Fragment Interpolation)的结果。当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置。
基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。
光栅化会把顶点数据映射为像素点,生成供片段着色器使用的片段,同时会裁切出超出视图外的所有像素。也就是说虽然我们只输入三个顶点但是光栅化会补充其他构成三角形的片段像素点。然后片段着色器会根据我们提供的三个点的颜色值对其他的像素点进行等比例插值。
比起前两步的工作:
窗口初始化设置:
// imgui窗口设置
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO(); (void)io;
// 设置颜色主题
ImGui::StyleColorsLight();
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 330");
启动imGUI的框架并处理相应的IO事件
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
{
//static float f = 0.0f;
ImGui::Begin("HW2");
ImGui::Text("change color");
//ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
// 将更改的颜色存入color中
ImGui::ColorEdit3("choose one color", (float *)&color, 1);
// 勾选框,选中则使三角形保持三色渐变色,取消勾选则课进行全局颜色设置
ImGui::Checkbox("default color", &default_color);
if (default_color)
{
vertices[3] = 0.0f; vertices[4] = 1.0f; vertices[5] = 0.0f;
vertices[9] = 0.0f; vertices[10] = 0.0f; vertices[11] = 1.0f;
vertices[15] = 1.0f; vertices[16] = 0.0f; vertices[17] = 0.0f;
}
else
{
for (int i = 0; i < 3; i++) {
vertices[i * 6 + 3] = color.x;
vertices[i * 6 + 4] = color.y;
vertices[i * 6 + 5] = color.z;
}
}
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
}
同时因为在每次IO操作都会改变三角形的属性,所以将VBO,VAO配置加入到窗口循环中,并用ImGUI进行渲染
// 渲染
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
最终clean up
// 清除GUI资源
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
实现效果:
加入两条直线,6个点
// 直线1
-0.5f, 0.0f, 0.0f, 0.9f, 0.9f, 0.666f, // 左下
-1.0f, 1.0f, 0.0f, 0.9f, 0.9f, 0.666f, // 左上
// 直线2
0.5f, 0.0f, 0.0f, 0.8f, 0.36f, 0.36f, // 右下
1.0f, 1.0f, 0.0f, 0.8f, 0.36f, 0.36f, // 右上
// 点1
0.75f, 0.05f, 0.0f, 0.0f, 0.0f, 0.0f, // 上
// 点2
0.75f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // 中
// 点3
0.75f, -0.05f, 0.0f, 0.0f, 0.0f, 0.0f, // 下
// 点1
0.8f, 0.05f, 0.0f, 0.0f, 0.0f, 0.0f, // 上
// 点2
0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // 中
// 点3
0.8f, -0.05f, 0.0f, 0.0f, 0.0f, 0.0f // 下
加入勾选框,选择则出现对应的图形
ImGui::Checkbox("default color", &default_color);
ImGui::Checkbox("draw lines", &lines);
ImGui::Checkbox("draw points", &points);
绘制图形
glDrawArrays(GL_LINES, 3, 4);
glDrawArrays(GL_POINTS, 7, 6);
效果
五个顶点索引2个三角形
// 三角形
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // 左
1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 右
0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 上
0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 第二个三角形
0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, //
绘制索引
// 索引绘制
unsigned int indices[] = {
0, 1, 2, // 第一个三角形
2, 3, 4 // 第二个三角形
};
生成EBO对象
unsigned int VBO, VAO, EBO;
// 生成对象
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
将索引数组复制到索引缓冲中供openGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
绘制
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
成品展示:
代码地址:
https://github.com/WangPerryWPY/Computer_Graphics/tree/master/HW2