重写Opengl版本

一个三角形

2022-07-13  本文已影响0人  大旺旺的弟弟小旺旺

三个单词:

屏幕都是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将用户定义的数据绑定到当前缓冲区函数。

根据顶点数据如何变化,选择一个合适的模式。

顶点着色器

第一个可编程的着色器。渲染需要一个顶点着色器一个是片段着色器。
之前说过,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使用它,我们动态的编译源代码。

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

连接顶点属性

顶点着色器允许我们指定任何以顶点属性为形式的输入。所以我们需要指定那一部分属于什么。如何解释数据。

如何解释?

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中,只需要调用一次,以后只需要绑定就可以了。
好处就是在切换顶点的时候比较的快。

使用:

unsigned int VAO;
glGenVertexArrays(1, &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);
// 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);
上一篇 下一篇

猜你喜欢

热点阅读