一个三角形
三个单词:
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO 使用步骤:create 存储数据 绑定 使用
- 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
屏幕都是2d的,但是绘制都是3d的,那么需要将2d转换为3D,这个通过opengl的图形渲染管线完成的。
最终就是将3d转换为2D,然后转换为具体的像素。
· 2d像素和2d不一样,这个颜色值会受到分辨率的影响。opengl坐标变换到屏幕坐标上,一个像素不一定对应一个颜色值(线性 临近 mipmap等)。
多重采样,来处理锯齿。
图形渲染管线可以被划分为几个阶段
,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化
的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行
的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
有些部分是可以使用用户自己定义的。
每个部分的输出作为下一个部分的输入,每个阶段都是一段程序,有的可以自己定义,有的不可以自己定义。
图形绘制运行在GPU上,节约了cpu资源。
每个部分都是将点数据转换为像素的过程。
过程
1.3d顶点{顶点数据}传递给管线 ,每个顶点{顶点属性}是一个3d集合
2.告诉它是需要绘制什么 线 三角形??
一般地,传递3个3D坐标表示三角形(顶点数据),它是一系列顶点的集合。
顶点着色器
将一个单独的顶点作为输入,目的是将3d坐标转换为另一种3d坐标,并且运行对3d坐标进行一些处理。
图元装配
顶点着色器的输出作为输入,将所有的点装配为一个指定的图元形状
这个部分的输出传递给几何着色器,将图元形式的一些顶点作为输入,构造出新的图元形状。
几何着色器
几何着色器图元一系列顶点的集合作为输入,可以通过新图元构造出一个基本图形。
裁剪
着色器之前会进行裁剪,裁剪去屏幕之外的部分。
光栅化
几何着色器输出回传入光栅化阶段,它会把图元映射到屏幕的相应像素,供给片段着色器使用的片段,
open GL的每一个片段是Opengl渲染一个像素所需要的所有数据。(颜色、深度等值)
片段着色器
片段着色器是计算一个最终的像素,包含了光照,阴影,光照颜色等,计算出来的一个最总颜色。
颜色确定之后会进行一个alpha和混合阶段。深度值来判断在前还是在后,决定是否应该丢弃
可以对alpha值进行判断,丢弃掉一些不需要的片段。
alpha测试
会检查alpha,对物体进行混合,所有在片段着色器中计算出一个值,
一般的情况,我们只关心片段和片元即可,其他的使用默认。
顶点输入
开始绘制,需要先给一些顶点数据,opengl不是简单的将3d坐标转换为2d坐标,它只能处理-1到1范围内的,这个范围叫做标准化设备坐标,最终会显示在屏幕上。
三角形3个顶点以标准化形式展示,给定一个float数组。
-0.5F,-0.5F,0.0f,
0.5f,-0.5f,0.0f,
0.0f,0.5f,0f
渲染一个2维图形,将z设置为0,三角形的深度是一样的(深度z,glfrood来获取),实现一个2d效果。
一旦顶点经历了顶点着色器处理之后,就变为了标准化坐标了,在-1到1之外的就会被抛弃。
z一般表示的是深度,空间距离你的距离,距离远的会被抛弃,以节省资源。
顶点数据准备好之后,将数据发送给第一给处理阶段:顶点着色器,它会在GPU上存储我们的顶点数据,还需要配置open gl怎样解释内存,如何发送到显卡,顶点着色器会处理我们在内存中指定的顶点数据。
解释一下:
1.我们给的顶点数据可能是单一的,可能是所有的东西都在一起,所以我们应该如何分配这些数据。
2.数据如何发送?使用缓冲区?使用的是属性、uniform还是其他的。
cpu发送到GPU是很慢的,所以我们需要需要一次发送比较多的信息。
顶点缓冲对象
会在GPU中存储大量的顶点,好处就是可以一次发送一大批的数据去显卡(之前是每个顶点发送一次)。cpu将数据发送到显卡相对较慢,需要我们要尝试一次发送尽可能多的数据,发送到显卡之后,会被GPU立即访问。
顶点缓冲区对象,opengl可以存在多个缓存区,只要是不同类型的。可以使用glGenBuffers生成一个缓冲ID。
OPENGL有许多缓冲区对象类型,比如:GL_ARRAY_BUFFER
使用
//创建缓冲区
int VBO;
glGenBuffers(1,&VBO);
//绑定缓冲区数组
glBindBuffer(GL_ARRAY_BUFFER,VBO);
下来我们可以对这个缓冲区进行配置
- 存入数据
glBufferData(GL_ARRAY_BUFFER,size,vertices,GL_STATIC_DRAW);
glBufferData将用户定义的数据绑定到当前缓冲区函数。
-
目标类型
-
数据大小
-
数据
-
如果管理
- GL_STATIC_DRAW :数据不会或几乎不会改变。 不会改变的
- GL_DYNAMIC_DRAW:数据会被改变很多。 会发生改变的
- GL_STREAM_DRAW :数据每次绘制时都会改变。 会发生改变的
根据顶点数据如何变化,选择一个合适的模式。
顶点着色器
第一个可编程的着色器。渲染需要一个顶点着色器一个是片段着色器。
之前说过,GPU每一个阶段都是一个小的程序代码,有的是可以编程的。
所以写代码,我们使用的是GLSL语言。
attribute vec3 aPos;
void main(){
gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0f);
}
属性是一个4个float组成的东西。
为了设置顶点着色器的输出,我们需要将position给gl_Position,它是一个vec4类型的东西。
顶点的数据并不是简单的赋值,他将顶点数据变为着色器顶点
上面的数据都是0.x的形式,在实际中,这个不是必须的,根据实际情况进行定义,比如宽高为720 x 1280
就可以将上面的0.5写为360 640.
编译着色器
为了pengl使用它,我们动态的编译源代码。
- 先得到一个着色器的ID.
int vertexShader;
vertextShader = glCreateShader(GL_VERTEX_SHADER);
- 将代码附着在着色器上面。
glShaderSource(vertexShader,1,&vertextShaderSource,NULL);
- 进行编译
glCompileShader*(vertexShader);
片段着色器
第二个可以更改的部分。计算像素最终的颜色输出。片段着色器需要一个输出变量,表示最终输出的颜色,将最终的结果,给Fragcolor
void main(){
FragColor = vec4(1.0f,1.0f,1.0f,1.0f);
}
- 创建片段着色器
- 附着程序
- 编译
int fragmentShader;
glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource,null);
glCompileShader(fragmentShader);
将两个着色器对象连接到一个着色器程序中。
着色器程序
着色器程序是合并之后并最终连接完成的版本,如果要使用我们还需要将他们连接为一个可执行程序对象,然后激活它
它会将每个着色器连接到下一个着色器的输入。
int shaderProgram;
shaderProgram = glCreateProgram();
将两个部分附着到程序对象上,并连接他们
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(shaderProgram);
下一步就是使用它.
glUseProgram(shaderProgram);
每一个着色器调用和渲染调用都会使用这个程序。
· 注意这个时候可以删除我们的着色器对象了,因为不需要了。
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
发送数据给GPU
- 指示在GPU中如何使用顶点和片段着色器
- 如何使用内存
- 顶点数据给属性
连接顶点属性
顶点着色器允许我们指定任何以顶点属性为形式的输入。所以我们需要指定那一部分属于什么。如何解释数据。
- 位置数据存储为32位浮点值
- 每个位置包含3个值
- 值之间没有间隙
- 数据中第一值在缓冲的开始。
如何解释?
1.位置顶点属性
2.指定属性的大小
3.值当数据的类型
4.是否希望数据标准化(所有数据都会映射到0到1之间)
5.步长
6.偏移量(或者buffer)
glVertexAttribPointer(glPosition,3,GL_FLOAT,GL_FALSE,3*sizeof(float),0);
告诉了怎么解释数据,下一步就是启用顶点属性。
glEnableVextexAttribArray(0);
使用顶点缓冲区使用的一个完整案例:
1.生成一个VBO
2.绑定一个ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO);
3.绑定数据
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
4. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
5.启动顶点属性
glEnableVertexAttribArray(0);
6. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
7. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
上面的缺点是,每次每一个属性都需要这么写,可以使用下面的顶点数组对象
顶点数组对象
和顶点缓冲对象那样,任意的顶点属性调用都会存储在vao中,只需要调用一次,以后只需要绑定就可以了。
好处就是在切换顶点的时候比较的快。
使用:
- 创建vao
unsigned int VAO;
glGenVertexArrays(1, &VAO);
- 绑定和配置对于的vao和属性指针
glBindVertexArray(VAO);
- 设置值
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);
- render中
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
元素缓冲对象
元素缓冲对象,索引缓冲对象。简单说就是通过索引知道点如何进行绘制,去除重复的点。
1.创建
unsigned int EBO;
glGenBuffers(1, &EBO);
先绑定glBufferData,将索引放入缓冲区中
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
glBufferData(GL_ELEMENT_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);
绘制
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
缓冲数组做了什么?
// ..:: 初始化代码 :: ..
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 绘制代码(渲染循环中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);