已经有了VAO跟VBO,那还需要EBO干什么。别急,我们画一个矩形来实战一下,代码如下:
#include
#include
using namespace std;
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()
{
glfwInit();
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
float vertices[] = {
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.5f,0.5f,0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
while (!glfwWindowShouldClose(window))
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
但是这个时候你会发现,用两个三角形画矩形时有两个点重复绘制了。也就是说矩形对角线上的两个点重复了。那么我就用EBO的方式记录顶点的索引来解决这个问题。(实质就是只存储不同的顶点,并设定绘制这些顶点的顺序。)下面上代码:
#include
#include
#include
using namespace std;
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()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
float vertices[]={
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
-0.5f, 0.5f,0.0f,
};
unsigned int indices[]={
0,1,2,
0,2,3,
};
unsigned int VBO,VAO,EBO;
glGenVertexArrays(1,&VAO);
glGenBuffers(1,&VBO);
glGenBuffers(1,&EBO);
glBindVertexArray(VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
unsigned int vertexShader;
vertexShader=glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
glCompileShader(fragmentShader);
unsigned int fragmentShader;
fragmentShader=glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
glCompileShader(fragmentShader);
unsigned int shaderProgram;
shaderProgram=glCreateProgram();
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(shaderProgram);
while(!glfwWindowShouldClose(window))
{
glClearColor(0.2f,0.3f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
glfwSwapBuffers(window);
glfwPollEvents();
}
return 0;
}
着色器开头要声明版本,接着是输入和输出变量,uniform和main函数。每个着色器的入口点都是main函数。在这个函数中我们处理所有的输入变量。并将结果输出到输出变量中。
GLSL包含C语言的默认大部分数据类型:int,float,double,unsigned int,bool.另外GLSL中没有指针和字符串,也没有字符。返回值可以为void。
GLSL有两种容器类型,分别是向量和矩阵。下面我们来聊一聊向量:
向量是一个可以包含有1,2,3,4个分量的容器。分量的类型可以是前面默认基础类型的任意一个。
类型 | 特点 |
---|---|
vec2,vec3,vec4 | 2分量,3分量,4分量浮点型向量 |
ivec2,ivec3,ivec4 | 2分量,3分量,4分量整型向量 |
uvec2,uvec3,uvec4 | 2分量,3分量,4分量无符号整型向量 |
bvec2,bvec3,bvec4 | 2分量,3分量,4分量布尔向量 |
位置向量的分量可以用vec.x,vec.y,vec.z,vec.w来获取他们的第1,2,3,4个分量。颜色向量的分量可以用vec.r,vec.g,vec.b,vec.a来获取他们的第1,2,3,4个分量。纹理向量的分量可以用vec.s,vec.t,vec.p,vec.q来获取他们的第1,2,3,4个分量。可以使用下列3组标识符中的任意一组:xyzw、rgba和stpq。但不能混合到一个向量中使用。另外向量还支持调换操作。例如,将颜色数据从RGB顺序转换到BGR顺序:FragColor.rgba=FragColor.bgra;
接下来聊一聊矩阵:矩阵类型只支持浮点数。如果是方阵的话就直接matn表示n行n列。如果不是方阵的话就直接matn*m表示m行n列。具体如下:
类型 | 特点 |
---|---|
mat2 | 两行两列 |
mat3 | 三行三列 |
mat4 | 四行四列 |
mat2*3 | 三行两列 |
mat3*2 | 两行三列 |
关于存储限定符
限定符用于将变量标记为输入变量,输出变量或者是常量。
限定符 | 特点 |
---|---|
const | 一个编译时常量,或者说是一个不可修改,只可读的参数 |
in | 一个从上一个处理阶段或者上一个函数输出中传递过来的变量 |
out | 传递到下一个处理阶段或者在一个函数中指定一个返回值 |
uniform | 一个从客户端代码传递过来的变量,在顶点之间不做改变 |
关于in和out有一点要注意:如果我们打算从一个着色器向另一个着色器发送数据。必须要在发送方着色器中声明一个输入,然后在接收方着色器中声明一个输出。当输入与输出的类型和名字都一样的时候。OpenGL就会把两个变量链接到一起。这么说可能会有一点模糊,那我们直接上代码吧。直接把顶点着色器跟片段着色器修改一下就好了。
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,1.0);\n"
" vertexColor = vec4(0.5f,0.0f,0.0f,1.0f);\n"
"}\n";
const char *fragmentShaderSource="#version 330 core\n"
"out vec4 FragColor;\n"
"in vec4 vertexColor;\n"
"void main()\n"
"{\n"
" FragColor=vertexColor;\n"
"}\n\0";
可以看到的是在顶点着色器源码里面我们out vec4 vertexColor输出了一个vec4类型的向量,叫vertexColor,然后在片段着色器源码里面我们in vec4 vertexColor输入了一个vec4类型的向量,叫vertexColor。这样就前后衔接上了。
关于uniform我们需要知道的是,这是一种从CPU中的应用像GPU中的着色器发送数据的方式。uniform是全局的,所以它可以被着色器程序的任意着色器随时访问。而且除非你主动更新,不然uniform的值不会改变。接下来我们在片段着色器里面声明一个uniform值来作为片段着色器的颜色输出。那么修改后的片段着色器代码如下:
const char *fragmentShaderSource="#version 330 core\n"
"out vec4 FragColor;\n"
"uniform vec4 ourColor;\n"
"void main()\n"
"{\n"
" FragColor=ourColor;\n"
"}\n\0";
在片段着色器里面我们声明了一个uniform vec4类型的ourColor。然后把片段着色器的输出颜色FragColor设置为uniform值的内容。但是现在uniform还是空的。那么我们还如何为它添加颜色数据呢?看一行代码:
glUseProgram(shaderProgram);
float timeValue=glfwGetTime();
float greenValue=sin(timeValue)/2.0f+0.5f;
int vertexColorLocation=glGetUniformLocation(shaderProgram,"ourColor");
glUniform4f(vertexColorLocation,0.0f,greenValue,0.0f,1.0f);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES,0,3);
首先找到着色器中uniform属性的位置值。通过glGetUniformLocation函数查询uniform ourColor的位置值。找到了以后通过glUniform4f函数来设置uniform值。这次我们来设置一个随时间改变的颜色值。(注意:在更新一个uniform之前你必须先写一句glUseProgram函数来激活着色器程序然后再在这个已经激活的程序中设置uniform值)。可以看出来,对于渲染循环中需要改变的顶点属性来说,使用uniform就很方便。
可是如果打算为每一个点单独设置一个颜色呢?设置和顶点个数一样多的uniform?如果顶点个数过大那显然不现实。那么我们就换一种方法。之前我们输入数据时只包含了xyz这种顶点位置属性,现在我们加上颜色属性。代码如下:
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec3 aColor;\n"
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos,1.0);\n"
" ourColor = aColor;\n"
"}\0"
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec3 ourColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(ourColor, 1.0);\n"
"}\n\0";
在前面的文章讲到过,layout (location=0)表示顶点位置属性。layout (location=1)表示顶点颜色属性。layout (location=2)表示顶点纹理属性。所以对应的,我们要使用glVertexAttribPointer函数更新顶点格式。
#位置属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
//颜色属性
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
好,这次就这样了。下次开始我们来学习着色器类。
推荐一个计算机视觉群。群号是736854977。群主是百度大牛。别的就不谈了。你懂的。