简单着色器编写(上)
在我的第一篇OpenGL文章中,我已经成功的画出了一个三角形,默认是白色的,那么该怎么把它换一个颜色呢?
先给出完整的代码。
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include<iostream>
static unsigned int CompileShader(unsigned int type,const std::string& source)
{
unsigned int id = glCreateShader(type);
const char* src = source.c_str();
glShaderSource(id, 1, &src, nullptr);
glCompileShader(id);
int result;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
if (result==GL_FALSE)
{
int length;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
char* message = (char*)alloca(length * sizeof(char));
glGetShaderInfoLog(id, length, &length, message);
std::cout << "Failed to compile"<<
(type==GL_VERTEX_SHADER?"vertex":"fragment")
<< "shader!" << std::endl;
std::cout << message << std::endl;
glDeleteShader(id);
return 0;
}
return id;
}
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
unsigned int program = glCreateProgram();
unsigned int vs = CompileShader(GL_VERTEX_SHADER,vertexShader);
unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
glValidateProgram(program);
glDeleteShader(vs);
glDeleteShader(fs);
return program;
}
int main(void)
{
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
glewInit();
/* Create a windowed mode window and its OpenGL context */
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK)
std::cout << "error";
std::cout << glGetString(GL_VERSION) << std::endl;
float positions[6] =
{
-0.5f, -0.5f,
0.0f, 0.5f,
0.5f, -0.5f
};
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float),positions,GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
std::string vertexShader =
"#version 330 coren"
"n"
"layout(location=0)in vec4 position;"
"n"
"void main()n"
"{n"
" gl_Position = position;n"
"}n";
std::string fragmentShader =
"#version 330 coren"
"n"
"layout(location=0)out vec4 color;"
"n"
"void main()n"
"{n"
" color=vec4(1.0,0.0,0.0,1.0);n"
"}n";
unsigned int shader = CreateShader(vertexShader,fragmentShader);
glUseProgram(shader);
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
glClear(GL_COLOR_BUFFER_BIT);
//glBegin(GL_TRIANGLES);
//glVertex2f(-0.5f, -0.5f);
//glVertex2f(0.0f, 0.5f);
//glVertex2f(0.5f, -0.5f);
//glEnd();
glDrawArrays(GL_TRIANGLES, 0,3);
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
glDeleteProgram(shader);
glfwTerminate();
return 0;
}
不同于之前,这里我使用了新的方法来画三角形。
下面是新的三角形画法:
float positions[6] =
{
-0.5f, -0.5f,
0.0f, 0.5f,
0.5f, -0.5f
};
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float),positions,GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
仅靠这些还不能在屏幕上显示出一个三角形,还要继续添加OpenGL渲染的步骤,这些我们待会儿说。
我来逐步解释一下上面代码的作用;
float positions[6] =
{
-0.5f, -0.5f,
0.0f, 0.5f,
0.5f, -0.5f
};
这段代码定义了一个包含三个顶点坐标的数组,每个顶点由两个浮点数表示(X 和 Y 坐标)。这些顶点坐标定义了一个位于屏幕中心的等腰三角形,顶点坐标如下:
- 第一个顶点:(-0.5, -0.5)
- 第二个顶点:(0.0, 0.5)
- 第三个顶点:(0.5, -0.5)
这些坐标被用作顶点数据,可以通过OpenGL的顶点缓冲区对象(VBO)来传递给渲染管线,从而绘制这个三角形。这个代码片段是OpenGL中的基础步骤之一,用于准备顶点数据以进行渲染。
请注意,这里的坐标是以标准化设备坐标(Normalized Device Coordinates,NDC)表示的,范围从 -1 到 1。实际渲染时,这些坐标会经过变换和投影,最终映射到屏幕上的像素位置。
unsigned int buffer;
用于定义一个无符号整数变量 buffer
,这个变量通常用来表示OpenGL中的缓冲区对象标识符(如顶点缓冲区对象、帧缓冲区对象等)。
glGenBuffers(1, &buffer);
是OpenGL函数调用,用于生成缓冲区对象的标识符(ID)。让我解释一下这个函数的作用:
-
glGenBuffers
:这是一个OpenGL函数,用于生成一个或多个缓冲区对象的标识符。在这里,我们生成一个缓冲区对象,所以是生成单个标识符。 -
(1, &buffer)
:这是函数的参数。1
表示生成一个标识符,&buffer
是用于接收生成的标识符的变量的地址。在这里,buffer
是我之前声明的无符号整数变量,用于存储生成的缓冲区对象的标识符。
通过调用这个函数,OpenGL会为我生成一个唯一的标识符,用于标识一个缓冲区对象。之后,你可以通过这个标识符来操作和管理这个缓冲区对象,比如绑定、填充数据等操作。
这个函数很长,不好记忆,那么我们把它拆开看看,
gl是OpenGL函数的前缀,这个是必须要加的,
“gen”是“generate”的缩写,表示生成,
“buffer”表示缓冲区对象,
因此,glGenBuffers
的意思可以理解为“生成缓冲区对象的标识符”。
glBindBuffer(GL_ARRAY_BUFFER, buffer);
是OpenGL中用于绑定缓冲区对象的函数调用。让我解释一下这段代码的含义:
-
glBindBuffer
:这是一个OpenGL函数,用于将一个缓冲区对象绑定到OpenGL上下文中的指定缓冲区目标。 -
GL_ARRAY_BUFFER
:这是一个缓冲区目标的常量,表示要绑定的缓冲区对象的类型。在这个情况下,它表示顶点缓冲区对象。 -
buffer
:这是我之前生成的缓冲区对象的标识符。通过这个标识符,我可以引用特定的缓冲区对象。
通过调用 glBindBuffer(GL_ARRAY_BUFFER, buffer);
,将指定的缓冲区对象绑定到OpenGL上下文中的当前顶点缓冲区目标。这意味着后续的顶点数据操作将应用于这个特定的缓冲区对象。
在绑定缓冲区之后,可以使用其他函数来填充数据、配置属性等操作。完成操作后,通常会使用 glBindBuffer(GL_ARRAY_BUFFER, 0);
解绑缓冲区,以确保后续的操作不会影响到这个缓冲区对象。
同样,我来解释一下这个函数的命名方式,
"bind" 表示将一个对象绑定到OpenGL上下文的特定目标,
综合起来,当我们说 "bind a buffer" 时,意味着将一个缓冲区对象绑定到特定的缓冲区目标,以便后续的操作将作用于这个缓冲区对象。在操作缓冲区对象时,需要使用 bind
来确定正在操作的对象,然后使用 buffer
来描述这个对象的类型。
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float),positions,GL_STATIC_DRAW);
是一个OpenGL函数,用于将数据上传到缓冲区对象中。让我解释一下这段代码的含义:
-
GL_ARRAY_BUFFER
:这是一个缓冲区目标的常量,表示要绑定的缓冲区对象的类型。在这个情况下,它表示顶点缓冲区对象。 -
6 * sizeof(float)
:这个参数表示要上传的数据的大小,以字节为单位。在这里,你上传了包含6个浮点数的数据数组,每个浮点数占用4个字节(sizeof(float)
)。 -
positions
:这是一个指向包含要上传数据的数组的指针,即顶点的坐标数据。 -
GL_STATIC_DRAW
:这是一个提示,告诉OpenGL你打算如何使用这些数据。在这个情况下,GL_STATIC_DRAW
表示这些数据将不会在绘制过程中被频繁修改,因此OpenGL可以根据需要对内部数据进行优化。
通过调用 glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), positions, GL_STATIC_DRAW);
,将顶点数据上传到绑定到 GL_ARRAY_BUFFER
目标的缓冲区对象中。这样,OpenGL就可以在渲染过程中使用这些数据来绘制图形。
需要注意的是,这只是上传数据到缓冲区对象中的第一步。接下来,可能需要在顶点着色器中设置属性指针,以告诉OpenGL如何解释这些数据,从而正确地渲染图形。
这个函数使用起来比较复杂,我来介绍一下它的函数签名,
void glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
这里是参数的解释:
-
target
:缓冲区的目标,表示将数据上传到哪种类型的缓冲区对象。常见的值包括GL_ARRAY_BUFFER
(顶点缓冲区)、GL_ELEMENT_ARRAY_BUFFER
(索引缓冲区)等。 -
size
:要上传的数据大小,以字节为单位。 -
data
:指向要上传的数据的指针。 -
usage
:数据使用提示,告诉OpenGL如何使用这些数据,以便优化内存和性能。常见的值包括GL_STATIC_DRAW
、GL_DYNAMIC_DRAW
等。
通过调用 glBufferData
,你可以将数据上传到指定的缓冲区对象中,以便在渲染过程中使用。
glEnableVertexAttribArray(0);
是一个OpenGL函数,用于启用指定的顶点属性数组。让我解释一下这段代码的含义:
-
0
:这是一个顶点属性的索引,表示要启用的顶点属性数组。在这里,索引为0表示启用顶点位置属性。
通过调用 glEnableVertexAttribArray(0);
,你告诉OpenGL要启用顶点位置属性数组,以便在渲染时使用。这是因为在使用顶点缓冲区对象绘制时,你可以将多个顶点属性存储在不同的顶点属性数组中,然后使用 glEnableVertexAttribArray
来启用这些属性数组。
在顶点着色器中,你需要通过指定的属性索引(在这里是0)来访问启用的顶点属性。通过启用和设置多个顶点属性,你可以在渲染过程中使用多个属性来定义顶点的属性,比如位置、法线、颜色、纹理坐标等。
这么写可能不太好理解,让我们用画画来类比,
想象你正在绘制一个彩色的风景画,你有一组颜料盒,每个盒子里装着不同颜色的颜料。你还有几个画笔,每支画笔可以绘制不同的部分。
- 在OpenGL中,你的画布是屏幕,你的风景是3D模型。
- 顶点属性数组就像是你的颜料盒,每个属性数组存储不同的顶点属性,比如位置、颜色、纹理坐标等。
- 每支画笔就是你的顶点着色器,它决定了如何绘制每个顶点。
-
glEnableVertexAttribArray(0);
就相当于告诉你的画笔,你要使用颜料盒中的第一种颜色(索引0),也就是启用位置属性数组。
因此,这句话就是在告诉OpenGL的画笔(顶点着色器):“嘿,我要用位置属性这个颜料盒(属性数组)中的颜色(数据)来绘制我的风景(模型)!” 这样,OpenGL就知道在绘制过程中如何使用顶点属性数据来渲染图形了。
这个函数也比较难以理解,拆开来解释下,
-
gl
:OpenGL 库的前缀,表示这个函数是属于OpenGL库的一部分。 -
Enable
:启用的意思,表示你要激活或开启某个功能。 -
Vertex
:顶点的意思,通常用于表示3D图形中的一个点。 -
AttribArray
:属性数组,指的是一组包含顶点属性数据的数组。在OpenGL中,你可以在属性数组中存储顶点的各种属性,如位置、颜色、法线、纹理坐标等。
综合起来,glEnableVertexAttribArray
表示你要启用或开启一个顶点属性数组,以便在OpenGL渲染中使用这些属性数据。这个函数的调用告诉OpenGL,你想要激活某个顶点属性数组,从而在绘制过程中使用这些属性数据来定义模型的特征。
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);
让我来解释一下 glVertexAttribPointer
函数的参数:
-
0
:这是顶点属性的索引,表示要设置的顶点属性数组的索引。在这里,索引0表示设置顶点位置属性数组。 -
2
:这是指定每个顶点属性的组件数量。在这里,表示每个顶点位置有两个组件,即 x 和 y 坐标。 -
GL_FLOAT
:这是指定顶点属性的数据类型。在这里,表示顶点位置属性的数据类型是浮点数。 -
GL_FALSE
:这是一个布尔值,用于指定是否应该将顶点属性的值标准化。在这里,表示不标准化顶点位置属性的值。 -
sizeof(float) * 2
:这是指定顶点属性数组中每个顶点属性的字节数。在这里,表示每个顶点位置属性由两个浮点数(每个浮点数占4字节)组成,所以是sizeof(float) * 2
字节。 -
0
:这是指定顶点属性数组中第一个顶点属性的偏移量。在这里,表示从数组的开头开始。
通过调用 glVertexAttribPointer
函数,你告诉OpenGL如何解释顶点属性数组中的数据。在这个例子中,它告诉OpenGL如何解释顶点位置属性的数据:每个顶点位置由两个浮点数组成,不标准化,每个顶点属性在数组中的偏移量为0。这些设置将在渲染时被顶点着色器使用,以正确地处理顶点位置属性。
把单词拆开来看,
-
gl
:OpenGL 库的前缀,表示这个函数是属于OpenGL库的一部分。 -
Vertex
:顶点的意思,通常用于表示3D图形中的一个点。 -
Attrib
:属性的缩写,表示顶点的一个特征或属性,如位置、颜色、法线等。 -
Pointer
:指针的意思,表示指向数据的指针。在这里,它指的是告诉OpenGL如何访问顶点属性数据的指针。
glBindBuffer(GL_ARRAY_BUFFER, 0);
是一个OpenGL函数调用,让我为你解释一下这个函数的作用:
-
gl
:OpenGL 库的前缀,表示这个函数是属于OpenGL库的一部分。 -
Bind
:绑定的意思,表示把一个对象或资源绑定到OpenGL上下文中,以便在后续的操作中使用。 -
Buffer
:缓冲区的意思,表示一个用于存储数据的内存块,通常用于存储顶点数据、纹理数据等。 -
GL_ARRAY_BUFFER
:这是OpenGL中的一个常量,表示绑定目标为顶点属性数组的缓冲区。 -
0
:这是一个特殊的值,表示要解绑目标缓冲区。
综合起来,glBindBuffer(GL_ARRAY_BUFFER, 0);
表示你正在将顶点属性数组的缓冲区解绑定,即将之前绑定的缓冲区与顶点属性数组分离。在OpenGL中,通过绑定和解绑缓冲区,你可以在渲染过程中使用不同的缓冲区数据来绘制不同的图形。将目标缓冲区设置为0,表示将当前绑定的缓冲区解绑定,以后的操作将不再影响该缓冲区。
所以,这句代码的作用是取消之前与顶点属性数组绑定的缓冲区,以便后续的操作不再受该缓冲区的影响。