OpenGL学习之着色器与渲染管线
看此篇之前请看https://www.jianshu.com/p/68ee05d6106a
着色器与渲染管线
渲染管线一般是有显示芯片(GPU)内部处理图形信号的并行处理单元组成。这些并行处理单元两两之间是相互独立的,在不同型号的硬件上独立处理单元的数量也有很大的差异。
图中阴影部分是可编程的部分
基本处理
该阶段设定3D空间中物体的顶点坐标,顶点对应的颜色,顶点的纹理坐标等属性,并且指定绘制方式。顶点着色器
顶点着色器是一个可编程的处理单元,功能为执行顶点的变换,光照,材质的应用于计算等顶点的相关操作。工作过程为首先将原始的顶点几何信息及其他属性传送到顶点着色器中,经过自己开发的顶点着色器处理后产生纹理坐标,颜色,点位置等后继流程需要的各项顶点属性信息,然后将其传递给图元装配阶段。
顶点着色器的输入主要为待处理顶点相应的attribute变量,uniform变量,采样器以及临时变量;输出主要为经过顶点着色器生成的varying变量及一些内建输出变量。
顶点着色器的输入数据由下面组成:
Attributes:使用顶点数组封装每个顶点的数据,一般用于每个顶点都各不相同的变量,如顶点位置、颜色等。
Uniforms:顶点着色器使用的常量数据,不能被着色器修改,一般用于对同一组顶点组成的单个3D物体中所有顶点都相同的变量,如当前光源的位置。
Samplers:这个是可选的,一种特殊的uniforms,表示顶点着色器使用的纹理。
Shader program:顶点着色器的源码或可执行文件,描述了将对顶点执行的操作。
输出变量-易变比那里在顶点着色器赋值后并不是将赋的值送入后后继的片元着色器中,而是在光栅化阶段由管线根据片元所属顶点对应的顶点着色器对此易变变量的赋值情况及片元与各项顶点的位置插值产生。
下面是一个用opengl es着色器语言编写的顶点着色器源码,这个顶点着色器使用一个position和跟它相关联的color数据作为输入数据,通过一个4×4矩阵变换位置,然后输出变换后的位置和颜色数据。
// uniforms used by the vertex shader
uniform mat4 u_mvpMatrix; // matrix to convert P from model
// space to normalized device space.
// attributes input to the vertex shader
attribute vec4 a_position; // position value
attribute vec4 a_color; // input vertex color
// varying variables – input to the fragment shader
varying vec4 v_color; // output vertex color
void main() {
v_color = a_color;
gl_Position = u_mvpMatrix * a_position;
}
图元装配(primitive assembly)
顶点着色器之后,渲染管线的下一个阶段是图元装配,图元是一个能用OpenGL ES绘图命令绘制的几何体,绘图命令指定了一组顶点属性,描述了图元的集合形状和图元类型。顶点着色器使用这些顶点属性计算顶点的位置,颜色以及纹理坐标,这样才能传到片元着色器。
这个阶段主要有两个任务,一个是图元组装,另一个是图元处理。
图元组装是指顶点数据根据设置的绘制方式被结合成完整的图元,例如,点绘制方式下仅需要一个单独的顶点,每个顶点为一个图元;线段绘制方式则需要两个顶点,每两个顶点构成一个图元。
图元处理最重要的工作是剪裁,对于每个图元,必须确定它是否位于视椎体内(3维空间显示在屏幕上的可见区域),如果图元部分在视椎体中,需要进行裁剪,如果图元全部在视椎体外,则直接丢弃图元。裁剪之后,顶点位置转换成了屏幕坐标。背面剔除操作也会执行,它根据图元是正面还是背面,如果是背面则丢弃该图元。经过裁剪和背面剔除操作后,就进入渲染流水线的下一个阶段:光栅化。光栅化(Rasterization)
光栅化阶段把图元转换成片元集合,之后会提交给片元着色器处理,这些片元集合表示可以被绘制到屏幕的像素。如下图所示:
- 片元着色器
片元着色器是用于处理片元值及相关数据的可编程单元,其可以执行纹理的采样,颜色的汇总,计算雾颜色等操作。片元着色器主要功能为通过重复执行(每片元一次)将3D物体中的图元光栅化后产生的每个片元的颜色等属性计算出来送入后继阶段;
片元着色器对光栅化阶段产生的每个片元进行操作,需要的输入数据如下:- Varying variables:顶点着色器输出的varying变量经过光栅化插值计算后产生的作用于每个片元的值。
- Uniforms:片元着色器使用的常量数据
- Samplers:一种特殊的uniforms,表示片元着色器使用的纹理。
- Shader program:片元着色器的源码或可执行文件,描述了将对片元执行的操作。
片元着色器也可以丢弃片元或者为片元生成一个颜色值,保存到内置变量gl_FragColor。光栅化阶段产生的颜色、深度、模板和屏幕坐标(Xw, Yw)成为流水线中pre-fragment阶段(FragmentShader之后)的输入。如下图:
Varting0~n值的是从顶点着色器传递到片元活色器的易变数据变量,它是由系统在顶点着色器后的光栅化阶段自动插值产生,其个数不一定。
-
gl_FragColor值的是计算后此片元的颜色。一般在片元着色器的最后都需要对gl_FragColor进行赋值。
下面是一个简单的片元着色器源码,可以跟上面的顶点着色器源码结合绘制一个高洛德着色的三角形。
precision mediump float;
varying vec4 v_color; // input vertex color from vertex shader
void main(void) {
gl_FragColor = v_color;
}
- 追个片元操作阶段(Per-Fragment Operations)
片元着色器之后就是追个片元操作阶段,包括一系列的测试阶段。一个光栅化阶段产生的具有屏幕坐标(x,y)的片元,只能修改framebuffer(帧缓冲)中位置在(x,y)的像素。下图是Opengl es 2.0逐片元操作的过程:
Pixel ownership test:像素所有权测试,决定framebuffer中某一个(Xw, Yw)位置的像素是否属于当前Opengl ES的context,比如:如果一个Opengl ES帧缓冲窗口被其他窗口遮住了,窗口系统将决定被遮住的像素不属于当前Opengl ES的context,因此也就不会被显示。
Scissor test:裁剪测试,决定位置为(Xw, Yw)的片元是否位于裁剪矩形内,如果不在,则被丢弃。
Stencil and depth tests:模板和深度测试,传入片元的模板和深度值,决定是否丢弃片元。
Blending:将新产生的片元颜色值和framebuffer中某个(Xw, Yw)位置存储的颜色值进行混合。
Dithering:抖动,可以用来最大限度的减少使用有限精度存储颜色值到framebuffer的工件。
逐片元操作之后,片元要么被丢弃,要么一个片元的颜色,深度或者模板值被写入到framebuffer的(Xw, Yw)位置,不过是否真的会写入还得依赖于write masks启用与否。write masks能更好的控制颜色、深度和模板值写入到合适的缓冲区。例如:颜色缓冲区中的write mask可以被设置成没有红色值写入到颜色缓冲区。另外,Opengl ES 2.0提供从framebuffer中获取像素的接口,不过需要记住的是像素只能从颜色缓冲区读回,深度和模板值不能读回。
注意:Opengl ES 2.0 的Per-Fragment Operations已经不再支持Alpha test 和 LogicOp了,这两个步骤在 OpenGL 2.0 和 OpenGL ES 1.x中是存在的。Alpha test 阶段不再需要的原因是片元着色器可以丢弃片元,所以可以在片元着色器中执行Alpha test。 LogicOp因为很少使用,所以不再支持了。
- 帧缓冲
OpeGL ES中的物体绘制并不是直接在屏幕上进行的,而是预先在帧缓冲区中进行绘制,每绘制玩一帧再将绘制的结果交换到屏幕上。因此,在每次绘制新的一帧时都需要清除缓冲区中的相关数据,否则有可能产生不正确的绘制效果。
同时,微课应对不同方面的需要,帧缓冲是由一套组件组成的,主要包括颜色缓冲,深度缓冲以及模板缓冲- 颜色缓冲用于存储每个片元的颜色值,每个颜色值包括RGBA 4个色彩通道,应用程序运行时在屏幕上看到的就是颜色缓冲中的内容。
- 深度缓冲用来存储每个片元的深度值,所谓深度值是指以特定的内部格式表示的从片元处到观察点(摄像机)的距离。在启用深度测试的情况下,新片元想进入帧缓冲时需要将自己的深度值与帧缓冲中对应位置的片元的深度值进行比较,若结果小于才可能进行缓冲,否则丢弃。
- 模板缓冲用来存储每个片元的模板值,供模板测试使用。