GPU编程基础

  什么是GPU

  GPU,也就是图形处理器,是位于显卡或主板(现在也能集成到CPU里面)上的一块芯片。这块芯片是干什么的呢?GPU主要用来支持图形数据的大量运算,它就是专门用来干这个的。GPU也不是一开始就是programmable,经过一段时间的发展才变成“可编程图形处理硬件”。

  历史

  早期的GPU(大约上世纪80年代)使用单片集成电路作为图形芯片,90年代初基于数字信号处理芯片的GPU被研发出来。这个时代的GPU能够进行一些2D图像的快速合成工作。1998年,NVIDIA公司宣布modern GPU研发成功,标志着GPU的发展迎来了新的时代。modern GPU使用晶体管进行运算,在微芯片中,GPU所使用的晶体管数量远远超过了CPU。

  第一代modern GPU(1998)包括NVIDIA TNT2,ATI的Rage等,可以独立于CPU进行像素缓存区的更新,并可以光栅化三角面片以及进行纹理操作,但是缺乏三维顶点的空间坐标变换能力。

  第二代modern GPU(1999)可以进行三维坐标转换和光照计算(T&R,3D Object Transformation and Lighting),并且OpenGL和DirectX7都提供了开发接口,支持应用程序使用基于硬件的坐标变换。对纹理的支持增加了立方体纹理。这一阶段GPU包括 NVIDIA GeForce256,ATI Radeon 7500等。

  第三代modern GPU(2001)提供了顶点编程能力,如ATI 8500,NVIDIA GeForce 3,GeForce 4Ti等。这些GPU允许应用程序指定一个序列的指令进行顶点操作控制。

  第四代GPU(2002)迎来了同时支持 vertex programmability 和 fragment programmability的GPU,同时OpenGL和DirectX也都提供了扩展以支持这些新功能。这一时代的GPU能够支持:

  1、vertex programmability 和 fragment programmability。

  2、IEEE32位浮点运算;  

  3、4元向量,4阶矩阵计算;

  4、分支指令,循环语句;

  5、高带宽的内存传输能力;

  6、支持1D、2D、3D纹理像素查询和使用,且速度极快;

  7、支持绘制到纹理功能(Render to Texture ,RTT)。

  为什么要有GPU?

  GPU和CPU在结构上有较大差距,CPU主要是寄存器和控制器,GPU拥有更多的ALU(逻辑运算单元)用作数据处理,而非数据高速缓存和流控制,这样的结构适合对密集型数据进行并行处理。CPU执行计算任务时,一个时刻只处理一个数据,不存在真正意义的并行,而GPU具有多个处理器核,在一个时刻可以并行处理多个数据。

  GPU采用流式并行计算模式,对每个数据进行独立的并行计算。“独立”计算,指的是对某个数据的计算不依赖于其它同类型数据,比如计算一个顶点的世界坐标,不依赖于其它顶点。“并行”计算,指的是同一时间可以对多条数据进行运算,运算时间和对单条数据运算的时间是一样的。

  GPU的优点是快速并行处理大量数据,但是因为对任意元素的计算独立于其它元素,那么对于需要元素之间相关性的算法在GPU上难以实现。比如,射线与不规则物体的求交运算。此外,GPU在控制流方面弱于CPU,GPU中的控制器数量少于CPU。

  GPU图形绘制管线

  图形绘制管线描述GPU渲染流程,即“给定视点、光源、照明模式,和纹理等元素,如何绘制一幅二维图像”。图形绘制管线主要分为三个阶段:程序阶段、几何阶段、光栅阶段。

  应用程序阶段,使用高级编程语言开发,主要和CPU、内存打交道,如碰撞检测、场景图建立、空间八叉树更新、视锥裁剪等算法都在此阶段执行。在该阶段末端,几何体数据(顶点、法向量、纹理坐标)通过数据总线传送到图形硬件。(数据总线是用于在多个设备之间传送数据的通道,端口是在两个设备间传送数据的通道,带宽(字节每秒)用来描述总线或端口的数据吞吐量,数据总线和端口将各个不同部件连接在一起。)

  几何阶段要负责顶点变换、光照计算、裁剪、投影、屏幕映射,该阶段基于GPU进行运算,在阶段末端得到经过变换和投影的顶点坐标、光照颜色、纹理坐标。

  光栅阶段,基于几何阶段的输出数据,为像素正确配色,以便绘制完整图像。该阶段进行的都是单个像素的操作,每个像素的信息存储在颜色缓冲区中(frame buffer 或 color buffer)。

  由于光照计算是在世界空间中计算的(要用到视点、光源、物体的世界坐标,其实只需保证三者在同一坐标空间就够了),因此它属于几何阶段。雾化以及涉及物体透明度的计算属于光栅阶段,因为上述两种计算要要用到深度信息(Z Value),而深度信息是在几何阶段计算,然后传递到光栅阶段。

  几何阶段

  几何阶段主要做的是顶点坐标变换光照计算。显卡中有一个“T&R”硬件,就是“Transform & Lighting”。为什么要进行顶点坐标变换?因为计算机得到的输入是一个三维坐标顶点,但是输出是屏幕上的二维坐标,所以肯定要进行坐标变换。在Unity3D中,从视点发出一条射线到屏幕,射线穿过的世界物体映射到屏幕的坐标正好是射线与屏幕的交点。也就是说,三维顶点坐标变换就是我们观察到的一个三维顶点在某个二维平面的映射。当然,这个变换过程还要进行一些光照计算、剔除、裁剪等操作。一般来说,GPU帮我们自动完成了这个转换,基于CPU的顶点程序为开发人员提供了控制顶点坐标空间转换的方法。

  根据顶点坐标变换的先后顺序,有如下几个坐标空间:模型空间(object space)、世界空间(world space)、摄像机空间(也叫眼睛空间)、裁剪空间(也叫标准视体空间)、屏幕空间。

