看过那么多3d动画、玩过那么多3d游戏,大家是否好奇过这些模型是怎么做出来的?这个问题说起来要几天几夜了,我们今天先了解一下3d可视化入门基础——渲染管线。
01. 什么是渲染管线(render pipeline)
作为hmi开发人员,随着对技术的深入研究,难免会接触到渲染管线的概念。简单来说,渲染管线描述了一套将3d数据转换为2d图像的实时流程。
图1. 渲染管线流程 [1]
上面这张图(图1),对渲染管线有一定了解的同学,一定再熟悉不过了,它从功能上介绍了渲染管线在不同阶段所执行的任务:
▷ application阶段在cpu端执行,通常用于准备绘制所需的几何数据、uniform数据等,并发送给gpu端
▷ geometry processing阶段又可以拆分为顶点着色、细分着色(可选)、几何着色(可选)、stream out(可选,用于将geometry processing阶段处理的结果写回到buffer中)、裁剪(将超过ndc的坐标裁剪掉并生成新的顶点)以及屏幕映射(screen mapping,将ndc坐标映射到屏幕坐标)
▷rasterization(光栅化)阶段将上一阶段处理的图元(点,线,三角形)转变为一个个离散的像素块
▷ pixel processing阶段将光栅化生成的像素进行着色(pixel shading),之后将计算好的像素根据一定规则写入到framebuffer或者开发者指定的buffer中
正如程序员需要了解一定cpu及计算机体系结构的概念一样,图形程序员/hmi开发者,除了从功能的角度了解渲染管线在各个阶段的任务之外,也可以尝试从gpu的角度,了解其在渲染管线的流程中都做了哪些工作。
02. 关于现代gpu架构
在了解gpu都做了哪些工作之前,让我们先对现代gpu的整体架构有个初步的认识。
tesla架构是nvidia第一个实现了统一着色模型(unified shader model)的gpu架构,随着架构的更新,这一代的gpu架构在渲染管线上替换了传统的固定管线流程,并在驱动上支持dx10/opengl3.3。
图2是基于tesla架构的第一款显卡(geforce 8800 gtx)的架构图:
图2. geforce 8800 gtx 架构 [2]
geforce 8800 gtx包含总计128个流式处理器(streaming-processor / sp),被分别组织到16个不同的流式多处理器(streaming-multiprocessors / sms)中,每两个流式多处理器被组织到一个texture / processor cluster(tpc)中。
下方的raster operation processors(rops),负责将tpcs产生的结果(颜色 / 深度)写入到framebuffer中。每个tpc除了包含2组流式多处理,还包含了其他功能单元如geometry controller,texture(texture unit texture l1 cache)等(图3)。
图3. texture/processor cluster (tpc)
· geometry controller主要用于将顶点处理任务(顶点着色程序或可选的几何着色程序)分配到具体的流式多处理中。
· texture unit负责对纹理进行采样及滤波处理,虽然采样或者滤波本身属于耗时操作,但是可以通过线程切换尽可能隐藏该延迟(当一组像素执行采样指令时,线程切换上下文到另外一组像素,并执行其shader指令)。
· 流式多处理器作为统一的图形/计算中心,主要用于执行vertex / geometry / fragment shader或者并行计算程序(cuda程序)。每个流式多处理器包含8个sp core(当然你也可以叫它cuda core),主要用于进行单精度浮点数/整数的乘-加计算;同时还含有2个特殊函数单元(special function unit),主要用于完成像sqrt,sin,cos之类的特殊函数计算以及顶点属性插值。
03. 渲染管线如何在现代gpu运作?
在初步了解了现代gpu的架构之后,我们回过头来看渲染管线是如何在该架构上运作的。区别于早期的固定管线,新的渲染管线增强了其可编程能力,将一部分之前仅可通过图形api配置实现的功能或者通过配置也无法实现的功能,以可编程的形式开放给了图形程序员,也就是shader。
其中,可编程管线中最重要的两个shader分别是vertex shader和fragment shader。下面给出一个例子,描述了片元着色程序是如何在gpu上执行的[3]。通过这个例子,相信大家能对可编程渲染管线有一个更深入的理解。
图4. fragment shader及编译后的代码
图4的左边是fragment shader的一部分代码,右边则是其被编译后的代码(近似于汇编的形式)。这些指令一条一条的在gpu的sp或sfu上执行(区别于cpu的流水线执行)。由于多个像素通常是共享同一批shader指令的,所以shader代码最终编译成类似于图5的样子。
图5左边结构就是第二部分介绍的流式多处理器部分。像素着色同时在8个alu(sp core)上同步执行。而ctx部分,则用于在线程切换时临时存储上当前执行线程的上下文,当某一组像素指令在执行时被阻塞了(例如贴图采样等耗时操作),线程调度器会保存当前执行的上下文,并迅速切换到另一组像素及指令进行执行,尽可能隐藏耗时指令带来的延迟。
图5. 多像素共享同一组指令
当然,除了像素着色外,顶点变换、图元组装或者通用计算也会按照上面的模式执行对应操作,并且不同的功能也是可以并行(imr架构下有效,tbdr架构稍有区别)执行的(图6所示)。
图6. 在gpu上并行执行多种任务
回顾图2和图6,让我们对整个渲染管线的执行做一次梳理。
① 模型数据以及渲染指令通过cpu端调用图形api,将其传输到gpu端。
② 之后进入几何处理阶段,vertex work distribution将顶点数据分配到不同的流式多处理器中进行执行vertex shader,执行完成的离散顶点再次被分配到流式多处理器中进行图元装配工作。
③ 图元装配结束后,将结果传输给裁剪、光栅化等功能单元产生离散片元,由pixel work distribution分配给不同的流式多处理器执行fragment shader。
④ 执行结束后的结果,通过interconnection network分配给不同的rop,通过rop写入对应的framebuffer中。
关注怿星公众号,获取更多资讯