MelonTeam 移动终端前沿技术的探索者

OpenGL学习笔记(二)——渲染管线&着色语言

2017-08-31
vianhuang

| 导语 渲染管线(渲染流水线),一般由显示芯片(GPU)内部处理图形信号的并行处理单元组成。这些并行处理单元两两之间相互独立。不同的型号硬件上独立处理单元的数量有很大差异。 与CPU串行执行不同,渲染管线中的各个处理单元并行处理,渲染效率可以得到极大地提升。

  • 1. 渲染管线
    • 1.1 OpenGl ES1.0 渲染管线
      • 1.1.1 基本处理
      • 1.1.2 顶点缓冲对象
      • 1.1.3变换和光照
      • 1.1.4图元装配
      • 1.1.5光栅化
      • 1.1.6 纹理环境和颜色求和
      • 1.1.7 深度测试和模板测试
      • 1.1.8 帧缓冲
    • 1.2 OpenGl ES2.0 渲染管线
      • 1.2.1 顶点着色器
      • 1.2.2 片元着色器
  • 2. 着色语言
    • 2.1. 数据类型
      • 2.1.1 标量: bool, int, float
      • 2.1.2 向量
      • 2.1.3 矩阵
      • 2.1.4 采样器
      • 2.1.5. 结构体
      • 2.1.6 空类型:void
    • 2.2. 限定符
      • 2.2.1 attribute
      • 2.2.2 uniform
      • 2.2.3 varying
    • 2.3. 程序基本结构
    • 2.4. 内建变量
      • 2.4.1. 顶点着色器中的内建变量
      • 2.4.2. 片元着色器中的内建变量

1. 渲染管线

1.1 OpenGl ES1.0 渲染管线

[ OpenGl ES1.0 渲染管线 ]

1.1.1 基本处理

该阶段设定3D空间中物体的顶点坐标,顶点对应颜色,顶点的纹理坐标等属性。并且之指定绘制方式:点绘制,线绘制,三角形绘制。

1.1.2 顶点缓冲对象

这部分功能在程序中是可选的。对于某些场景下顶点的基本数据不变的情况。可以在初始化阶段将顶点数据经过基本处理后直接送入顶点缓冲对象。在绘制每一帧时就可以直接从缓冲对象中取顶点数据,一定程度上节省了GPU的IO带宽和提升渲染效率吧。

1.1.3变换和光照

  • 顶点变换任务:对3D物体的各个顶点进行平移,旋转和缩放等操作。
  • 光照计算任务:根据程序送入的光源位置,性质,各通道强度,物体材质,计算各顶点的光照情况。

1.1.4图元装配

  • 图元组装:顶点数据根据设置的绘制方式被结合成完整的图元。
    例如: 点绘制方式仅需要一个单独的顶点,此方式下每个顶点为一个图元。

         线绘制方式需要两个顶点,此方式下每两个顶点构成一个图元。
    
  • 图元处理:主要工作就是裁剪,消除半空间之外的部分几何图元。
    之所以裁剪是因为随着观察位置,角度的不同,并不总能看到3D物体的某个图元的全部。
    例如:当观察一个正四边体并离某个三角形面很近时,可能只能看到此面的一部分。这时在屏幕上显示的就不再是三角形,而是经过裁剪后的多边形。如图所示:

[ 从不同距离不同角度观察正四面体 ]

1.1.5光栅化

由于虚拟3D世界当中物体的几何信息一般采用连续的数学量来表示。但是目前的显示设备屏幕都是离散化的(由一个个像素组成)因此还需要讲投影结果离散化,将其分解成一个个离散化的小单元,这些小单元一般称为片元。这些片元都对应帧缓冲区中的一个像素。

[ 投影后图元离散化 ]

1.1.6 纹理环境和颜色求和

  • 纹理采样任务:从纹理图中某个纹理坐标位置获取该位置颜色值。
  • 颜色求和:根据纹理采样值和光照计算等结果生成片元的最终颜色。

1.1.7 深度测试和模板测试

  • 深度测试:将输入片元的深度值与帧缓冲区中存储的对应位置的片元的深度进行比较,若输入片元的深度值小则将输入片元送入下一阶段准备覆盖帧缓冲区中的原片元,或者与原片元混合。否则丢弃输入片元。

1.1.8 帧缓冲

物体预先在帧缓冲区中进行绘制,每绘制完一帧再将绘制完的结果交换到屏幕上。因此每次绘制新的一帧时需要清除缓冲区中的相关数据,否则有可能产生不正确的绘制效果。


1.2 OpenGl ES2.0 渲染管线

[ OpenGl ES2.0 渲染管线 ]

