02渲染管线

渲染管线

渲染管线(Rendering Pipeline)也称渲染流水线,是GPU内部处理图形信号相互独立的并行处理单元。一个流水线是一系列可以按照固定顺序进行或并行的阶段。每个阶段都从它的前一阶段接收输入,然后把输出传给随后的阶段。图形硬件以流水线的方式可以同时处理大量的顶点、几何图元和片元,大大加快了渲染速度。

《Real-Time Rendering, Third Edition》一书中将一个渲染流程分成3个概念阶段:应用阶段(Application Stage)、几何阶段(Geometry Stage)和光栅化阶段(Rasterizer Stage)。3个概念阶段之间的联系如下:

渲染管线中的三个概念阶段

应用阶段

应用阶段是由CPU发起的,渲染所需的几何信息即渲染图元(Rendering Primitives)输出给GPU。应用阶段大致可分为3个阶段:

  1. 准备渲染所需数据并将数据加载到显存
  2. 设置渲染状态
  3. 发出渲染命令

所有渲染所需数据都需要从硬盘(Hard Disk Drive,HDD)加载到系统内存(Random Access Memory,RAM)中。然后,网格和纹理等数据又将被加载到显存(Video Random Access Memory,VRAM)中。这是因为显卡对于显存的访问速度更快,并且大多数显卡没有直接访问RAM的权利。CPU可以在此过程中进行粗粒度剔除,以减少不可见物体的渲染。

此后,CPU需要设置渲染状态,即设置场景中网格使用的材质、纹理、着色器、光源属性等。

最后,CPU通过图形应用程序接口向GPU发出渲染命令。

Draw Call

Draw Call是一个由CPU发起、GPU接收的渲染命令。该命令指向一个需要被渲染的图元列表,而不包含任何材质信息。当给定了一个Draw Call时,GPU会根据该Draw Call指向的数据进行计算并渲染图像。

CPU和GPU通过命令缓冲区(Command Buffer)实现并行工作。命令缓冲区包含了一个命令队列,由CPU向其中添加命令,GPU从其中取出命令。添加和取出相互独立。

命令缓冲区中的命令有很多中,Draw Call是其中一种,其他命令还有改变渲染状态等。

在发起一个Draw Call前,CPU需要收集、准备数据。当CPU通过图形应用程序接口将Draw Call发送给GPU后,GPU驱动需要翻译命令,然后GPU才能根据命令进行渲染。这样的一个调度过程是昂贵的。由于GPU的渲染效率往往快于CPU提交命令的速度,如果频繁的触发Draw Call,CPU会花费大量时间收集数据、提交命令,造成CPU过载和程序运行瓶颈。

批处理(Batching)就是将许多小的Draw Call合并成一个大的Draw Call,从而减少每次提交Draw Call过程中相同的操作。由于需要在CPU内存中合并网格,而合并过程中是需要消耗时间的,因此批处理技术更适合于静态物体。

GPU管线

当GPU从CPU处接收到渲染命令后,以Draw Call指定的顶点数据作为输入,进行一系列的流水线操作,最终将图像渲染到屏幕上。

所谓顶点(Vertex),就是组成3D图形的顶点,由于设计3D模型是基于坐标空间内部设计的,所以顶点信息包含了3D模型在空间内的坐标,此外还包含法线方向、顶点颜色、纹理坐标等信息。

几何阶段用于处理所有和要绘制的几何相关的事情,进行逐顶点、逐多边形操作,主要任务是通过对输入的渲染图元进行多步处理后,将顶点坐标变换到屏幕空间中

光栅化阶段使用上个阶段传递的数据产生屏幕上的像素,并渲染出最终的图像。光栅化的主要任务是决定每个渲染图元中的哪些像素应该被绘制在屏幕上,以及计算这些像素的颜色

