Triangle是LearnOpenGL CN教程中的第二个项目,里面包含了完整的OpenGL渲染流程,后续的所有程序都是基于此程序的。这个程序就像c语言中输出"Hello World"一样,很有必要每一行代码都精读。最近在复习之前学习的OpenGL时,发现有些基本的概念比较模糊,于是对照着重新写了一遍,把每个比较重要的地方都加了中文注释,方便以后自己查阅。此代码基于Linux下的Clion编写,项目目录如下:
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(Triangle)
set(CMAKE_CXX_STANDARD 14)
set(SOURCE_FILES main.cpp glad.c)
add_executable(Triangle ${SOURCE_FILES})
target_link_libraries(Triangle glfw3 GL m Xrandr Xi X11 Xxf86vm pthread dl Xinerama Xcursor)
main.cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void processInput(GLFWwindow *window); // 处理键盘/鼠标的输入
// 设置窗口大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// 顶点着色器
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n" //输入变量的位置值
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" //顶点着色器的输出
"}\0";
// 片段着色器
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n" //输出变量
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" //片段着色器的输出
"}\n\0";
int main() {
// glfw的初始化和配置
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // 设置主版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // 设置次版本
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用OpenGL核心模式
// 如果是苹果电脑,需要加上这个
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif
// 创建OpenGL窗口
// -------------------------------------------------------------------------------------------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Triangle", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << 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, SCR_WIDTH, SCR_HEIGHT);
// 创建和编译着色器
// --------------------------------------------------------
// 顶点着色器
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,VBO,顶点属性等
// ------------------------------------------------------------------
// 三角形的三个顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
// 生成并绑定VAO,VBO
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
GLuint VBO;
glGenBuffers(1, &VBO); // 第一个参数是要生成的缓冲对象的数量,第二个是要输入用来存储缓冲对象名称的数组
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 把之前定义的顶点数据复制到缓冲的内存中
// 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
/* 第一个参数指定我们要配置的顶点属性,为layout(location = 0)定义的position顶点属性的位置值(Location)
第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
第四个个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。*/
glEnableVertexAttribArray(0);
/* 每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)
获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。由于在调用
glVertexAttribPointer之前绑定的是先前定义的VBO对象,顶点属性0现在会链接到它的顶点数据。*/
// 解绑VAO和VBO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 正常模式和线框模式
//glPolygonMode(GL_FRONT_AND_BACK, GL_FULL);
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 渲染循环
// -----------------------------------
while (!glfwWindowShouldClose(window))
{
// 处理输入
processInput(window);
// 渲染
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清空屏幕所用的颜色
glClear(GL_COLOR_BUFFER_BIT); // 清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲
// 画三角形
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0);
// 交换缓冲并且检查是否有触发事件(比如键盘输入、鼠标移动等)
glfwSwapBuffers(window);
glfwPollEvents();
}
// 删除VAO和VBO
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// 释放/删除之前的分配的所有资源,退出程序
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
// 如果按下了esc键,关闭渲染窗口
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}