OpenGL ES2.0 中“顶点着色器”取代了OpenGL ES1.0渲染管线的“光照和变换”阶段。
OpenGL ES2.0中“片元着色器”取代了OpenGL ES1.0渲染管线中的“纹理环境和颜色求和”,“雾”,“Alpha测试”等阶段。

1.2.1 顶点着色器

其工作过程为首先将原始的顶点几何信息及其他属性传送到顶点着色器中,经过自己开发的顶点着色器处理后产生纹理坐标,颜色,点位置等后续流程需要的各项顶点属性信息,然后将其传递给图元装配阶段。

[ 顶点着色器工作原理 ]

顶点着色器的输入包括:

  • 着色器程序——描述顶点上执行操作的顶点着色器程序源代码或者可执行文件。
  • 属性变量(attribute)——用顶点数组提供的每个顶点的数据。
  • 统一变量(uniform)——顶点着色器使用的不变数据。
  • 采样器——代表顶点着色器使用纹理的特殊统一变量类型。

顶点着色器的输出包括:

  • 内建输出变量——例如gl_Position,经过变换矩阵变换后的顶点最终位置。
  • 易变变量(varying)——从顶点着色器计算产生并传递给片元着色器的数据变量。

1.2.2 片元着色器

片元着色器是用于处理片元值及其相关数据的可编程单元,其可以执行纹理的采样,颜色的汇总,计算雾颜色等操作,每片元执行一次。片元着色器主要功能为通过重复执行(每片元一次),将3D物体中的图元光栅化后产生的每个片元的颜色等属性计算出来送入后继阶段。

[ 片元着色器工作原理 ]

片元着色器的输入包括:

  • 易变变量(varying)—从顶点着色器传递到片元着色器的易变变量数据。

片元着色器的输出包括:

  • gl_FragColor ——计算后的片元颜色,一般在片元着色器的最后都会对gl_FragColor 进行赋值。

2. 着色语言

2.1. 数据类型

2.1.1 标量: bool, int, float

顶点着色器中可以直接声明使用浮点类型变量,而片元着色器中需要指定浮点类型变量的精度,否则会产生编译错误。
3种精度类型:

  • lowp
  • mediump
  • highp

    //声明精度的方法一 lowp float color; varying mediump vec2 coord; highp mat4 m;

    //声明精度的方法二 //在片元着色器第一句声明,整个片元着色器将使用同一精度 precision mediump float;

2.1.2 向量

向量类型 说明 向量类型 说明
vec2 包含2个浮点数的向量 ivec4 包含4个浮点数的向量
vec3 包含3个浮点数的向量 bvec2 包含2个布尔值的向量
vec4 包含4个浮点数的向量 bvec3 包含3个布尔值的向量
ivec2 包含2个整数的向量 bvec4 包含4个布尔值的向量
ivec3 包含3个整数的向量    

分向量访问方式:

  • 将一个向量看做颜色时,可以使用r, g, b, a这4个分量名
  • 将一个向量看做位置时,可以使用x, y, z, w这4个分量名
  • 将一个向量看做纹理坐标时,可以使用s, t, p, q这4个分量名
  • 还可以将向量看做一个数组,用下标来访问。

2.1.3 矩阵

3D场景中的移位,旋转,缩放等变换都是由矩阵的运算来实现的。

矩阵类型 说明
mat2 2x2的浮点矩阵
mat3 3x3的浮点矩阵
mat4 4x4的浮点矩阵

2.1.4 采样器

采样器变量不能在着色器中初始化。一般情况下采样器变量都是用uniform限定符来修饰,从宿主语言传递进着色器的值。

采样器类型 说明
sampler2D 用于访问二维纹理
sampler3D 用于访问三维纹理
samplerCube 用于访问立方贴纸纹理

2.1.5. 结构体

//定义结构体CubeInfo
struct CubeInfo{
    vec3 color;
    vec3 position;
    vec2 textureCoor;
}

//声明一个CubeInfo类型的变量
CubeInfo info;

2.1.6 空类型:void

  • 内建变量都是以“gl_”开头,因此用户自定义的变量不允许使用“gl_”开头。
  • 属性变量,一致变量和易变变量在声明的时候一定不能进行初始化。

2.2. 限定符

限定符 说明
attribute 一般用于每个顶点都各不相同的量。如顶点位置,颜色等
uniform 一般用于对同一组顶点组成的单个3D物体中所有顶点都相同的量,如当前的光源位置
varying 用于从顶点着色器传递到片元着色器的变量
const 用于声明常量

2.2.1 attribute