对于几何阶段和概念阶段,开发者无法拥有绝对的控制权,但顶点可编程能力和片元可编程能力使得GPU管线中某些阶段具有可配置性或可编程性。如下图所示,绿色表示该阶段是完全可编程控制的,黄色表示该阶段是可配置的,蓝色表示该阶段是GPU固定实现的;实线表示该阶段着色器必须由开发者编程实现,虚线表示该阶段着色器是可选的。

GPU管线的实现及可控性

顶点着色器(Vertex Shader)的输入来自于CPU,其处理单位是顶点。输入数据中的每个顶点都会调用一次顶点着色器。顶点着色器本身无法创建或销毁任何顶点,而且无法得到顶点与顶点之间的关系。顶点着色器需要完成的工作主要包括坐标变换和逐顶点光照,也可以输出后续阶段所需数据。

坐标转换就是将顶点坐标转从模型空间转换到齐次裁剪空间,接着通常再由硬件做透视触发后得到归一化的设备坐标(Normalized Device Coordinates,NDC)。

曲面细分着色器(Tessellation Shader)是一个可选的着色器,用于细分图元。

几何着色器(Geometry Shader)是一个可选的着色器,用于执行逐图元(Per-Primitive)的着色操作或者产生更多图元。

裁剪(Clipping)的目的是将不在摄像机视野内的顶点裁剪,并剔除某些三角图元的面片。此阶段是可配置的,如可以使用自定义裁剪平面配置裁剪区域或通过指令控制裁剪三角图元的正面还是背面。

屏幕映射(Screen Mapping)是将每个图元的x和y坐标转换到屏幕坐标系(Screen Coordinates)下。屏幕坐标系和z坐标构成窗口坐标系(Window Coordinates)。屏幕映射不会对输入的z坐标做任何处理。

注意:OpenGL把屏幕左下角当作屏幕坐标系的原点,DirectX将屏幕的左上角当作屏幕坐标系的原点。

三角形设置(Triangle Setup)是计算三角网格表示数据的过程。上一阶段输出的是三角网格顶点,为了得到整个三角网格对像素的覆盖情况,需要计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,需要得到三角形边界的表示方式

三角形遍历(Triangle Traversal)是找到哪些像素被三角网格覆盖的过程,也称作扫描变换(Scan Conversion)。三角形遍历会检查每个像素是否被一个三角网格覆盖。如果被覆盖的话,就会生成一个片元(Fragment)。这一阶段的输出是一个片元序列。

片元并不是真正意义上的像素,而是包含了许多状态的集合。 这些状态用于计算每个像素的最终颜色,包括(但不限于)屏幕坐标、深度信息、顶点信息等。

片元着色器(Fragment Shader,在DirectX中被称作像素着色器Pixel Shader)的输入是上一阶段对顶点信息插值得到的结果,而输出是一个或多个颜色值。这一过程可以完成很多重要的渲染技术,如纹理采样。

逐片元操作(Per-Fragment Operations,在DirectX中被称作输出合并阶段Output-Merger)主要包括决定每个片元的可见性(如模板测试、深度测试等)、将通过测试的片元与存储在颜色缓冲区的颜色进行合并或混合

模板测试(Stencil Test)将使用到模板缓冲区(Stencil Buffer)。如果开启了模板测试,GPU首先读取(使用读取掩码)模板缓冲区中该片元的模板值,然后将该值与读取(使用读取掩码)到的参考值(Reference Value)进行比较,这个比较函数是可以由开发者指定的。如果这个片元没有通过模板测试,该片元会被舍弃。不管一个片元有没有通过模板测试,都可以根据模板测试和深度测试的结果修改模板缓冲区,这个修改操作也是由开发者指定的。模板测试通常用于限制渲染区域或其他高级用法(如渲染阴影、轮廓渲染等)。

如果片元通过模板测试,则会进行深度测试(Depth Test)。如果开启了深度测试,GPU会把该片元的深度值和已经存在于深度缓冲区的深度值进行比较,这个比较函数也是可以由开发者指定的。通过深度比较函数是小于等于的关系,即如果这个片元的深度值大于等于当前深度缓冲区的值,则舍弃它,因为通常总是渲染离摄像机最近的物体,而被其他物体遮挡的就不需要渲染。如果这个片元没有通过测试,该片元就会被舍弃。与模板测试不同的是,如果片元没有通过深度测试,则它没有权利修改深度缓冲区的值;如果通过测试,开发者可以通过开启/关闭深度写入指定是否要用通过的片元覆盖原有的值。

模板测试和深度测试的简化流程图

如果片元通过深度测试,则会进行合并。对于不透明物体,可以关闭混合以使当前渲染物体的颜色覆盖颜色缓冲区中的值;对于半透明物体,需要通过混合操作实现半透明效果。

混合操作是可以高度配置的。开发者可以选择开启/关闭混合功能。如果没有开启混合,则直接使用当前颜色覆盖颜色缓冲区中的值。如果开启了混合,GPU会将源颜色(当前片元的颜色)和目标颜色(颜色缓冲区存在的颜色)通过混合函数进行混合。

混合操作的简化流程图

上述给出的测试顺序并不是唯一的,而且虽然从逻辑上将上述测试是在片元着色器之后进行的,但对于大多数GPU来说,为了降低片元着色器的计算量,通常会在片元着色器之前进行测试以提前知道需要被舍弃的片元,这些片元就不需要再使用片元着色器计算颜色值。
Unity的渲染管线中,深度测试是在片元着色器之前的,这种将深度测试提前执行的技术叫Early-Z技术。
但是,如果将这些测试提前,其检验结果可能会与片元着色器中的操作冲突。例如,如果在片元着色器中进行了透明度测试而该片元没有通过,则会在着色器中手动调用API舍弃该片元。这就导致GPU无法提前执行各种测试。因此,现代GPU会判断片元着色器中的操作是否和提前测试发生冲突,如果有冲突,就会禁用提前测试。但是,这样也会造成性能上的下降,因为有更多片元需要被处理,即透明度测试会造成性能下降的原因。

当图元经常计算和测试后,就会显示在屏幕上。屏幕显示的是颜色缓冲区中的颜色值。 但是,为了避免看见正在进行光栅化的图元,GPU会使用双重缓冲(Double Buffering),即对场景的渲染是发生在后置缓冲区(Back Buffer)中的。一旦场景被渲染到了后置缓冲区,GPU会交换后置缓冲区和前置缓冲区(Front Buffer)中的内容,由此包装图像总是连续的。

图形应用程序接口

渲染时CPU与GPU直接需要实现通信。如果开发者直接访问GPU是一件非常麻烦的事,不仅需要和寄存器、显存进行交互,还需要考虑到不同型号的GPU。

图形应用程序接口(Graphic Application Program Interface)是对GPU硬件的抽象,架起了上层应用程序和底层GPU沟通的桥梁。一个应用程序像图形API发送渲染命令,然后图形API向GPU驱动发送渲染命令。GPU驱动知道如何真正的与GPU通信,也正是GPU驱动将图形API发送的渲染命令翻译成GPU指令,同时也负责把纹理等数据转换成GPU支持的格式。

图形API

目前,两大图形API即OpenGL和DirectX。

OpenGL(Open Graphics Library)是一个由Khronos定义的跨编程语言、跨平台的专业图形API,是一个功能强大、调用方便的底层图形库。

OpenGL是行业领域中最广泛接纳的2D/3D图形API,自其诞生至今已催生了各种计算机平台及设备上的数千优秀应用程序。OpenGL独立于视窗操作系统或其他操作系统,亦是网络透明的。OpenGL是与硬件无关的软件接口,可以在不同平台如Windows、Unix、Linux、MaxOS之间进行移植。因此,支持OpenGL的软件具有很好的移植性,可以获得非常广泛的应用。

