#include <stdio.h>
#include <GLAD/glad.h>
#include <GLFW/glfw3.h>
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
/**
* 我们需要做的第一件事是用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,
* 然后编译这个着色器,这样我们就可以在程序中使用它了
*
* #version 330 core:
* 表示声明我们使用OpenGL3.3的核心模式开发
*
* layout (location = 0) in vec3 aPos:
* layout (location = 0)设定了输入变量的位置值(Location)
* in关键字:在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)。
* 现在我们只关心位置(Position)数据,所以我们只需要一个顶点属性
*
* gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0):
* 为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position变量,
* 它在幕后是vec4类型的。在main函数的最后,我们将gl_Position设置的值会成为该顶点着色器的输出。
* 由于我们的输入是一个3分量的向量,我们必须把它转换为4分量的。
* 我们可以把vec3的数据作为vec4构造器的参数,同时把w分量设置为1.0f
* */
//顶点着色器
const char *vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main(){\n"
"gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
/**
* 片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色,
* 我们应该自己将其计算出来。我们可以用out关键字声明输出变量,这里我们命名为FragColor。
* 下面,我们将一个alpha值为1.0(1.0代表完全不透明)的橘黄色的vec4赋值给颜色输出。
* */
//片段着色器
const char *fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main(){\n"
"FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}";
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* glfWwindow = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Triangle", NULL, NULL);
if (NULL == glfWwindow){
printf("Failed to create GLFW window\n");
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(glfWwindow);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
printf("Failed to initialize GLAD\n");
return -1;
}
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
/**
* 顶点着色器(vertex shader)
* 为了能够让OpenGL使用它,我们必须在运行时动态编译着色器的源码
* 由于我们创建的是定点着色器,所以传入的是GL_VERTEX_SHADER
* 下一步用glShaderSource把这个着色器源码附加到着色器对象上,
* 然后用glCompileShader编译它
* glShaderSource参数:
* 参数1着色器对象vertexShader 参数2传递的源码字符串数量,这里只有一个
* 参数3顶点着色器源码 参数4暂时设为NULL
* */
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
/**
* 如果想知道glCompileShader编译vertexShader是否成功或失败后的错误信息
* 我们用glGetShaderiv检查是否编译成功。
* 如果编译失败,我们会用glGetShaderInfoLog获取错误消息,然后打印它。
* 以下代码可以实现
* */
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success){
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
printf("shader vertex compile failed: %s \n", infoLog);
}
/**
* 片段着色器(Fragment Shader)
* 和顶点着色器的编译步骤差不多
* 只是类型改成了GL_FRAGMENT_SHADER
* */
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
/**
* 到目前,两个着色器都编译了,接下来就是将两个着色器链接到用于渲染的着色器程序(ShaderProgram)
* 如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。
* 已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
* */
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
/**
* 与编译着色器一样,同样可以检测链接着色器程序是否失败
* */
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success){
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
printf("Link program failed: %s\n", infoLog);
}
/**
* 调用glUseProgram激活程序对象
* 调用之后,每个着色器调用和渲染调用都会使用这个程序对象(也就是之前写的着色器)了。
* 之后就可以删掉着色器对象了,因为不再需要他们了
* */
glUseProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//创建顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f, //左
0.5f, -0.5f, 0.0f, //右
0.0f, 0.5f, 0.0f //上
};
unsigned int VBO, VAO;
/**
* 我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,
* 它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以
* 一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
* 从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。
* 当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
* 顶点缓冲对象是我们在OpenGL教程中第一个出现的OpenGL对象。就像OpenGL中的其它对象一
* 样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象
* */
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
/**
* OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。
* OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。
* 我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上,
* 这样我们使用的任何在GL_ARRAY_BUFFER目标上的缓冲调用都会用来配置当前绑定的缓冲(VBO)
* */
//先绑定VAO,再绑定VBO,然后再配置顶点属性
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
/**
* 然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中,
* glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
* 第一个参数是目标缓冲的类型:顶点缓冲对象VBO当前绑定到GL_ARRAY_BUFFER目标上。
* 第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
* 第三个参数是我们希望发送的实际数据。
* 第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
* GL_STATIC_DRAW :数据不会或几乎不会改变。
* GL_DYNAMIC_DRAW:数据会被改变很多。
* GL_STREAM_DRAW :数据每次绘制时都会改变。
* 三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。
* 如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW
* 或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分。
* */
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
/**
* 现在,我们已经把输入顶点数据发送给了GPU,并指示了GPU如何在顶点和片段着色器中处理它。
* 就快要完成了,但还没结束,OpenGL还不知道它该如何解释内存中的顶点数据,
* 以及它该如何将顶点数据链接到顶点着色器的属性上。我们需要告诉OpenGL怎么做。
* glVertexAttribPointer: 设置顶点属性指针
* 这个函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)
* 第一个参数是顶点属性,之前在顶点着色器中用layout(location = 0)定义了position顶点属性的
* 位置值(Location),他可以把顶点属性的位置值设置为0,因为我们希望把数据传递到这一个顶
* 点属性中,所以这里我们传入0。
* 第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3
* 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)
* 第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,
* 所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE
* 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。
* 由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)
* 要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具
* 体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,
* 我们在后面会看到更多的例子
* (译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
* 最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。
* 由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数
*
* glEnableVertexAttribArray:
* 以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的
* */
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
while (!glfwWindowShouldClose(glfWwindow)){
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwPollEvents();
glfwSwapBuffers(glfWwindow);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
运行结果: