GPU与CPU区别
- CPU在结构设计上大部分面积为控制器和寄存器,适合进行数据告诉缓存、流的控制、逻辑运算。
- CPU执行任务是根据时间片轮转,不是真正意义上的并行,一个时刻只能处理一个数据。
- GPU在结构设计上大部分都是ALU(Arithmetic Logic Unit,逻辑运算单元)适合对密集型数据进行并行处理,在处理图像数据和复杂算法方面拥有比CPU更高的效率。
- GPU采用流失并行计算模式,对每个数据进行独立的并行计算,但任意一个元素在进行计算的时候不能依赖其它同类型数据(例如进行一个顶点坐标变换,不可以依赖其它顶点的信息),这也是CPU与GPU不能互相替代的一个原因,多个数据可以同时被使用,多个数据并行运算的时间和一个数据单独执行的时间一样。
显示屏是二维的,GPU 所需要做的是将三维的数据,绘制到二维屏幕上,让二维的画面看起具有三维立体感,这过程进行的顶点变换的都是为了这个目的而存在。
CPU内存资源
- 顶点定义:CreateVertexDeclaration()
- 顶点缓存:CreateVertexBuffer()
- 索引缓存:CreateIndexBuffer()
- 贴图:D3DXCreateTextureFromFile()
- 摄像机:D3DXMatrixLookAtLH()
- 投影:D3DXMatrixPerspectiveFovLH()
CPU从内存中发送以下资源到显存供GPU使用
- 顶点定义:SetVertexDeclaration()
- 顶点缓存:SetStreamSource()
- 索引缓存:SetIndice()
- 贴图:SetTexture()
- 坐标系变换矩阵:SetTransform()
- 渲染状态:SetRenderState()
- 贴图采用方式:SetSamplerState()
图形绘制管线
图形绘制管线主要分为三个主要阶段应用程序阶段,几何阶段,光栅阶段。
应用程序阶段
使用高级编程语言(C、C++等)进行开发,主要和CPU、内存打交道,诸如碰撞检测、场景图建立、空间八叉树更新、视锥裁剪等经典算法都在此阶段执行。在该阶段的末端,几何体数据(顶点坐标、法向量、纹理坐标、纹理等)通过数据总线(数据总线是一个可以共享的通道,用于在多个设备之间传送数据)传送到图形硬件(时间瓶颈);
在应用程序阶段,首先,我们需要准备好场景数据,例如摄像机的位置、视锥体、场景中包括了哪些模型、使用了哪些光源等。其次,为了提高渲染性能,我们往往需要做一个粗粒度剔除(culling)工作,以把那些不可见的物体剔除出去,这样就不需要再移交给几何阶段进行处理。最后,我们需要设置好每个模型的渲染状态,这些渲染状态包括但不限于它使用的材质(漫反射颜色、高光反射颜色)、使用的纹理、使用的shader等。这一阶段最重要的输出就是渲染所需的几何信息,即渲染图元(点、线、三角面等)。
大致流程
- 把数据加载到显存中(渲染所需要的数据都从硬盘中加载到系统内存;网格和纹理等数据又被加载到显存中,这是因为显卡对显存的访问速度更快)
- 设置渲染状态(定义场景中的网格是怎样被渲染的,如使用哪个顶点着色器、片元着色器、光源属性、材质等)
- 调用Draw Call
几何阶段
几何阶段,主要负责顶点坐标变换、光照、裁剪、投影以及屏幕映射也可以理解为负责和每个渲染图元打交道,进行逐顶点、逐多边形的操作,该阶段基于 GPU 进行运算,在该阶段的末端得到了经过变换和投影之后的顶点坐标、颜色、以及纹理坐标。几何阶段的一个重要任务就是把顶点坐标变换到屏幕空间中,再交给光栅器进行处理。这一阶段将会输出屏幕空间的二维顶点坐标,每个顶点对于的深度值、着色等相关信息,并传递给下一个阶段。
光照计算属于几何阶段,因为光照计算涉及视点、光源和物体的世界坐标,所以通常放在世界坐标系中进行计算。
深度值信息( Z 值),而深度值是在几何阶段中计算,并传递到光栅阶段的。
大致流程
- 顶点着色器(完全可编程的,用于实现顶点的空间变换、顶点着色等功能)
- 曲面细分着色器(可选的着色器,用于细分图元)
- 几何着色器(可选着色器,用于执行逐图元的着色操作,或者被用于产生更多的图元)
- 裁剪(可配置的,将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。)
- 屏幕映射(不可配置和编程,负责把每个图元的坐标转换到屏幕坐标系中)
顶点坐标变换
将几何图元(例如三角形)的顶点从模型坐标系变换至显示屏坐标系。
模型介绍
模型通常是建模软件导出的文件。里面包含模型在渲染时需要的数据
- 模型的纹理数据
- 模型空间下的模型的坐标数据
- 模型空间下的模型的法线数据,在 GPU 的顶点程序中必须将法向量转换到 world space 中才能使用,如同必须将顶点坐标从 object space 转换到 world space 中一样,但两者的转换矩阵是不同的,准确的说,法向量从 object space 到 world space 的转换矩阵是 world matrix 的转置矩阵的逆矩阵
坐标系
模型坐标系
对象/模型坐标系(Object space),模型以自身做参考的坐标系。世界坐标系
世界坐标系(World space),模型在虚拟空间根据虚拟空间原点坐标为参考的坐标系。观察者坐标系
观察者坐标系(Eye space),以 camera(视点或相机)为原点,由视线方向、视角和远近平面,共同组成一个梯形体的三维空间。窗口坐标系
屏幕/窗口坐标系(Clip and Project space)。
空间
模型空间
模型空间(对象空间、局部空间)是一个三维空间。每个模型都有自己独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。
模型空间的原点和坐标轴通常是由美术人员在建模软件里确定好的。在游戏中我们可以在顶点着色器中访问到模型的顶点信息,其中包含了每个顶点的坐标。这些坐标都是相对于模型空间中的原点。世界空间
世界空间是一个三维空间,它建立了我们所关心的最大空间。
世界空间可以被用于描述绝对位置,绝对位置指的就是在世界坐标系中的位置。通常,我们会把世界空间的原点放置在游戏空间的中心。观察空间
观察空间(摄像机空间)是一个三维。游戏中,摄像机决定了我们渲染游戏所使用的角度,在观察空间中,摄像机位于原点。
观察空间和屏幕空间是不同的。观察空间是一个三维空间,而屏幕空间是一个二维空间。从观察空间到屏幕空间的转换需要经过投影操作。
为了得到顶点在观察空间中位置,我们可以由两种方法。一种方法是计算观察空间的3个坐标轴在世界空间下的表示,构建出从观察空间变换到世界空间的变换矩阵,再对该矩阵求你来得到从世界空间变换到观察空间的变换矩阵。我们还可以使用另一方法,平移整个观察空间,让摄像机原点位于世界坐标的原点,坐标轴与世界坐标空间中的坐标重合集合。裁剪空间
裁剪空间(齐次裁剪空间)是一个三维空间。
将顶点从观察空间转换到裁剪空间用到的矩阵叫裁剪矩阵,也被称为投影矩阵。
裁剪空间的模板是能够方便地对渲染图元进行裁剪:完全位于这块空间内部的图元将会被保留,完全位于这个空间外部的图元将会被剔除,而与这块空间边界相交的图元将会被裁剪。而决定图元的去留由视锥体决定。
视锥体指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由留个平面包围而成,这些平面也被称为裁剪平面。视锥体有两种类型,这涉及两种投影类型:正交投影和透视投影。
在透视投影中,地板上的平行线并不会保持平行,离摄像机越近网格越大,离摄像机越远网格越小。而在正交投影钟,所有网格大小都一样,而且平行线会一直保持平行。透视投影模拟了人眼看世界的方式,而正交投影则完全保留了物体的距离和角度。
在视锥体的6块裁剪平面中,有两块裁剪平面比较特殊,它们分别被称为近裁剪平面和远裁剪平面。它们决定了摄像机可以看到的深度范围。屏幕空间
屏幕空间是一个二维空间。
观察空间转屏幕空间第一步(标准齐次除非、透视除非):用齐次坐标系的w分量去除以x、y、z分量,得到的坐标叫做归一化设备坐标。
观察空间转屏幕空间第二步:将变换后的x和y坐标映射出生窗口对应的像素坐标。
顶点变换
object space 到 world space
将顶点坐标从模型空间变换到世界空间中,这个变换通常叫模型变换。
这一变换将每个物体模型的位置从以自己为中心的坐标系,摆放到整体游戏世界的具体某一个位置中区。world space 到 eye space
将顶点坐标从世界空间变换到观察空间中,这个变换通常叫做观察变换。
这一变换将每个物体模型的位置以世界为参照系,转换到以观察者为参照系。eye space 到 clip space
将顶点坐标从观察空间变换到裁剪空间,这个变换通常叫做投影变换。clip space 到 project
将顶点从裁剪空间变换到屏幕空间,这个变换通常叫屏幕映射。
这一变换将每个物体模型的位置从观察者所在的坐标体系中,转到观察者投影平面上。在透视模式下的坐标转换是非线性的。
面剔除
- 正向剔除(根据法线的方向判断是否正面)
- 反向剔除(根据法线的方向判断是否反面)
- 视锥剔除(去掉在视锥外的面的部分,在截取过程中,落在屏幕外面的面的部分已经被去除,视锥剔除阶段主要处理落在近端截除平面和远端截除平面之外的面的部分。通过硬件提供的深度缓存(Depth Buffer,或者称为Z-buffer)来判断。这里的深度指面距离镜头所在平面的距离。)
- 遮挡剔除(去掉在覆盖在其他面后面的面的部分,也需要得到深度缓存的支持才可以计算 )
光栅阶段
光栅阶段,基于几何阶段的输出数据进行插值处理,然后为得到的像素进行正确配色及过滤,以便绘制完整图像,并渲染出最终的图像。该阶段进行的都是单个像素的操作,每个像素的信息存储在颜色缓冲器( color buffer 或者 frame buffer)中。其主要目的是计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。
雾化以及涉及物体透明度的计算属于光栅化阶段
Rasterization
光栅化,决定哪些像素被集合图元覆盖的过程。经过几何阶段诸多坐标转换之后,我们得到了每个点的屏幕坐标值,也知道我们需要绘制的图元(点、线、面)。
将以向量为基础结构的面转换成一个个点阵形式的像素。
Pixel Operation
Pixel operation 又称为 Raster Operation,是在更新帧缓存之前,执行最后一系列针对每个片段的操作,计算出每个像素的颜色值。在这个阶段,被遮挡面通过一个被称为深度测试的过程而消除,这其中包含了很多种计算颜色的方法以及技术(Alpha测试、深度测试、模板测试、计算每个像素的雾化值、Alpha混合、浓淡处理、调整gamma值等)。
消除遮挡面
Texture operation
纹理操作,根据像素的纹理坐标,查询对应的纹理值;Blending
混色,根据目前已经画好的颜色,与正在计算的颜色的透明度( Alpha),混合为两种颜色,作为新的颜色输出。通常称为 alpha 混合技术。 当在屏幕上绘制某个物体时,与每个像素都相关联的有一个 RGB 颜色值和一个 Z 缓冲器深度值,另外一个称为是 alpha 值,可以根据需要生成并存储,用来描述给定像素处的物体透明度。如果 alpha 值为 1.0,则表示物体不透明;如果值为 0,表示该物体是透明的。为了在场景中绘制透明物体,通常需要对物体进行排序。首先,绘制不透明的物体;然后,在不透明物体的上方,对透明物体按照由后到前的顺序进行混合处理。如果按照任意顺序进行混合,那么会产生严重的失真。那么就需要用到深度缓冲区(z buffer)来进行排序。Filtering
将正在算的颜色经过某种 Filtering(滤波或者滤镜)后输出新的颜色。
大致流程
- 三角形设置(固定函数阶段)
- 三角形遍历(固定函数阶段)
- 片元着色器(完全可编程的,用于实现逐片元的着色操作。)
- 逐片元操作(不可编程的,负责修改颜色、深度缓存、进行混合等)
- 屏幕图像
概念
图形硬件
主要包括 GPU 中数据的存放硬件,以及各类缓冲区的具体含义和用途,如: z buffer(深度缓冲区)、 stencil buffer(模板缓冲区)、 frame buffer(帧缓冲区)和 color buffer(颜色缓冲区)。
draw call
Draw Call是一个命令(命令GPU进行渲染的命令),这个命令指向一个需要被渲染的图元列表,而不在包含任何材质信息。
给定一个Draw Call时,GPU根据渲染状态和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些漂亮的像素。
顶点着色器的形参是网格渲染组件将所有的mesh数据按每一帧一次传递给OpenGL。这中间的过程常常被称作一次draw call,往往一次性传输大量mesh信息作为一次draw call 比多次传输少量mesh信息引起多次draw call更加效率。
图元
渲染图元,通常是应用阶段处理模型所输出的点、线、三角面等信息。
图元信息,通常是模型的顶点位置、法向量、纹理坐标等信息。
CCV
通常称这个单位立方体为规范立方体( Canonical view volume,CVV),用于进行裁剪操作。该立方体的对角顶点分别是(-1,-1,-1)和(1,1,1)。CVV 的近平面(梯形体较小的矩形面)的X、 Y 坐标对应屏幕像素坐标(左下角是 0、 0), Z 坐标则是代表画面像素深度。
多边形裁剪就是 CVV 中完成的。
- 用透视变换矩阵把顶点从视锥体中变换到裁剪空间的 CVV 中;
- 在 CVV 进行图元裁剪;
- 屏幕映射:将经过前述过程得到的坐标映射到屏幕坐标系上。
Z Buffer
Z buffer 又称为 depth buffer,即深度缓冲区,其中存放的是视点到每个像素所对应的空间点的距离衡量,称之为 Z 值或者深度值。可见物体的 Z 值范围位于【0,1】区间,默认情况下,最接近眼睛的顶点(近裁减面上)其 Z 值为 0.0,离眼睛最远的顶点(远裁减面上)其 Z值为 1.0。使用 z buffer 可以用来判断空间点的遮挡关系,著名的深度缓冲区算法( depth-buffer method,又称 Z 缓冲区算法)就是对投影平面上每个像素所对
应的 Z 值进行比较的。
Z 值并非真正的笛卡儿空间坐标系中的欧几里德距离( Euclidean distance),而是一种“顶点到视点距离”的相对度量。所谓相对度量,即这个值保留了与其他同类型值的相对大小关系。z buffer 中存放的 z 值不一定是线性变化的。在正投影中同一图元相邻像素的 Z 值是线性关系的,但在透视投影中却不是的。在透视投影中这种关系是非线性的, 而且非线性的程度随着空间点到视点的距离增加而越发明显。
Z值 精度很重要,因为 Z 值决定了物体之间的相互遮挡关系,如果没有足够的精度,则两个相距很近的物体将会出现随机遮挡的现象,这种现象通常称为“flimmering”或”Z-fighting”。
$$z\_buffer\_value=(1<<N)*\dfrac{a*z+b}{z}$$
$$a=\dfrac{f}{f-n}$$
$$b=\dfrac{f*n}{n-f}$$
Stencil Buffer
Stencil buffer,模板缓冲区,它是一个额外的 buffer,通常附加到 z buffer 中,例如: 15 位的 z buffer 加上 1 位的 stencil buffer(总共 2 个字节);或者 24 位的 z buffer 加上 8 位的 stencil buffer(总共 4 个字节)。每个像素对应一个 stencil buffer(其实就是对应一个 Z buffer)。 Z buffer 和 stencil buffer 通常在显存中共享同一片区域。 Stencil buffer 对大部分人而言应该比较陌生,这是一个
用来“做记号”的 buffer,例如:在一个像素的 stencil buffer 中存放 1,表示该像素对应的空间点处于阴影体( shadow volume)中
Frame Buffer
Frame buffer,称为帧缓冲器,用于存放显示输出的数据,这个 buffer 中的数据一般是像素颜色值。 Frame buffer 有时也被认为是 color buffer(颜色缓冲器)和 z buffer 的组合。frame buffer
通常都在显卡上,但是有时显卡会集成到主板上,这种情况下 frame buffer被放在内存区域( general main memory)。
齐次纹理坐标
齐次纹理坐标( homogeneous texture coordinates),纹理坐标一般是二维的,如果是体纹理,其纹理坐标也只是三维的。齐次纹理坐标的出现是为了和三维顶点的齐次坐标相对应,因为本质上,投影纹理坐标是通过三维顶点的齐次坐标计算得到的。
齐次纹理坐标通常表示为(s,t,r,q),以区别于物体位置齐次坐标(x, y, z,w)。一维纹理常用 s 坐标表示,二维纹理常用(s, t)坐标表示,目前忽略 r 坐标,q 坐标的作用与齐次坐标点中的 w 坐标非常类似。值一般为 1。
顶点着色器
顶点着色器处理的单位是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器。
顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。(无法得知连个顶点是否属于同一个三角网格)
顶点着色器需要完成的工作主要有:坐标变换(把顶点坐标从模型空间转换到齐次裁剪空间)和逐顶点照明。
透明度测试
采用一种霸道的机制,只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对于的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则否九按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。也就是说,透明度测试是不需要关闭深入写入的,它和其它不透明物体最大的不同就是它会透明度来舍弃一些片元。透明度测试的效果比较极端,要么完全透明,急看不到,要么完全不透明,就像不透明物体一样。
透明度混合
这种方法可以得到真正的半透明效果。它会使用当前片元的透明度昨晚混因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深入写入,因此我们需要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭深入写入,但没有关闭深度测试。这意味着当使用透明混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么就不会再进行混合操作。这一点决定了当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常的遮挡透明物体,即对透明度混合来说,深度缓冲是只读的。