OpenGL的发展一直处于一种较为迟缓的态势,每次版本的提高新增的技术很少,大多只是对其中部分做出修改和完善。1992年7月,SGI公司发布了OpenGL的1.0版本,随后又与微软公司共同开发了Windows NT版本的OpenGL,从而使一些原来必须在高档图形工作站上运行的大型3D图形处理软件也可以在微机上运用。1995年OpenGL的1.1版本面市。该版本比1.0的性能有许多提高,并加入了一些新的功能。其中包括改进打印机支持,在增强元文件中包含OpenGL的调用,顶点数组的新特性,提高顶点位置、法线、颜色、色彩指数、纹理坐标、多边形边缘标识的传输速度,引入了新的纹理特性等等。OpenGL 1.5又新增了“OpenGL Shading Language”,该语言是“OpenGL 2.0”的底核,用于着色对象、顶点着色以及片元着色技术的扩展功能。

DirectX(Direct eXtension)是由微软公司创建的多媒体API,由C++编程语言实现,遵循COM,被广泛使用于Microsoft Windows、Microsoft XBOX、Microsoft XBOX 360和Microsoft XBOX ONE电子游戏开发,并且只能支持这些平台。最新版本为Directx 12,创建在最新的Windows 10。DirectX是这样一组技术:旨在使基于Windows的计算机成为运行和显示具有丰富多媒体元素(例如全色图形、视频、3D动画和丰富音频)的应用程序的理想平台。DirectX包括安全和性能更新程序,以及许多涵盖所有技术的新功能。应用程序可以通过使用DirectX API来访问这些新功能。

DirectX加强3D图形和声音效果,并提供设计人员一个共同的硬件驱动标准,让游戏开发者不必为每一品牌的硬件来写不同的驱动程序,也降低了用户安装及设置硬件的复杂度。从字面意义上说,Direct就是直接的意思,而后边的X则代表了很多的意思,从这一点上可以看出DirectX的出现就是为了为众多软件提供直接服务的。

着色器编程语言

着色器编程语言的发展方向是设计出在便捷性方面可以和C++/JAVA相比的高级语言,“赋予程序员灵活而方便的编程方式”,并“尽可能的控制渲染过程”,同时“利用图形硬件的并行性,提高算法的效率”。

在可编程管线出现之前,通常使用汇编语言编写着色器代码。

此后,为了更方便的编写着色器代码,出现了更高级(High-Level)的着色语言(Shading Language),常见的有基于OpenGL的GLSL(OpenGL Shading Language)、基于DirectX的HLSL(High Level Shading Language)以及NVIDIA的CG(C for Graphic)。这些语言会被编译成与机器无关的汇编语言,即中间语言(Intermediate Language,IL)。这些中间语言再交给显卡驱动来翻译成真正的机器语言。

GLSL的优点在于跨平台性,可以在Windows、Linux、Mac甚至移动平台等多种平台上工作。这种跨平台性源于OpenGL没有提供着色器编译器,而是由显卡驱动完成着色器的编译工作,即只要显卡驱动支持对GLSH的编译便可运行,即GLSL是依赖硬件而非操作系统层级的。这种做法的好处在于,由于供应商完全了解自己的硬件构造,他们知道怎样做可以发挥最大作用。但也意味着GLSL的编译结果将取决于硬件供应商,这可能造成编译结果不一致的情况,最终造成渲染效果的差异。

HLSL是由微软控制着色器的编译,但也仅支持微软自己的产品,如Windows、Xbox等,因为在其他平台上没有可以编译HLSL的编译器。

CG是NVIDIA与微软合作开发的跨平台高级着色语言。CG极力保留C语言的大部分语义,并让开发者从硬件细节中解脱出来。CG同时也有一个高级语言的其他好处,如代码的易重用性,可读性得到提高,编译器代码优化。CG是一个可以被OpenGL和DirectX广泛支持的图形处理器编程语言。CG和OpenGL、DirectX并不是同一层次的语言,而是OpenGL和DirectX的上层,即,CG程序是运行在OpenGL和DirectX标准顶点和像素着色的基础上的。由于与微软的合作,导致CG与HLSL非常相像,CG可以无缝移植成HLSL,但可能无法完全发挥出OpenGL的最新特性。