GPU编程基础_第1张图片

 

  物体空间坐标(object space coordinate)是在3D物体建模时就得到的,这个坐标完全基于物体本身,与其它物体没有任何参照关系。把模型导入世界空间后,模型会获得一个世界坐标。从物体空间坐标到世界空间坐标的转换由一个四阶矩阵控制,通常称为world matrix。需要注意的是,顶点法向量也处于物体空间。将顶点法向量转换到世界空间的矩阵同顶点坐标的转换矩阵是不同的,这个矩阵是world matrix的转置矩阵的逆矩阵。(一个是顶点变换矩阵:world matrix,一个是向量变换矩阵:world matrix的转置的逆,只在world matrix是正交矩阵的时候(比如旋转矩阵),world matrix等于它的转置的逆。)

  相机空间,也就是以相机或者“眼睛”为原点,由视线方向、视角和远近平面,共同组成的一个梯形体的三维空间,称之为视锥。近平面,是梯形体较小的矩形面,作为投影平面,远平面是梯形体较大的矩形面,在这个梯形体中所有的顶点数据是可见的,超出这个梯形体之外的场景数据,会被视点去除(Frustum Culling,也叫视锥裁剪)。

GPU编程基础_第2张图片

 

  从相机空间到屏幕空间由以下三个步骤组成:

  1、用透视变换矩阵把顶点从视锥体中变换到裁剪空间;

  2、在裁剪空间进行图元裁剪;

  3、屏幕映射:将顶点坐标映射到屏幕坐标系上。

  确定只有当图元完全或部分的存在于视锥内部时,才需要将其光栅化。当一个图元完全位于视体内部时,它可以直接进入下一阶段;完全在外的图元,将被剔除;部分位于视体内的图元进行裁剪处理。

  图元装配,Primitive Assembly,将顶点根据primitive原始连接关系还原出网格结构。网格由顶点和索引组成(vertex buffer & index buffer),在之前的流水线中是对顶点的处理,在这个阶段是根据索引将顶点连接在一起,组成线、面、单元。之后就是对超出屏幕外的三角形进行裁剪。此外三角形的顶点顺序能够确定三角形是正面还是背面。如果三角形的法向量朝向视点,则该面是正面。如果该面是反面,则进行背面剔除(back-face culling)。所有的裁剪剔除操作都是为了减少需要绘制的顶点个数。

  裁剪包括视锥裁剪(View Frustum Culling)、背面剔除(Back-Face Culling)、遮挡剔除(Occlusing Culling)和视口裁剪等。裁剪操作不是百分百固定的操作,而是视具体情况采取不同的策略。

  现在我们得到了一堆位于屏幕坐标的三角面片。这些面片将被用于光栅化(Rasterizing)。

  光栅化

  光栅化决定哪些像素被几何图元覆盖的过程。现在我们知道顶点的屏幕坐标值(Screen Coordinate),也知道要绘制的图元(点、线、面)。由于点的屏幕坐标值是浮点数,而像素屏幕坐标是整数,因此这里要进行一个转换,四舍五入。根据两个像素点绘制线段的算法有DDA算法、Bresenham画线算法。根据三个像素点绘制面片的算法有边界填充算法、扫描线多边形填充算法等。这个过程结束后,顶点以及绘制图元(线、面)已经对应到像素。下面该给像素计算颜色值。

  Pixel Operation,也叫Raster Operation,是在更新帧缓存之前,执行的最后一系列针对每个片段的操作,其目的是:计算每个像素的颜色值。在这个阶段,被遮挡面通过一个被称为深度测试的过程消除。Pixel Operation包括:

  1、消除遮挡面;

  2、Texture Operation,纹理操作,根据纹理坐标查询纹理值;

  3、Blending,混色。

  混色通常也叫alpha混合。当在屏幕上绘制某个物体时,每个像素关联一个rgb值和一个z缓冲器深度值,还有一个alpha值,用来描述给定像素处的物体透明度。

  Cd = a*Ca + (1-a)Cs 【over操作】

  a是透明度,Ca是透明物体的颜色,Cs表示混合前像素的颜色,Cd是最终计算得到的颜色值。如果要绘制透明物体,通常需要对物体进行排序。不透明物体首先绘制,然后在上方对透明物体按照由后到前的顺序进行混合处理。如果按照任意顺序进行混合,可能产生严重失真。排序需要用到z buffer。

  Filtering是经过某种计算(滤镜)得到最终的颜色。该阶段后,像素颜色被写入帧缓存。

  图形硬件

  Z Buffer,又叫depth buffer,即深度缓冲区,存储视点到每个像素对应的空间点的距离,称为Z值或深度值,范围在0 - 1之间。最接近眼睛的点(近裁剪面)的深度值为0,最远的(远裁剪面)点深度值为1。使用Z buffer可以用来判断空间点的遮挡关系。有Z值的范围可知,它不是实际的视点到顶点的距离,而是一种保留了与其它同类型值的相对大小的相对值。

  在平行投影中同一图元相邻像素的Z值是线性关系的,但在透视投影中则是非线性的,而且非线性程度随着空间点到视点的距离增加而越发明显。因为图元内部点的屏幕坐标必须由已知顶点差值获得,所以图元内部点的Z值也是差值得到的。下图AE线段是某个三角面片的两个顶点组成,投影到屏幕空间对应像素1和像素5.光栅化时,需要对像素2、3、4进行差值计算。从图中可以看出,点B、C、D并不是均匀分布在空间线段上的。如果离视点越远,这种差异就越突出。也就是说,在投影面上相等的步长,在空间中对应的步长会随着视点距离的增加而边长,对像素的Z值进行差值所得到的差值Z值并不能真实反映对应空间点的深度。为了避免这种情况,在设置相机的远裁剪面和近裁剪面时,两者比值应尽量小于1000。

