图形流水线
内存里面专门开了一块空间用来存储即将显示到显示器上的【像素】们。用你家显示器分辨率乘积的结果就可以算出你家的显示器可以显示多少个像素。每个像素用RGB24位存储,但为了32位对其,又添加了一个A分量,最终是RGBA32为存储。
Vertex
3D模型的多边形是由一个个顶点,也叫Vertex,连起来的。Vertex存储在VertexBuffer中。
一个Vertex存储了许多信息,包括位置(模型空间下的位置),法线(该Vertex在模型空间下的法线),UV坐标,颜色等
VertexBuffer有多重存储格式(单缓冲布局,多缓冲布局等),Vertex Shader并不需要知道顶点存储的格式。这就需要调用者提供一个顶点格式的描述,然后有专门的硬件根据这个描述,把Vertex Buffer组装成一个Vertex,送给Vertex Shader
Primitive Assembler(二十一世纪初显卡主流模式)
在开始光栅化之前,把Vertex们组装成一个又一个三角形,然后把屏幕外的三角形裁减掉,计算三条边的方程等操作是由专门的硬件完成的,这个硬件叫做Primitive Assembler。
疑问:为什么要计算三条边的方程?
完成操作之后,送给光栅化器
Primitive Assembler的进化
传统流水线无法处理primitive这个对象,人们对其进行了修改。把传统的Primitive Assembler分割成了下面三种流水线
- Vertex Shader → Early Primitive Assembler → Primitive Assembler
- Vertex Shader → Early Primitive Assembler→Geometry Shader → Primitive Assembler
- Vertex Shader → Early Primitive Assembler→Hull Shader → Tessellator→Domain Shader → Primitive Assembler
Geometry Shader
输入一个primitive
【进行处理】
输出一个或多个primitive
但是它的效率奇慢无比,于是就有了下面的方式
Hull Shader → Tessellator→Domain Shader
可编程的Hull Shader:指定每个图元要如何被细分,比如内部分成多少个三角形,每条边分成多少段等
固定的Tesselator:用固定的算法来执行细分
可编程的Domain Shader:根据细分的参数,负责计算细分之后新生成的顶点信息
GPU的其他用途
- 使GPU强大的算力服务于并行计算
这个偏向于计算而非渲染的方向被称为GPGPU(General Purpose GPU),用GPU做通用计算。这个需求进一步催生出硬件支持的GPGPU,可以多入多出,可以任意读取,无需经过固定流水线单元。这种Shader叫做Compute Shader,它的输入输出都是内存
- 光线追踪——一个新的独立流水线
包括Ray Generator,加速网格结构,Intersection Shader,Any Hit Shader,Closest Shader,Miss Shader,Callable Shader等
- 还有神经网络专用计算流水线、视频解码专用流水线等
GPU硬件基本原理
Unified Shader Core
Vertex Shader和Fragment Shader在流水线里面是不同的阶段,可是在硬件方面,他们却可以用同一种硬件单元来执行。
为什么可以用同一种硬件单元来执行呢?
因为随着需求不断提高,Vertex Shader和Fragment Shader所执行的操作越来越相近,直到几乎完全相同,都要进行32位浮点运算或从内存采样。
这同一种硬件单元就叫Unified Shader Core
,由一个调度器Scheduler来控制哪个Core执行Vertex或Fragment的操作
GPU和CPU的区别
- 核的定义不同
- CPU是SISD,GPU是SIMD
- GPU在同一时刻有一组线程在执行同样的指令,这组线程叫warp/wave
- CPU的核是有多少条路,GPU的核是有多少条车道
- 延迟与吞吐不同
- CPU低延迟低吞吐,GPU高延迟高吞吐
- 分支指令执行不同
SP和SM
GPU上的核被称为Streaming Processor简称SP,Unified Shader Core∈SP。
一组SP,配上控制器和片上内存等硬件,称为一个Streaming Multi-Processor,简称SM。
显卡上所谓的切一刀就是指,低价格的显卡是在高价格显卡的基础上减少了一部分SM而成的,而SM的组成结构不变
图形API
远古时期的图形程序员需要对不同的OS写一遍不同的代码,效率奇低。
为了改善这一现状,人们利用了抽象的思想提出了分层。程序负责做什么,图形API负责怎么做。图形API让软件和硬件分离开来。
之后又有DDI的出现,负责OS和显卡驱动的对接
API举例:
- OpenGL和OpenGL ES 跨平台且跨厂商,向下兼容 有OpenGL API 和 Vulkan API
- Cuda 跨平台但不跨厂商
- Cuda 是厂商英伟达研发的,能跨厂商?
- 更多被用来计算
- Direct3DX API(x=9,10,11) 仅在Windows上,而且版本之间不兼容,不跨平台但跨厂商
接口与抽象类的区别
相同点
(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
GPU执行的是驱动
发来的操作而非API
,驱动提供API程序调用API,所谓的支持哪个API
实际指的是GPU厂商支持了哪些驱动
。出过OpenGL说支持32位浮点运算但GPU厂商声称不支持的事情。
平台(操作系统)、驱动(API,UMD)、GPU三方会谈。往往会有GPU在不同的平台上支持不同的驱动
实际上,程序调用图形AP到GPU实现走完整个栈,并不是把结果直接写到帧缓冲上,而是写到【一张纹理】上。操作系统中【Compositor】负责把每个程序产生的【纹理】进行操作,再调用图形API走完整个栈才写进帧缓冲当中。我们在Windows上看到的毛玻璃窗口,甚至是每个程序的窗口都是Compositor处理后的结果。由于Compositor对程序而言是完全透明的,所以在图形API中很少提到它的存在。
不可编程部分
光栅化
光栅化有不可编程(硬件),可编程(软光栅)两个类别。
不可编程有立即光栅化和Tile-Based Rendering(TBR)两大类,分别适用于PC和手机。也有两者的结合Tiled Caching。从Vertex计算到pixel结果 却发现现在得到像素值 比在该像素格格已有的像素值更近或更远 而致使渲染流水线白忙活的问题,导致了Early-Z技术的出现。在满足一定条件下,立即光栅化会在光栅化后,进入Fragment Shader之前做一次Early-Z测试;TBR会进入TBDR模式。至于这个条件是什么,我不到啊。
为什么会有可编程的出现?因为渲染小三角形以及无法使用硬件等问题。
为什么立即光栅化所采用的扫描算法,是根据输入的三角形直接在边的轮廓附近确认像素是否在三角形内而且通过计算ddx、ddy来进行插值,而不是通过每个像素中心点与三角形每个点的连线进行叉乘来却是像素是否在三角形内而且通过重心坐标进行插值
Output Merger
光线追踪
光线追踪 vs 光栅化
简单地说,光线追踪是看摄像机从某个像素发出的光线打到了哪个物体,光栅化是看某个物体覆盖到了哪些像素。由此可推出光线追踪在计算时需要知道整个场景的数据,而光栅化是一个一个物体送去渲染一个一个三角形光栅化,GPU并没有场景的概念。
光线追踪大体步骤
- 生成光线
- 光线传播 Ray Marching
- 光线求交 用到了加速结构
- 光线命中与否 如果命中可能会产生新的光线,即间接光,直接光照+间接光照=全局光照 GI
- 计算颜色
加速结构
物体加速结构 场景加速结构
许多物体加速结构组成了场景加速结构
由于不可能所有的物体都不动,要处理这些会动的物体,加速结构必须有更新功能
光线追踪流水线
光线追踪的未来
现阶段的光线追踪,大部分是光栅化做直接光照,光线追踪做间接光照+阴影。少量的光线,使用周围和上一帧的信息,来得到接近大量光线的结果。
未来的光线追踪将会是把加速结构的构建与更新全部由硬件执行。