Unity渲染管线

Unity中的渲染管线渲染场景主要分为三个阶段:

  1. 剔除(Culling):剔除摄像机不可见对象(视锥体剔除Frustum Culling)和被遮挡对象(遮挡剔除Occlusion Culling)。
  2. 渲染(Rendering):将可见对象经过一系列计算(如光照计算)后绘制到像素缓冲区中。
  3. 后期处理(Post-Processing):对像素缓冲区上的颜色执行后期处理操作(如景深),生成最终的输出帧。

Unity提供四种渲染管线,分别为内置渲染管线(Built-in Render Pipeline)、通用渲染管线(Universal Render Pipeline,URP)、高清渲染管线(High Definition Render Pipeline,HDRP)、可编程渲染管线(Scriptable Render Pipeline,SRP)。不同的管线具有不同的特性和限制。

内置渲染管线提供了两种渲染路径可供选择,分别是前向渲染路径(Forward Rendering Paths)和延迟渲染路径(Forward Rendering Paths)。

  • 在使用(多通道)前向渲染路径时,场景中的所有对象都是按顺序渲染的。每个对象根据受到的光源数量可能在多个通道中渲染,因此当光源较多时,渲染成本会急剧上升。这种类型的渲染通常提供了各种各样的着色器,并且可以轻松地处理透明度。
  • 在使用延迟渲染路径时,所有不透明几何体先渲染到缓冲区。然后在延迟通道中,每个像素按顺序着色。渲染顺序主要取决于像素受到的光源数量。对于透明物体以及某些使用了复杂着色器的对象,仍然需要额外的前向渲染通道。这种类型的渲染通常用于处理包含许多动态光源的场景。

HDRP是一种混合了前向和延迟渲染的瓦片/簇渲染管线。HDRP提供了先进的渲染和着色功能,是专为要求逼真视觉的PC和高端游戏主机项目设计的。

瓦片是帧中的一个小型二维方形像素几何,而簇则是摄像机视锥体中的一个三维几何体。不管是瓦片还是簇,它们的渲染技术都依赖于一个光源列表,可以在一个单独的通道中根据这个列表进行光照计算。
不透明对象多使用瓦片系统进行着色,而透明对象的着色则多使用簇系统。
与内置渲染管线相比,HDRP的光照处理更快,带宽消耗更少。

URP是一种快速的单通道前向渲染管线,被设计用于不知此计算着色技术的低端设备。URP可以为中端设备提供更高质量的图形性能,有时性能消耗甚至低于内置渲染管线。URP根据每个对象来剔除光线,并允许在单个通道中计算光照,这会降低Draw Call。此外,URP也支持2D渲染和延迟渲染。

可以在Edit > Project Settings下的图形设置Graphics和质量Quality设置中指定要使用的渲染管线。

如果当前质量等级指定了要使用的渲染管线,则使用该渲染管线,否则使用图形设置中指定的默认渲染管线。如果两个设置都没有指定具体渲染管线,则使用内置渲染管线。

在C#脚本中也可以获取或设置渲染管线:

  • 使用GraphicsSetting.currentRenderPipeline获取当前活动渲染管线的资产。
  • 使用GraphicsSetting.defaultRenderPipelineQualitySetting.renderPipeline判断当前活动渲染管线是否是默认渲染管线或设置当前活动渲染管线。
  • 使用RenderPipelineManager.currentRenderPipeline获取当前活动渲染管线的实例。
  • 通过RenderPipelineManager.activeRenderPipelineTypeChanged来注册活动渲染管线改变事件。

参考

  • 蛮牛教育
  • 《Unity Shader入门精要》冯乐乐
  • Unity官方手册
  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值