顶点数据
废话我就不说了
三角形需要顶点数据,我们需要在建立好的窗口正中间画一个三角形,那么需要三个顶点的坐标
坐标规则:窗口正中心为原点建立直角坐标系,OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。
每个坐标设置为三维坐标(x,y,z),由于画平面三角形,所以z轴均设置为0.0f,
//vertices data
float vertices[]={
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f
0.0f, 0.5f,0.0f
};
OpenGL画三角形原则为逆时针,也就是说第一个点在坐标系的第三象限,第二个点在第四象限,最后一个在y轴正方向上。
如何画
顶点输入
在定义了定点数据之后,就得把这些顶点交给渲染管线的第一个阶段处理,顶点着色器
顶点着色器:它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。
我们用顶点缓冲对象来处顶点数据。
顶点缓冲对象(Vertex Buffer Objects, VBO),它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象,
unsigned int VBO;
glGenBuffers(1, &VBO);
顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
这个函数的第一个参数是顶点缓冲类型,第二个参数是输入顶点数据的大小,第三个是顶点数据,第四个参数指定了我们希望显卡如何管理给定的数据,这边考虑三角形顶点数据几乎不会改变,所以用GL_STATIC_DRAW
顶点着色器
用着色器语言编写顶点着色器,然后编译这个着色器,这样我们就可以在程序中使用它了。
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
稍微解释下这一段代码,它是一种类C语言,第一句话是规定最低的OpenGL版本,第二句话layout(location=0)说明它挖数据是从0号位开始挖数据,in可以理解引进的意思,vec3是三维向量的意思,aPos则是引进数据的名字,这里引进的是顶点的具体位置(position),主函数中的gl_Position还可以换个写法 gl_Position=vec4(aPos,1.0f)或者gl_Position=vec4(aPos.xyz,1.0f),函数中第四个就是最后一个参数设为1.0f即可,这个以后会讨论。
接下来编译着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
这段代码从英文表面即可了解意思,不详细讲,其中vertexShaderSource就是上面编写的顶点着色器代码(存储:const char* vertexShaderSource)。
片段着色器
片段着色器所做的是计算像素最后的颜色输出,代码:
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}
第二行的out可以理解为输出的意思,FragColor定义为四位向量,里面分别为rgba(红,绿,蓝,α值),我们在这边直接设为红色。
编译片段着色器的过程与顶点着色器类似,
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);
最后我们可以使用glUseProgram(shaderProgram)来用它。
链接顶点属性
先给出代码
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
第一个函数的第一个参数是index,这边设为0原因在于上面的顶点着色器layout location是0,两方要对应,第二个参数是一次性吃多少个数据,每个顶点均为三维向量,所以是3个,类型为float,所以第三个参数填GL_FLOAT,第四个参数是否考虑标准化数据(归一化,类似把向量变成单位向量的操作),这边写GL_TRUE会把数据全部变成0~1之间的数,有符号(signed)的则是-1到1之间,填GL_FALSE,第五个数据是步长,这边每次收一个顶点(3个float 型数据),所以下次收数据需要跨过三个数据的量,也就是3*sizeof(float),最后一个先填0,到后续博客会详细讲述。
具体实现
//Main.cpp
#include "Window.h"
const char* vertexSource = "#version 330 core\n"
"layout(location=0) in vec3 pos;\n"
"void main()\n"
"{\n"
"gl_Position=vec4(pos,1.0f);"
"}\n";
const char* fragmentSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"FragColor=vec4(1.0f,0.0f,0.0f,1.0f);"
"}\n";
unsigned int CompileShader(unsigned int type, const char* source) {
unsigned int shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
int success, length;
char* infoLog;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
infoLog = (char*)alloca(length * sizeof(char));
glGetShaderInfoLog(shader, length, NULL, infoLog);
std::cout << "shader compile error:" << "(" << infoLog << ")" << std::endl;
}
else return shader;
}
unsigned int CreateProgram(const char* vertexSource, const char* fragmentSource) {
unsigned int renderer = glCreateProgram();
unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexSource);
unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentSource);
glAttachShader(renderer, vs);
glAttachShader(renderer, fs);
glLinkProgram(renderer));
glValidateProgram(renderer));
glDeleteShader(vs);
glDeleteShader(fs);
int success, length;
char* infoLog;
glGetProgramiv(renderer, GL_LINK_STATUS, &success));
if (!success) {
glGetProgramiv(renderer, GL_INFO_LOG_LENGTH, &length));
infoLog = (char*)alloca(length * sizeof(char));
glGetProgramInfoLog(renderer, length, NULL, infoLog));
std::cout << "program error:" << "(" << infoLog << ")" << std::endl;
}
else return renderer;
}
int main() {
Window window("william", 1200, 800);
float vertices[] = {
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.0f, 0.5f,0.0f
};
unsigned int program = CreateProgram(vertexSource, fragmentSource);
unsigned int vao, vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
while (!window.closed()) {
window.clear();
glUseProgram(program);
glDrawArrays(GL_TRIANGLES, 0, 6);
window.update();
}
glDeleteProgram(program);
glfwTerminate();
return 0;
}
幸甚至哉,歌以咏志。欢迎来喷。