attribute 变量用于接收渲染管线传递进顶点着色器的当前待处理顶点的各种属性值。这些属性值每个顶点各自拥有独立的副本,用于描述顶点的各项特征:顶点坐标,法向量,颜色,纹理坐标等。
attribute限定符只能用于顶点着色器中,不能在片元着色器中使用。且attribute限定符只能用来修饰浮点数标量,浮点数向量以及矩阵变量,不能用来修饰其他类型变量。

若需要渲染的3D物体中有很多顶点,顶点着色器就需要执行很多次。因此当今主流的GPU中都配置了不止一套顶点着色器的硬件,数量从几套到几百套不等。通过这些顶点着色器的并发执行,可以大大提高渲染效率。

attribute变量的值只能由宿主程序传入渲染管线,相关代码如下:

//获取顶点位置属性引用的值
int maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");

//将顶点位置数据传进渲染管线
GLES20.glVertexAttribPointer(
    maPositionHandle, //顶点位置属性引用
    3,                //一个顶点的数据个数(x, y, z)
    GLES20.GL_FLOAT;  //数据类型
    false,            //是否规格化
    3*4,              //一个顶点的数据尺寸(每个浮点数4字节,共3*4字节)
    mVertexBuffer     //存放了数据的缓冲区
);

//启用顶点位置数据
GLES20.glEnableVertexAttribArray(maPositionHandle);

2.2.2 uniform

uniform为一致变量限定符,一致变量指的是对于同一组顶点组成的单个3D物体中所有顶点都相同的量。uniform变量可以用于顶点着色器和片元着色器中,支持用来修饰所有的基本数据类型。
uniform变量的值只能由宿主程序传入渲染管线,相关代码如下:

//获取着色器程序中总变换矩阵一致变量的引用
int muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "mMVPMatrix");
//通过一致变量的引用将一致变量值传入渲染管线
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFinalMatrix(mMMatrix), 0);

不同类型的一致变量传入渲染管线的方法也是不同的:

  • glUniformNfv : 将N个浮点数传入渲染管线,其中N的取值为2,3,4
  • glUniformNiv : 将N个整数数传入渲染管线,其中N的取值为2,3,4
  • glUniformMatrixNfv : 将NxN的矩阵传入渲染管线,其中N的取值为2,3,4

2.2.3 varying

varying变量用于将数据从顶点着色器传递到片元着色器。

[ 易变变量工作原理 ]

首先顶点着色器在每个顶点中都对易变变量vPosition进行赋值,接着在片元着色器中接收到的易变变量vPosition其实并不是某个顶点赋的特定值,而是根据片元所在位置及图元中各个顶点的位置进行插值计算产生的值。
从上述描述中可以知道,光栅化后产生多少个片元,就会插值计算出多少套的易变变量,同时,渲染管线就会调用多少次的片元着色器。对于一个3D物体,片元着色器的执行次数远远大于顶点着色器的执行次数。所以GPU硬件中配置的片元着色器数量远远大于顶点着色器数量。

2.3. 程序基本结构

一个着色器程序一般由3部分组成:全局变量声明,自定义函数和main函数。

uniform mat4 uMVPMatrix; 
attribute vec3 aPosition; 
attribute vec2 aTexCoord;
varying vec2 vTexCoord;

void positionShift(){
    gl_Position = uMVPMatrix * vec4(aPosition, 1);
}

void main(){
    positionShift();
    vTexCoord = vTexCoord;
}

着色器程序中要求被调用的函数必须在被调用之前声明。

2.4. 内建变量

2.4.1. 顶点着色器中的内建变量

  • gl_Position(内建输出变量)
    顶点着色器从程序中获得原始的顶点位置数据,这些原始顶点数据在顶点着色器中经过平移,旋转,缩放等数学变换后,生成新的顶点位置。新的顶点位置通过赋值给gl_Position进而传递给渲染管线的后续阶段。

  • gl_PointSize(内建输出变量)
    顶点着色器中可以指定一个点的大小(大小为像素)。并将其赋值给gl_PointSize进而传递给渲染管线的后续阶段。如果没有指定,默认值为1。gl_PointSize一般在指定点绘制后才有意义。

2.4.2. 片元着色器中的内建变量

  • gl_FragCoord(内建输入变量)
    gl_FragCoord(vec4类型)包含了当前片元相对于窗口的位置。

[ gl_FragCoord包含坐标信息 ]

  • gl_FrontFacing(内建输入变量)
    通过gl_FrontFacing(bool类型)知道该片元是否属于在光栅化阶段生成此片元的对应图元的正面。

  • gl_FragColor(内建输出变量)
    gl_FragColor(vec4类型)用于给片元着色器写入计算完成的片元颜色值。此颜色值将被传入渲染管线的后续阶段继续处理。

  • gl_FragData(内建输出变量)
    gl_FragData(vec4数组类型)。


下一篇 浅析Binder机制

说一说

目录