GPU编程基础_第3张图片

 

  Frame Buffer,称为帧缓冲区,用于存放显示输出的数据,一般是像素的颜色值。Frame Buffer有时也被认为是Color Buffer和Z Buffer的组合。Frame Buffer一般位于显卡,如果显卡被集成到主板,Frame Buffer会被放在内存区域。

  Shader Language

  Shader Language,称为着色语言,早期的Shader基于物体的属性和光照来计算像素的颜色,但是现在shasder已经用于通用计算领域。Shader Language被定位是一种高级语言,不过它还无法独立于硬件。Shader Language完全依赖GPU的支持,GPU编程技术的发展本质上还是图形硬件的发展。在Shader Language之前,基于图形硬件的编程只能靠汇编语言。目前Shader主要有基于OpenGL的GLSL,和基于Direct3D的HLSL。

  Vertex Program运行在Programmable Vertex Processor(可编程顶点处理器)上,Fragment Program运行在Programmable Fragment Processor(可编程片段处理器)上。

GPU编程基础_第4张图片

 

  从这张图可以看出,顶点着色器控制顶点坐标转换过程,片段着色器控制像素颜色计算过程,前者的输出是后者的输入。

  顶点着色器

  顶点着色程序(vertex shader program)从GPU前端模块(寄存器)中提取图元信息(顶点、法向量、纹理坐标等),并完成顶点坐标空间转换、法向量转换、光照计算等操作,然后将数据传到指定寄存器;片段着色程序从中获取需要的数据,通常为纹理坐标、光照信息等,并根据这些信息以及纹理信息(如果有的话)逐片段进行颜色计算,最后将处理后的数据给光栅操作模块。

  顶点和片段通常同时存在,前者的输出作为后者的输入。如果只有顶点没有片段,那么顶点之前的内部点会按照硬件默认方式自动差值计算。而片段程序是对每个片段进行独立颜色计算,并且自己编写算法。

  由于GPU对数据进行并行处理,所以每个数据都会执行一次shader程序。每个顶点执行一次顶点程序,没个片段执行一次片段程序。

GPU编程基础_第5张图片

 

  上图展示了顶点和像素着色器的数据处理流程。在应用程序中的图源信息(顶点位置坐标、颜色、纹理坐标)传递到vertex buffer,纹理信息传递到texture buffer。

  片段着色器

  顶点着色器主要进行几何运算,片段着色程序主要是计算最终颜色。

  片段其实是所有三维顶点在光栅化之后的数据集合,这些数据还没有经过深度值比较,而屏幕的像素是经过深度比较的。

你可能感兴趣的:(编程)