在opengl中任何事物都在3D空间中,而屏幕与窗口确实2D的,由3D坐标转化为2D的处理过程是opengl的图形渲染管线管理的。可以被划分为两个主要部分,第一部分把你的3D坐标转化为2D坐标,第二部分则是把2D坐标转化为有颜色的像素。
在图形渲染管线上运行各自的小程序,从而图形渲染管线鲁艾苏处理你的数据,这样的小程序叫做着色器。
有些着色器允许开发者自己配置,可以让我们更加细致的控制渲染管线的特定部分。
在现代OpenGL中,我们必须至少定义一个顶点着色器和一个片段着色器,
1.顶点输入
float vertices[]={
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.0f,0.5f,0.0f
};
由于我们渲染的是一个2d图形,所以我们将它顶点的z坐标设置为0.0。
定义了以上的顶点数据后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段。
我们通过顶点缓冲对象来管理这个内存,会在GPU内存中存储大量的顶点,好处就是可以一次性的发送大量的数据至显卡的内存中。顶点着色器能够立即访问到顶点。
我们可以使用glGenBuffers函数和一个缓冲ID来生成一个VBO对象,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER,然后调用glBufferData来将之前定义的顶点数据复制到缓冲的内存中:
unsigned int VBO;
glGenBuffers(1,&VBO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
glBufferData的第四个参数由三种形式,用于告诉显卡如何去管理给定的数据
//将其存储在vertexShaderSource 字符串中
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
然后就是编译该着色器。
unsigned int vertexShader;
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;
}
片段着色器与之类似,不过参数改为了GL_FRAGMENT_SHADER
然后创建一个着色器程序,将其连接到用来渲染的着色器程序。
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
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);
opengl解释顶点属性
位置数据被储存为32位(4字节)浮点值。
每个位置包含3个这样的值。
在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
数据中第一个值在缓冲开始的位置。
有了这些信息我们就可以使用glVertexAttribPointer函数告诉OpenGL如何解析顶点函数。
glVertexAttritPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void)*0);
glEnableVertexAttribArray(0);//以顶点属性位置值作为参数,启用顶点属性
在OpenGL中绘制一个物体,代码会像如下所示那样
// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
顶点数组对象(VAO)
OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
一个顶点数组对象会存储下面的东西,
unsigned int VAO;
glGenVertexArrays(1,&VAO);
要想使用VAO,要做的是使用glBindVertexArray绑定VAO。绑定之后起,绑定和配置对应的VBO和属性指针,之后解绑VAO供之使用,在绘制一个物体前,将VAO绑定到希望的设定上去。
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();