上篇了解了下 OpenGL,那接下来就开始进一步了解一下它吧。在开发 OpenGL 程序过程中我们会经常用到 着色器(顶点着色器、片元着色器),话不多说进入正题
着色器语言
在这之前先来了解一下必要的名词
图元:是图形软件中用来描述各种图形元素的函数(图像组成的基本单元)
GLSL :专门为图形开发设计的编程语言
可编程管线的编程阶段 渲染管线
渲染管线(又称渲染流水线):是显示芯片内部处理图形信号互相独立的并行处理单元!
图有些糙 凸(艹皿艹 )(下图)
渲染流程
流程:OpenGL首先接收用户提供的几何数据,并且将他输入的数据经过一系列着色阶
段中进行处理,包括顶点着色 ,细分着色,以及几何着色器,然后被送入光栅化单元,光栅化负责剪切区域内的图元并生成片元数据,然后对每一个片元都执行一个片元着色器,然后产生我们最终想要的效果。
那在这些步骤 里面都做了什么呢?
那前面我们也讲了,OpenGL需要将所有的数据都保存到缓存对象中,这也就相当于在OpenGL的服务端维护一块内存区域。当缓存初始化完毕后,我们通过调用OpenGL的绘制命令来渲染几何图元(例:glDrawArray命令,就是一个常用的绘制命令)
1、那我们把数据传过来之后OpenGL首先是顶点着色,
顶点着色器(必要):接收顶点缓存对象的顶点数据,单独处理每个顶点
2、当顶点处理完后,会**细分着色器,
细分着色器(可选):接收到来自顶点着色阶段的输出数据,并对收到的数据进一步处理
(用Patch描述一个物体的形状,在OpenGL线管内部生成新的几何体,并且模型的外观也会变得更加平顺,细分作色器会用到两个阶段来分别管理patch数据,并生成最终状态)
3、几何着色器(可选):在管线内部会对所有的图元进行修改,改变几何图元的类型,或者放弃所有的几何体。如果启用,那么输入可能会来自顶点着色阶段完成的几何图元,也可能来自细分阶段的图元数据
4、那以上的阶段都是顶点数据的处理,顶点着色决定了一个图元应该位于屏幕的什么位置,那接下来我们会用片元着色决定片元的颜色。
接下来这些顶点数据和顶点之间如何让构成几何图元的所有信息会被传入到OpenGL当中。然后将这些顶点与相关的几何图元之间组织起来,准备光栅化(图元装配)
5、在光栅化之前,有些顶点会落在视口之外,那此时与顶点相关的图元会做出改动,来保证相关像素不会在视口之外绘制。(这就是剪切)(视口:我们进行绘制的窗口区域),
6、剪切完之后马上就会进入光栅化,光栅化:将更新后的图元传递到光栅化单元,生成对应的片元。
7、然后接下来就是片元操作 在这个阶段里面,会计算每一个片元的颜色和深度值,然后传递到管线的片元测试和混合的模块(混合我们的效果), 接下来就是最后的独立片元处理过程 这个阶段里面会使用深度测试和模版测试的方式来决定一个片元是否可见。
如果一个片元通过了所有**测试,那他就会保存到帧缓存里面,它的对应的的像素的颜色值会被更新。如果开启了融合模式,那么片元的颜色会与该像素当前颜色相叠加形成新的颜色值并写入帧缓存中!
片元着色器(必要):这是管线最后一部分,用来处理OpenGL 光栅化之后生成的单独片元,并且这个阶段也必须绑定一个着色器。(在这个阶段里面,计算一个偏远的颜色和深度值,然后传递到管线的片元测试和混合的模块。)
可编程管线的 编程阶段:
在OpenGL中,我们可以使用四种shader阶段。
顶点着色器(必要):接收顶点缓存对象的顶点数据,单独处理每个顶点
细分着色器(可选):接收到来自顶点着色阶段的输出数据,并对收到的数据进一步处理
(用Patch描述一个物体的形状,在OpenGL线管内部生成新的几何体)
几何着色器(可选):在管线内部会对所有的图元进行修改,改变几何图元的类型,或者放 弃所有的几何体。如果启用,那么输入可能会来自顶点着色阶段完成的几何图元,也可能来 自细分阶段的图元数据
片元着色器(必要):作为管线最后一部分,用来处理OpenGL 光栅化之后生成的单独片元,并且这个阶段也必须绑定一个着色器。(在这个阶段里面,计算一个偏远的颜色和深度值,然后传递到管线的片元测试和混合的模块。)
最常见的就是:
vertex shaders——它们可以处理顶点数据;
fragment shaders,它们处理光栅化后生成的fragments。
还有Tessellation(细分着色器)和geometry (几何着色器)都是可选的。
顶点着色器和片段着色器 是每个OpenGL程序必不可少的部分。
分析简单的 着色器
#version 330 core //版本号
in vec4 vPosition;//in数据入口 ,vec4 (x,y,z,w)向量(w叫做齐次坐标) vPosition变量名//顶点
uniform mat4 modelViewProjectionMAtrix ;//矩阵 来做矩阵变换
void main(){
//内置变量
glPosition = modelViewProjectionMAtrix * vPosition;
}
来看一下这些代码都是什么意思
1、大家看到在程序的起始位置总是要使用 #version来声明所使用的版本
2、OpenGL会使用输入和输出变量来传输着色器所需的数据。
in在这里是输入的意思,将数据内容拷贝到着色器中
out是输出的意思,将着色器的内容拷出去
这些变量会在OpenGL每次执行着色器的时候更新。
3、Unifrom变量的作用 ——直接从OpenGL应用程序中接收数据。
该变量不会随着顶点或者片元的变换而变化,他对于所有的几何体图元的值都是一样的,除非应用程序对它进行更新。
4、Main()函数:着色器程序和C程序类似,都是从main()函数开始执行的
每个着色器程序一开始都是这个样子
5、基本语法
(1)注释:支持单行注释符 "//" 和 多行注释符 "/*……*/ "
(2)main函数没有返回值
(3)每行结尾都必须有一个分号
ok,上面对着色器有个大概的了解下面来看看着色器的基本结构吧
程序基本结构
组成 : 一个着色器程序一般由3大部分组成:全局变量声明,自定义函数, main函数
案例:
【1】前四行为全局变量声明
attribute属性限定符只能用于顶点着色器中。
verying用于从顶点着色器传递到片元的量
uniform为一致变量限定符
(一致变量指的是 对同一组顶点组成的单个3D物体中所有顶点都相同的量。)
uniform变量可以用在顶点着色器 或者 片元着色器中。
可以修饰所有的基本数据类型
【2】自定义函数部分 void positionShift()
这里这个gl_Position是顶点着色器的内置变量(后面会介绍)
【3】主函数 voidmain()
注意: 着色器程序中要求被调用的函数必须再调用之前声明!!!
【4】拓展知识
gl_Position是vec4类型的?不是应该是vec3吗,多出来的那个是什么呀?
在3d图形用到了 4x4的矩阵(4行4列),矩阵乘法要求 nxm * mxp(n行m列 乘 m行p列)才能相乘
注意:m是相同的,所以 1x4 * 4x4 才能相乘(nxm * mxp)。