OpenGL 学习记录2-绘制三角形
基本概念
在上次学习窗口的过程,其实有个窗口和渲染 buffer切换的概念。三角形的概念定义很多,也是最基本最关键的,推荐记住如下名词
VAO: 顶点数组对象 Vertex Array Object
VBO: 顶点缓冲对象 Vertex Buffer Object 用于一次性发送大量VAO到显卡
Opengl中用glGenBuffers函数和一个缓冲ID生成一个VBO对象
unsigned int VBO;
glGenBuffers(1, &VBO)
EBO/IBO: 索引缓冲对象 Element Buffer Object、 Index Buffer Object
Primitive: 图元,任何一个绘制指令的调用都是把图元传递给OPENGL,比如GL_POINTS ,GL_TRIANGLES,GL_LINE_STRIP
Vertext Shader: 顶点着色器, 把一个单独的顶点作为输入,主要哦目的是把3D坐标转化成另外一个3D坐标
Primtive Assembly:图元装配,讲顶点着色器输出的所有顶点作为注入,并所有的点装配成图元的形状
Geometry Shader: 几何着色器,图元装配阶段的输出会传递给几何着色器,
Rasterization Stage:光栅化阶段,几何着色器的输出会传递到这个光栅化阶段,最终把图元银色到屏幕的像素,生成供 片段着色器 使用的片段。 在片段着色器运行之前会进行 裁切(Clipping)—把屏幕之外的像素都丢弃,提升效率
Fragment Shader: 片段着色器 :主要是计算一个像素的最终颜色(包含3D场景数据-光照 阴影 光的颜色)
Alpha 测试(透明度)/Blending 阶段(混合):最终可见的颜色是基于透明度和混合后的结果来显示,而不是片段着色器的结果
上面顶点着色器和片段着色器是必须步骤
NDC: normalized device coordinates 标准化设备坐标–正常顶点坐标呗顶点着色器处理就应该是NDC.
因为手机屏幕等都是2D平面,所以任何3D都需要变成2D来在屏幕显示,需要对3D的坐标进行处理
X,Y,Z ,最终投射到屏幕XY来实现。最简单的场景就是Z是0,其他Z不是0的场景,深度会被前面的给遮挡的情况。 X Y 都为0正常设置为屏幕的中心。
定义并复制顶点数据到显卡的内存
OpenGL允许绑定多个缓冲,主要是不同缓冲类型,
VBO 顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER ,要用如下glBindBuffer函数来把新创建的VBO绑定达到GL_ARRAY_BUFFER目标上。然后后续使用这个GL_ARRAY_BUFFER都会调用配置绑定的缓冲(VBO),这样用glBufferData函数就可以把前面定义的顶点数据复制到缓冲的内存中
glBufferData函数的第一个参数是是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。第四个参数指定了我们希望显卡如何管理给定的数据,有三种形态,分别表示数据基本不变或者会变或者每次都变GL_STATIC_DRAW,GL_DYNAMIC_DRAW,GL_STREAM_DRAW
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
定义顶点着色器
该文比较详细的解释了参数信息
https://blog.csdn.net/huhaoxuan2010/article/details/77717519?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159220686519724835858267%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=159220686519724835858267&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v1~rank_blog_v1-16-77717519.pc_v1_rank_blog_v1&utm_term=apos
如下,先编写一个顶点着色器, 从教程摘录
#version 330 core //申明opengl版本3.3
layout (location = 0) in vec3 aPos; // 定义一个aPos变量,vec3类型, layout(location = 0)设置输入变量的位置是0 也就是屏幕中心。
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); //glsl的向量数据类型float gl_Position,,main函数最后,**gl_Position设置的值会成为该顶点着色器的输出**,gl_Position是vec4类型的,
}
在openGL里面有日下的数据类型
int, float, double, uint以及bool,还有两种容器类型向量(Vector)和矩阵(Matrix)。
在GLSL中一个向量有最多4个分量,每个分量值都代表空间中的一个坐标,它们可以通过vec.x、vec.y、vec.z和vec.w来表示这个容器里面的分量
vecn 来表示向量有几个分量,如上分别是vec3 和vec4,表示这个容器里面有3个分量和4个分量,float类型。
ivecn ,表示包含N个int类型的分量
bvecn,N个bool分量的向量
uvecn:包含n个unsigned int分量的向量
dvecn:包含n个double分量的向量
layout 元数据指定输入变量,
layout (location = 0),顶点着色器需要为它的输入提供一个额外的layout标识以把它链接到顶点数据
也可忽略layout (location = 0)标识符,在opengl代码中使用glGetAttribLocation查询属性位置值
输入输出
使用in关键字设定输入,使用out关键字设定输出。若打算从一个着色器向另外一个着色器发送数据,必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似输入。当类型和名字都一样时,OpenGL会把两个变量链接到一起,它们之间就能发送数据
编译着色器
创建一个着色器对象,因为是ID引用所以用无符号整型, glCreateShader 来创建着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
下一步需要把着色器源码附加到着色器对象上,编译它
glShaderSource(vertexShader, 1, &vertextShaderSource,NULL);
glCompileShader(vertexShader);
glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数指定了传递的源码字符串数量,这里只有1个。第三个参数是顶点着色器真正的源码,第四个参数我们先设置为NULL。
glCompileShader 是编译它。
因为编译成功是前提,正常用下面代码来实现检查编译是否成功。
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;
}
infoLOG用来存储失败的消息,glGetShaderiv是用来检查编译是否成功,
glGetShaderInfoLog函数用来获取错误信息
片段着色器
Fragment Shader 用来计算像素最后的颜色输出,
在计算机图形中颜色被表示为有4个元素的数组:红色、绿色、蓝色和alpha(透明度)分量,通常缩写为RGBA
#version 330 core
out vec4 FragColor; // out 用来输出特定数据的输出变量,本次是vec4
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); //颜色
}
编译片段着色器
如下,一样创建一个无符号的整型类型。 用GL_FRAGMENT_SHADER 常量作为着色器类型,和前面顶点着色器的GL_VERTEXT_SHADER 类似。
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
着色器链接到着色器程序
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本,然后再渲染对象的时候激活这个着色器程序, 被渲染调用
如下,还是先定义对象,然后用glCreateProgram来创建一个程序,然后用glAttachShader 去把前面编译好的顶点着色器和片段着色器附加到程序对象,然后 再用glLinkProgram来链接它们
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
这个执行完成后,shaderProgram就是一个程序对象,然后可以被 glUseProgram使用,这个对象作为参数,激活这个程序对象
glUseProgram(shaderProgram)
glDeleteShader(vertexShader); //着色器链接到程序对象后,记得删掉,我们不再需要
glDeleteShader(fragmentShader);
惯例需要确定程序是否失败,先获取程序是否成功,再获取失败日志打印
glGetProgramiv(shaderProgram, GL_LINK_STATUS,&success);
if(!success)
{
glGetProgramInfoLog(shaderProgram, 512,NULL, infoLOG);
}
链接顶点属性
告诉GPU如何处理这个内存的顶点数据,以把顶端数据及链接到顶点着色器的属性上
这时候需要用glVertexAttribPointer这个函数,范例如下:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
第一个参数 0: 顶点属性, 也就是layout(location = 0)已经定义
第二个参数 3: 表示3个数值,vec3
第三个参数 GL_FLOAT:数据类型,表示vec3 3个参数都是float的
第四个参数GL_FALSE: 表示数据是否标准化 Normalize,,也就是坐标系是否都因素到0-1之间,如果不是就是false,如果是,就是GL_TRUE.
第五个参数 3* sizeof(float): 步长(stride),本次表示下个数组(顶点属性组)在3个float之后。(该参数需要深入了解含义和用途)
第六个个参数 (void*)0: 偏移量 offset, 表示数据在缓冲起始位置的缓冲量,因为位置数据在数组的开头,所以是0.
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices, GL_STATIC_DRAW); //复制顶点数组到缓冲
glVertexAttribPoint(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);//
glEnableVertexAttribArray(0);//启用顶点属性0
glUseProgram(shaderProgram);//使用着色器程序
顶点数组对象 VAO
VAO也需要和VBO一样被绑定,这样后续的顶点属性调用都会存储在这个VAO中,
当配置顶点属性指针时,只需要调用一次,后续再绘制就只需要绑定对应的VAO就好了
**
OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
**
如下,VAO
//创建一个VAO
unsigned int VAO;
glGenVertextArrays(1,&VAO);
glBindVertexArray(VAO);//绑定
glDrawArrays(GL_TRINGLES,0,3); //绘制三角形,0是顶点数组的其实索引,最后一个参数3小时3个顶点的意思
glfwSwapBuffers(window);