Real-time Rendering 第三章 图形处理单元

历史上,图形加速始于面片上面颜色的值做插值并且显示他们。包括获取图片的数据并且应用到表面上面的能力。添加了硬件插值以及深度检测的功能提供了内置的可见性检测。他们经常被用到例如,硬件里面的处理器就拿来增强性能。渲染管线部分越多,硬件功能也就越多,一代代都在增加。图形硬件的优势在于速度。

在过去的20年,图形硬件经历了不可想象的变化,第一个消费GPU balabala,,一开始固定管线balabala。。后来可编程管线。。。

GPU专注于高并行化的任务,他们有定制的芯片用给z-buffer,用来快速获取贴图以及其他buffer,然后找到哪个像素覆盖于一个面片,这个过程在23章中有讲。更重要的是如何GPU上实现给可编程shader 的并行化。

Section3.3讨论了shaderfunction。现在你需要知道一个微处理器相当独立地处理一个shader,比如把一个顶点从世界坐标转换到屏幕坐标,或者每帧把成千上万的三角面送到屏幕上,每秒有上亿次的shdare调用。

一开始,延迟是处理器需要处理的问题。获取数据需要一定时间。数据离得越远,需要的时间就越长。Section23.3讨论了延迟更多的细节。在memory Chip上储存的数据需要比本地注册器上获取要花更长时间。Section18.4.1讨论了更多内存访问相关的知识。获取数据将会阻塞处理器,降低性能。

3.1 Data-Parallel Architectures 数据并行构架

不同的处理器用了不同的方式避免阻塞。CPU优化是为了解决大量的数据结构以及巨大的代码块。CPU有多个处理器,但是每个处理器都是线性处理,限制了SIMD的向量计算。为了最小化延迟,许多CPU芯片包含了本地缓存(包含了有可能会需要使用数据的内存)CPU也使用了一些聪明的办法,类似于分支预测,instuction recordering(指令记录),register renaming和cache prefetching。

Hennessy, John L., and David A. Patterson, Computer Architecture: A Quantitative Approach, Fifth Edition, Morgan Kaufmann, 2011. Cited on p. 12, 30, 783, 789, 867, 1007, 1040

GPU使用了不同的思路,大部分的GPU将大部分芯片区域用于的处理器(shader core),这些处理器用于处理想死的数据。因为顶点,像素之类的都是相似的,大部分需要并行计算。另一些重要的部分是这些设备都是尽可能互相独立的,因为他们不需要获取相邻的信息,也不需要共享可写区域。这个规则有时候为了实现一些特殊的功能也会被打破,但是这样的情况会导致延迟,因为一个处理器需要等待另一个工作结束。

GPU针对吞吐量进行设计,也就是最大的数据量处理速率。然而频繁获取有消耗,更少的芯片区域用于内存以及逻辑控制,每个shader core的延迟实际上比CPU单个处理器要更高。

想象一个两千像素的片元需要处理,一个像素着色器需要调用两千次。想象如果只有一个shader处理器,世界上最弱的GPU,开始执行shader给两千中第一个片元着色。处理器用了一点点计算在寄存器上。寄存器是本地并且很快。没有阻塞问题,shader处理器然后去调用一个贴图获取,应用程序需要知道要取那个颜色。贴图是一个完全分离的资源,不在本地内存,获取一次要花上成百上千次的时钟时间,在获取贴图的时候啥都做不了,这个时候就阻塞了,等待贴图的颜色返回。

为了获取更好的性能,每个本地寄存器有一个小的本地储存空间。现在允许将shader处理器切换到另一个片元渲染,而不是一直阻塞在获取贴图数据。通过不断切换片元渲染,虽然看起来单个处理器变慢了,但是整体变快了。

这个构架之中,延迟被GPU片元切换掩盖了。GPU将这种想法扩展到了数据上面。称之为SIMD(single instruction,muliple data)对固定数量的shader程序执行同一个命令。SIMD的有点是可以使用更少的资源用于处理数据与切换,相对于把独立的逻辑派送到每一个处理单元上。将处理两千个片元的例子放到当代GPU上,每一个处理像素的程序被称作一个thread。这种线程不同于cpu的线程。它由少量用于储存输入的内存以及任意用于运算的寄存器空间组成。这些使用同种shader的线程进行了分组,在NVIDIA中称作Wrap而在AMD中被称作wavefronts。一个wrap或者wavefront由一定数量的GPU Shader核心进行调度,一般数量是8到64个,使用SIMD处理,每个线程映射到一个SIMD lane。

我们现在有两千个线程需要处理,在NVIDIA的GPU上的wrap包含32个线程。想当与2000/32=62.5个wrap,63个wrap将会被申请,其中一个是半个为空的。一个wrap的执行有点像单个GPU处理器的例子。shader程序对所有32个处理器进行同步。当memory获取的时候,所有线程同时遇到,这个时候wrap会被阻塞,等待它们(不同的)结果。而wrap并不会被阻塞,而是切换到另外一个wrap。切换wrap的速度就和切换处理器的速度一样快,每个线程拥有自己的寄存器,每一个wrap则管理自己的执行流程。切换过程只是把一些处理器切换到另外的处理器,没有任何消耗。由此切换直到所有的wrap执行结束为止。

Real-time Rendering 第三章 图形处理单元_第1张图片
image

在我们的简单例子里面,获取贴图的延迟可以引发一个wrap的切出操作。在现实中的wrap能够在极短的时间里面进行切出,因为消耗太小了。其他的很多一些技术习惯于优化执行流程

Kubisch, Christoph, “Life of a Triangle—NVIDIA’s Logical Pipeline,” NVIDIA GameWorks blog, Mar. 16, 2015. Cited on p. 32

,但是wrap切换主要是在设备上避免了延迟。这样做的效用有多大被很多因素影响。举个例子,如果只有很少的线程,那么wrap数也就越小,减少延迟的效果也就越差。

shader程序的结构也是影响效率的重要部分。一个主要的原因是每个线程使用的寄存器数量。在我们的例子里面,我们假设两千个线程能同时被GPU执行。如果有更多的寄存器被用到的话,那么我们只能使用更少的线程,wrap也就更少,也会成为GPU的短板。一个wrap的短板可以造成我们无法更好隐藏延迟。正在使用的wrap叫做in flight,然后数量则被称作occupancy(使用率)。高使用率意味着更多的wrap可以被使用,那么idle的处理器可以更少。低占用率则意味着低效率。内存的获取率也影响了延迟隐藏的程度。Lauritzen之初了共享内存以及寄存器数量对使用率的影响。

Lauritzen, Andrew, “Future Directions for Compute-for-Graphics,” SIGGRAPH Open Problems in Real-Time Rendering course, Aug. 2017. Cited on p. 32, 812, 908

Wronski讨论了使用率可以由shader使用的操作类型决定。

Wronski, Bartlomiej, “Assassin’s Creed: Black Flag—Road to Next-Gen Graphics,” Game Developers Conference, Mar. 2014. Cited on p. 32, 218, 478, 571, 572, 801

Wronski, Bartlomiej, “GCN—Two Ways of Latency Hiding and Wave Occupancy,” Bart Wronski blog, Mar. 27, 2014. Cited on p. 32, 801, 1005

另一个会产生影响的应诉是动态分支,由“if”或者循环语句引起。如果所有的线程走的是同一个分支,那么久不需要关心其他分支。但是如果在一些线程中,甚至只要有一个线程,形成分叉,那么就得同时执行所有分支,然后丢弃每个线程不需要的结果。

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

Kubisch, Christoph, “Life of a Triangle—NVIDIA’s Logical Pipeline,” NVIDIA GameWorks blog, Mar. 16, 2015. Cited on p. 32

这个问题被称作thread divergence(线程分歧?)少量线程需要执行一个循环迭代或者执行一个if路径,那么就会让他们单独处理。

所有的GPU在严格限制以及大量的计算量下实现了这些构架思想。理解这些系统行为能够帮助你实现更搞笑的程序,接下来耳朵部分讨论一下GPU如何实现渲染管线,可编程shader是如何操作的,以及每个GPU阶段的进化以及功能。

3.2 GPU管线概览

Real-time Rendering 第三章 图形处理单元_第2张图片
image

GPU实现了概念上的几何阶段、光栅化以及像素处理阶段。并且管线被分到不同的硬件管线阶段并且不同程度地可配置或者可编程。上图显示了哪些阶段可配置,哪些阶段可编程。物理阶段和第二章展示得稍微有些出入。

我们这里讨论的是GPU的逻辑模型,也就是暴露给用户的API。就像18章与23章所描述的,相对于逻辑实现,物理实现则取决于硬件供应商。一个固定功能的管线阶段有可能只是在临近的可编程管线添加指令。单独的程序有可能被分成若干元素,在不同子单元执行,或者执行在一个完全分离的过程。逻辑模型能帮助你知道什么会影响性能,但是不能混淆GPU实际的实现方式。

顶点shader是完全可编程的阶段,用于实现几何处理阶段。几何shader用于可编程地操作图元顶点。能够用于逐片元shading操作,或者创建一个新图元。曲面细分以及几何shader同样是可选的不过不是所有的GPU都会支持它们,特别是移动平台上面。

裁剪、图元组装以及遍历三角形阶段是由固定功能的硬件实现的。屏幕映射是被窗口以及视口设置决定的。像素着色阶段是完全可编程的,虽然合并阶段不是可编程的,但是高度可配置的,而且可以通过设置来实现大量的操作。在合并阶段中,可以操作颜色、zbuffer、混合、模板以及其他非输出buffer。像素着色器一起和合并阶段执行,就像第二章所描述的。

随着时间的流逝,GPU管线从完全硬编码朝着灵活以及可控发展。下一节描述了很多可编程阶段。

3.3 可编程Shader阶段

当代shader编程使用一个通用的shader设计。主要是顶点、像素、几何以及曲面细分shader共享了一个通用编程模型。在内部他们有一样的指令集(instruction set architecture ,ISA)。一个处理器实现模型被称作一个common shader core在DX中,拥有这样核心的GPU一般是有通用shader构架的。这种构架背后shader处理器扮演了非常多的角色,并且GPU可以合适地申请它们。举个例子,一组拥有微小三角面的mesh将会需要更多的顶点shader处理器,比起只有两个三角面的方片。一个GPU把顶点以及像素shader核心的池子分开意味着理想的方法是保持所有核心繁忙。使用通用的shader核心,GPU可以决定如何进行平衡。

描述整个shader编程模型显然超过了这本书的范围。有很多相关文档、数据以及网站已经这么搞了。Shader通过C-like的语言类似于DX的High-Level Shading Language(HLSL)以及OpenGL的OpenGL Shading Language(GLSL)DX的HLSL可以通过机器码编译,也被称作intermediate language(IL或者DXIL)提供了平台无关性。一个中间表示也可以允许shader编译并且离线保存。中间语言转换到特定GPU的ISA。控制台编程经常避免中间语言步骤,因为系统只有一种ISA。

基础的数据类型是32位单精度浮点标量和向量,通过向量,这有一部分shader代码不被硬件支持。现代GPU32位整数以及64位浮点也天然支持。浮点向量一般用作位置、发现、矩阵行、颜色或者贴图uv。整数大部分用于表示数量,indices以及bitmask。集合类型也是支持的,结构体、数组、矩阵。

一次draw call调用了API去绘制一组图元,并且让图形管线执行shader。每一个可编程shader阶段有两种类型的输入:uniform 输入,使用的是贯穿于一次draw call的常量(在两次drawcall之间可以修改)。还有varying输入,数据来自于三角面片顶点以及栅格化。举个例子,像素shader会提供光源的颜色作为uniform值,而三角面片的位置变化,逐像素则是varying的。一个贴图是一种特殊的uniform输入,基本上都是将颜色应用到表面,但是现在可以通过任意大数组做到。

底层的虚拟机提供了特殊的寄存器给不同的输入以及输出。可用的常量寄存器是远远大于可变输出输入寄存器的。这是因为可变输入输出需要为不同的顶点以及像素储存,所以天然就有限制。uniform输入则是储存一次,被所有的顶点以及像素使用。虚拟机也有多用途的临时寄存器(temporary registers)用于屏幕抓取。所有类型的寄存器可以通过整形进行数组下标引用,shader虚拟机的输入以及输出在下图可以看到。

Real-time Rendering 第三章 图形处理单元_第3张图片
image

图形计算的操作在当代GPU上是非常高效的。shadering语言暴露了大部分常用操作借口,例如加法与乘法使用*以及+。剩余暴露的通过内置函数(intrinsic functions)类似于atan(),sqrt(),log()还有很多其他,GPU都有进行优化。也包含更多复杂的操作,类似于向量标准化以及反射,叉乘,矩阵转置以及行列式计算。

控制流改变了代码执行的流向。高级语言控制流之类像是if或者是case还有各种类型的循环。shader支持两种类型的控制流。静态控制流基于输入的uniform值。这意味着一个drawcall中的流向是固定的。静态控制流最基本的好处是运行相同的shdare产生不同的效果,例如使用不同数量的灯光。这种方式不会出现线程分歧,因为所有的调用使用了同一个代码路径。动态控制流基于了varying输入值,意味着每一个片元能够执行不同的代码。这种方式比静态控制流更加灵活但是消耗更多性能,特别是代码流不规律地在shader之间变动。

可编程Shader以及API的进化

可编程shader的框架要追溯到1984年COok的shade tree

Cook, Robert L., “Shade Trees,” Computer Graphics (SIGGRAPH ’84 Proceedings), vol. 18, no. 3, pp. 223–231, July 1984. Cited on p. 37, 765

一种简单的shader以及相应的shade树在下图中显示:

Real-time Rendering 第三章 图形处理单元_第4张图片
image

RenderMan编程语言在1980年代后期被开发出来了。

Apodaca, Anthony A., and Larry Gritz, Advanced RenderMan: Creating CGI for Motion Pictures, Morgan Kaufmann, 1999. Cited on p. 37, 909

Upstill, S., The RenderMan Companion: A Programmer’s Guide to Realistic Computer Graphics, Addison-Wesley, 1990. Cited on p. 37

至今仍然在电影制作中被使用,也衍生出了分支,例如Open Shading Language(OSL)项目。

Gritz, Larry, ed., “Open Shading Language 1.9: Language Specification,” Sony Pictures Imageworks Inc., 2017. Cited on p. 37

消费者级别的图形硬件第一次在1996年的3dfx Interactive发布。

下图是时间线。

Real-time Rendering 第三章 图形处理单元_第5张图片
image

他们的Voodoo图形卡能够高质量渲染Quick并且导致它被快速普及。这种硬件使用了固定管线。在GPU支持可编程shader之前,有过很多可编程shader操作的尝试,通过多渲染pass。Quake III: Arena脚本语言第一次普及在这一领域成功。在早期章节提到过的GeForce256是第一个被称作GPU的硬件,但是并不是可编程的。它是可配置的。

在2001年早期,NVDIA的GeForce3是第一个支持可编程顶点shader的GPU

Lindholm, Erik, Mark Kilgard, and Henry Moreton, “A User-Programmable Vertex Engine,” in SIGGRAPH ’01 Proceedings of the 28th Annual Conference on Computer Graphics and Interactive Techniques, ACM, pp. 149–158, Aug. 2001. Cited on p. 15, 38

对DX8开放,对OpenGL可扩展。这些shader使用一种装配语言通过硬件在运行时转换成微码(macrocode)。像素着色器也在DX8中被包含,但是像素着色器还远远没有到达可编程的程度,编程的限制转换到贴图混合阶段,和硬件绑定。程序不只是限制了之类长度,还缺乏可用性。依赖的贴图读取以及浮点数据被Peercy et al认为是真正可编程性的关键,从他们对RenderMan的学习中得到。

Peercy, Mark S., Marc Olano, John Airey, and P. Jeffrey Ungar, “Interactive Multi-Pass Programmable Shading,” in SIGGRAPH ’00: Proceedings of the 27th Annual Conference on Computer Graphics and Interactive Techniques, ACM Press/Addison-Wesley Publishing Co., pp. 425–432, July 2000. Cited on p. 38

Shader在这一时期不允许控制流,所以条件必须通过同事计算选择或者差值来进行模拟。在2002年DX9包含了shader model 2.0,可以进行真正可编程顶点以及像素着色器。支持任意的依赖贴图读取并且16位浮点值的储存支持 了。shader资源的限制,类似于指令、贴图、寄存器也增加了,所以shader能够制作更加复杂的效果。控制流的支持也加上了。shader的长度以及复杂性的增加使得装配编程模型越来越笨重。幸运的是DX9包含了HLSL。这个编程语言由微软和NVIDIA一起合作开发。在同时,OpenGL ARB(Architecture Review Board)发布了GLSL,

Kessenich, John, Graham Sellers, and Dave Shreiner, OpenGL Programming Guide: The Official Guide to Learning OpenGL, Version 4.5 with SPIR-V, Ninth Edition, Addison-Wesley,

  1. Cited on p. 27, 39, 41, 55, 96, 173, 174

一门用于OpenGL的相似语言。这些语言深度被C语言影响,并且包含了RenderMan Shader语言的元素。

Shader Model 3.0在2004年被提出,添加了动态控制流,使得shader更加强大。并且添加了可选特性,更多地增加了资源限制,添加了可读的贴图数量。当一个新的游戏机在2005年末发布(微软xbox360)以及2006年末(索尼ps3)他们装配了Shader Model 3.0的GPU,任天堂wii是最后一个使用固定管线的游戏机。目前为止,固定管线已经没落。shader语言已经有非常多的工具去创造以及管理了。下图的工具,使用Cook的shader树概念。

Real-time Rendering 第三章 图形处理单元_第6张图片
image

下一个更大的一部在2006年末,Shader Model4.0发布,在DX10中包含了。

Blythe, David, “The Direct3D 10 System,” ACM Transactions on Graphics, vol. 25, no. 3, pp. 724–734, July 2006. Cited on p. 29, 39, 42, 47, 48, 50, 249

介绍了主要的特性,类似于几何着色器以及流输出。Shader Model 4.0包含了一个所有shader的通用编程模型。资源限制进一步增加,以及整数数据类型支持增加了。GLSL3.30在OpenGL3.3也有类似的模型。

在2009年DX11以及Shader Model5.0发布了,添加了曲面细分shaer以及compute shader,也称作DirectCompute。这个也专注于CPU多处理更加高效,在18.5节进行了讨论。OpenGL添加了曲面细分在4.0以及4.3添加了compute shader。DX和OpenGL各自得到了发展。两边都设置了特定版本需要硬件支持的等级。微软控制了DX的API,所以能够直接在非硬件供应商相关下进行工作,类似于AMD、NVIDIA,以及Intel,游戏开发者和电脑软件设计者,决定开发什么样的API。OpenGL则是由硬件厂商软件供应商有Khronos Group进行管理。因为数量太多,所以API经常在DX发布之后才出现。然而OpenGL支持扩展,特定供应商或更一般,允许最新的GPU功能在官方版之前就进行发布。

下一个标志性改变是AMD发布的Mantle语言,在2013.和游戏开发商DICE共同研发,Matle的想法是去除过多的驱动overhead以及提供给开发者更直接的控制。随着这样的改变,CPU的多线程能够更好地发挥作用。新的API注重于减少CPU驱动花费的时间,而更多支持CPU多线程(18章)这样的思路在2015年被微软的DX12所吸收。DX11.3发布了同样的硬件特性。API也能够发送图片给虚拟现实设备,更好地映射到当代GPU构架。low-overhead的驱动对于CPU驱动瓶颈的应用程序来说非常有用,更多的CPU处理器也能更好地服务于性能。

Kubisch, Christoph, “Transitioning from OpenGL to Vulkan,” NVIDIA GameWorks blog, Feb. 11, 2016. Cited on p. 40, 41, 796, 814

从更早的API上一直会比较难,原生支持也会有更低的性能。

Chajdas, Matth¨aus G., “D3D12 and Vulkan: Lessons Learned,” Game Developers Conference, Mar. 2016. Cited on p. 40, 806, 814

Hector, Tobias, “Vulkan: High Efficiency on Mobile,” Imagination Blog, Nov. 5, 2015. Cited on p. 40, 794, 814

Pranckeviˇcius, Aras, “Porting Unity to New APIs,” SIGGRAPH An Overview of Next Generation APIs course, Aug. 2015. Cited on p. 40, 806, 814

苹果在2014年发布了自己的低功耗API——Metal。Metal是首先在移动设备上可用,在一年之后可以在mac电脑上使用。超越性能,降低CPU功耗,对移动设备的优化。这个API拥有自己的shading语言,对图形以及GPU计算都有意义。

AMD将Mantle的工作贡献给了Khronos Group,2016发布了最新的API,Vulkan。就像OpenGL一样,Vulkan也多平台工作。Vulkan使用新的高级语言,称作SPIRV,在shader表现以及普通的GPU运算中度可以使用。预编译shader可以被人话的GPU使用。Vulcan也被用作无图形的GPU计算。和其他的低功耗图形驱动的一个区别是它在广泛的系统中使用,从工作站到手机。

手机上的规范是使用OpenGL ES,ES代表了嵌入式系统(Embedded System),代表移动设备中用的API。标准OpenGL的一些调用非常庞大缓慢,而且也很少用。2003年OpenGL ES1.0发布了,是OpenGL 1.3的阉割版,使用固定管线描述。同时DX安排了图形硬件去支持他们,为移动设备开发图形支持并不一样流行。举个例子,第一个iPad2010年发布,使用了OpenGL ES1.1。2007年基于OpenGL2.0但是没有固定功能组件,所以不是向后兼容的。OpenGL ES 3.0在2012年发布,提供了多目标渲染,贴图压缩,transform feed back,instancing以及更多贴图格式以及模式,shader语言也进行了改进。OpenGL ES 3.1添加了Compute shader,3.2添加了几何shader以及曲面细分shader,以及其他的特性。23章描述了移动设备更多的细节。

说完OpenGL ES之后是其分支,基于浏览器的WebGL,通过javascript进行调用。在2011年发布,第一个版本可以用于大部分移动设备,其相当于OpenGL ES2.0的功能。对于OpenGL,扩展可以获取更多GPU特性。WebGL2假设OpenGL ES3.0是受支持的。

WebGL有特别适合课堂体验的特性:

  • 跨平台在所有个人电脑上,以及所有手机设备

  • 硬件细节浏览器已经做完了,即使浏览器不支持特定GPU以及扩展,但是其他浏览器支持

  • 代码是解释性的,非编译,只要文本编辑器就可以开发

  • debug在大部分的浏览器中都有,并且任意网站的代码都可以被测试

  • 程序可以发布到github上,随意部署

高等级以及效果库,类似于three.js

Cabello, Ricardo, et al., Three.js source code, Release r89, Dec. 2017. Cited on p. 41, 50, 115, 189, 201, 407, 485, 552, 628

可以简单获取代码,了解阴影算法,后处理效果,基于物理的shdare以及延迟渲染。

3.5 顶点shader

顶点shader是第一个出现在功能管线的阶段。同时也是第一个直接由程序控制的阶段,在这个阶段开始数据操作就开始有意义了。在DX里面称作Input Assembler

Blythe, David, “The Direct3D 10 System,” ACM Transactions on Graphics, vol. 25, no. 3, pp. 724–734, July 2006. Cited on p. 29, 39, 42, 47, 48, 50, 249

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

Microsoft, “Direct3D 11 Graphics,” Windows Dev Center. Cited on p. 42, 233, 525

一系列的流数据可以形成顶点以及片元集合传送到管线的下一个阶段。举个例子一个object可以被表示成位置以及颜色的数组。这个输入集合将会通过在这些位置以及颜色数据数据创建物体的面片。第二个物体也可以用同样的位置以及不同的颜色数组创建出来数据表示在16.4.5节有详细的描述。input assembler也支持了instancing,允许一个object通过一些varying数据绘制多个实例,只需要在一个drawcall中便可以完成。instancing的使用方法在18.4.2节中描述了。

三角网格被若干顶点描述,每一个顶点都假设了一个特定的位置。除了位置以外还有很多其他的属性在顶点上面,例如颜色或者贴图坐标。表面法线在网格顶点上也被定义了,这个参数对于使用曲面着色更加有意义。然而,当渲染的时候三角面经常用于表示一个曲面的一部分,而顶点法线用于表示表面的方向,而不是这个面片本身。16.3.4节讨论了计算顶点法线的函数。下面图形显示了使用面片表示表面,一个光滑另一个棱角分明。

Real-time Rendering 第三章 图形处理单元_第7张图片
image

顶点shader是处理三角网格的第一个阶段。三角面片的结构无法被顶点shader获取。就像名字所描述的,它就只是处理输入进来的顶点而已。顶点shader提供了一种方式来修改、创建或者湖绿每一个顶点的值,比如颜色、法线、贴图坐标。一般顶点shader将顶点从模型空间转换到齐次裁剪空间(4.7节)在最小的顶点shader中必须输出它的位置。

顶点shader是和同意shader模型最接近的,每一个顶点在这个阶段被顶点shader程序传递,输出了一系列的值并且在三角面以及线之间进行插值。顶点shader既不能创建也不能销毁顶点,一个顶点产生的结果不能用于另外的顶点。因为每个顶点是被单独对待的,任意数量的shader处理器能够对顶点输入流进行处理。

Input assembly是在顶点程序执行之前经常会被执行。这是物理模型与逻辑模型不同的一个例子。在物理上,获取数据创建顶点也许会发生在顶点shader,然后驱动悄悄对每个shader进行合适地预演算,这些都是对开发者无感知的。

接下来的章节会讨论一系列的顶点效果,例如顶点混合以及动画关节,以及剪影渲染。其他一些顶点shader的应用有:

  • Object生成,通过创建一个Mesh然后使用顶点shader进行扭曲。

  • 动画角色的身体以及脸部使用蒙皮以及变形技术

  • 过程化变形,例如旗帜的飘动、衣服、或者水。[802,943]

  • 粒子生成,通过发送退化的Mesh到管线中以及放在需要的区域中。

  • 光学变形、热扭动、水波纹、翻页以及其他效果 ,通过整个framebuffer内容作为贴图,然后贴在全屏Mesh上面进行扭动。

  • 用于地形的高度通过从顶点贴图获取。

    Andersson, Johan, “Terrain Rendering in Frostbite Using Procedural Shader Splatting,” SIGGRAPH Advanced Real-Time Rendering in 3D Graphics and Games course, Aug. 2007. Cited on p. 43, 175, 218, 877, 878

    Mittring, Martin, “Finding Next Gen—CryEngine 2,” SIGGRAPH Advanced Real-Time Rendering in 3D Graphics and Games course, Aug. 2007. Cited on p. 43, 195, 239, 242, 255, 457, 476, 559, 856, 860, 861

其他一些顶点扭曲的应用在下图可见。

Real-time Rendering 第三章 图形处理单元_第8张图片
image

顶点shader的输出能够在不同的方式被使用。通常的方式是对不同实例的扭曲,然后生成并且栅格化,然后单独的像素片元被发送到片元着色器在接下来的阶段进行处理。在相同的GPU,数据也可以输入到曲面细分shader以及几何shader,或者储存在内存。这些可选阶段在接下来的小节中会被描述到。

3.6 曲面细分阶段

曲面细分阶段让我磕可以渲染曲面,GPU的任务将用于描述每一个表面,并且将其表示成一组三角面片。这个阶段是一个可选的GPU阶段并且在DX11上第一次被使用。也在OpenGL4.0以及OpenGL ES 3.2中支持。

使用曲面细分阶段有许多好处。曲面描述经常比直接使用三角面片更加简洁。处理能够节省内存以外,这个特性能够保持CPU与GPU的平衡而避免遇到在每一帧对一个任务或者物体进行动画产生的瓶颈。表面能够有效地通过视口判断使用合适数量的三角面片进行渲染。控制LOD(level of detail)的特性帮助应用程序改善性能。在较差的GPU上使用低质量的网格来保证帧率。通过平面表示的模型能够转换到使用更好的三角面片表示,然后扭曲变化成我们想要的形状。

Rideout, Philip, and Dirk Van Gelder, “An Introduction to Tessellation Shaders,” in Patrick Cozzi & Christophe Riccio, eds., OpenGL Insights, CRC Press, pp. 87–104, 2012. Cited on p. 44, 46

或者他们能够细分将复杂的shader更少进行执行。

Cantlay, Iain, and Andrei Tatarinov, “From Terrain to Godrays: Better Use of DX11,” Game Developers Conference, Mar. 2014. Cited on p. 44, 569

曲面细分的阶段经常包含三个元素。使用DX技术的话就分成了hull shader,tessellator以及domain shader。在OpenGL中,hull shader就是tessellation control shader以及domainshader就是tessellation evaluation shader,相对而言描述性更强,虽然更啰嗦一点。固定方程 tessellator被称作primitive generator,意识一样。

如何特化以及细分曲线以及表面在17章有详细说明。这里我们做一个简单介绍。一开始的时候输入到hull shader的是一个特殊的patch片元。有多个控制点来细分表面,贝塞尔patch或者其他类型的曲线元素。hull shader有两个方法。首先,它告诉tessellator多少三角片会被生成,以及使用什么样的配置。第二步,它处理每一个控制点。当然可选的,hull shader可以修改输入的patch描述,添加或者删除控制点,根据需要。hull shader输出了一组控制点,与细分控制数据一起送到domain shader。如下图所示。

Real-time Rendering 第三章 图形处理单元_第9张图片
image

细分器是一个固定阶段,只是用于细分shader。它包含了为domain shader添加若干新顶点用于处理的任务。hull shader发送了细分信息关于希望使用什么类型的细分表面:三角形,四边形或者等值线(isoline)。isoline是一组线条,有时候用于头发渲染。

Yuksel, Cem, and Sara Tariq, SIGGRAPH Advanced Techniques in Real-Time Hair Rendering and Simulation course, July 2010. Cited on p. 45, 642, 646, 649

另外一个hull shader发送的值有,比如细分因子(tessellation factor),在OpenGL中称作tessellation levels。有两种类型:向内(inner)或者向外(outer edge)。向内因子决定细分向内使用三角面片或者四边形。向外因子决定了边扩展方式(17.6节)。一个增加细分因子的例子在下图显示

Real-time Rendering 第三章 图形处理单元_第10张图片
image

通过分别处理我们可以将曲面的边适应于细分,无视内部是如何细分的。边匹配避免了crack或者其他看起来奇怪的情况。顶点被质心坐标分配22.8节,每个点与面的相对位置都可以按我们的需要调整。

hull shader总是输出一个patch,一组控制点的位置。然而如果hull shader 给细分器发了一个小于0或者更小的向外细分值,可以触发错误。否则细分器会产生一个面片并且发送给domain shader。曲面的控制点,用于每一个domain shader的调用,用于计算顶点的输出值。domain shader有一个数据流模式,就像一个vertex shader一样,每一个输入顶点进行处理后然后产生一个修改过的输出顶点。处理过的三角面传入下一个阶段。

这个系统听起来很复杂,因为性能的原因它被设计成这样,每一个shader可以非常简单。通过hull shader的patch可能修改一点点或者无修改。shader也可能使用patch的估算距离或者屏幕尺寸用于实时计算细分因子,例如地形渲染。

Fernandes, Ant´onio Ramires, and Bruno Oliveira, “GPU Tessellation: We Still Have a LOD of Terrain to Cover,” in Patrick Cozzi & Christophe Riccio, eds., OpenGL Insights, CRC Press, pp. 145–161, 2012. Cited on p. 46, 879

hull shader也许只是传递应用程序计算好并且提供的值。而细分器只是提供位置产生顶点,并且指定其想要的曲线。数据在外部处理好的过程是为了计算更有效率。

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

domain shader 拿到了质心坐标为每一个顶点产生了位置、法线以及贴图坐标,以及其他想要的顶点信息。下图则是一个例子。

Real-time Rendering 第三章 图形处理单元_第11张图片
image

几何 shader

Real-time Rendering 第三章 图形处理单元_第12张图片
image

几何shader可以将片元转换为其他片元,有时候细分阶段并无法做到。举个例子,一个三角面片可以转换成通过每个边产生面片。线可以被面向视口的四边形代替以渲染更粗的边。

Rideout, Philip, “Silhouette Extraction,” The Little Grasshopper blog, Oct. 24, 2010. Cited on p. 47, 668

几何shader则是在2006年DX10加入到了硬件加速图形管线。放置在曲面细分的后面,并且是可选的。而OpenGL3.2以及OpenGL ES 3.2也支持这一类型的 shader。

几何shader的输入是一个简单的object以及它关联的顶点。这个object包含了三角面片,以带状形式、线形式或者简单的点。扩展图元的形式能够定义以及处理在几何shader中。特别是三个在三角面片之外的顶点可以被传入,几何线毗邻的两个顶点也可以使用,就如上图所示。使用DX11以及shader model 5你可以传入更多的详细的patch,最多32个控制点。也就是说细分阶段比patch生成更高效。

Blythe, David, “The Direct3D 10 System,” ACM Transactions on Graphics, vol. 25, no. 3, pp. 724–734, July 2006. Cited on p. 29, 39, 42, 47, 48, 50, 249

几何shader处理片元输出0或者多个顶点,看做是点、几何线或者三角条带。需要注意的是几何shader并没有输出。这种方式,网格可以通过编辑顶点进行修改,添加新的图元或者移除其他的。

几何shader用于修改输入数据或者产生有限制数量的靠背。举个例子,一种用途是产生留个变换过的拷贝来同事渲染cube map的六个面。在10.4.3节中有被提到。它也可以用作高效创建CSM阴影贴图,用于高质量阴影生成。其他利用了几何shader的算法还有通过点数据创建可变尺寸的粒子,extruding fins along silhouettes for fur rendering(毛发渲染)以及找到阴影边缘的算法。下图有更多例子。

Real-time Rendering 第三章 图形处理单元_第13张图片
image

这些例子在本书剩余部分会有提到。

DX11添加了用于instancing的几何shader,几何shader可以在指定的图元上运行多次。

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

Zink, Jason, Matt Pettineo, and Jack Hoxley, Practical Rendering & Computation with Direct3D 11, CRC Press, 2011. Cited on p. 47, 54, 90, 518, 519, 520, 568, 795, 813, 814, 914

在OpenGL4.0可以指定一个执行次数。几何shader也可以输出到4个流中。每个流可以输出到剩余的渲染管线。所有这些流可以选择哪个最终输出到渲染目标。

几何shader包中了输出几何图元的输出结果和其输入顺序是一样的。这影响到效率,因为多个shader核是并行处理的,结果必须保存并且有序。这个原因以及其他的因素导致几何着色器用于复制或者创造更多数量的集合体在单独call中。

Blythe, David, “The Direct3D 10 System,” ACM Transactions on Graphics, vol. 25, no. 3, pp. 724–734, July 2006. Cited on p. 29, 39, 42, 47, 48, 50, 249

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

在一个draw call执行的时候只有三个阶段可以创建:光栅化、细分阶段、几何着色器。几何着色器是最后一个可以得知资源以及内存所需的步骤,因为是完全可编程的。通常可以发现几何着色器比较少使用,因为它对于GPU的能力并不匹配,有些移动设备通过软件支持,所以不太鼓励使用它。

ARM Limited, “ARMR MaliTMApplication Developer Best Practices, Version 1.0,” ARM documentation, Feb. 27, 2017. Cited on p. 48, 798, 1029

3.7 流输出 Stream Output

在GPU标准的管线里面,输出顶点shader然后栅格化输出数据到片元着色器。一般的用法会将几何结果通过管线,而中间结果是无法获取的。而流输出的概念是在Shader Model 4.0提出的。在顶点shader处理完顶点之后(当然可以选择执行细分以及几何shader)然后结果可以输出到流中,一个有序数组,也可以继续输出到光栅化阶段。光栅化实际上可以完全关闭,而不输出任何的图形。通过这种数据处理可以送回管线,因此允许反复处理。这种类型的操作对于水体流动以及其他粒子特效非常有用,例如13.8节描述的。可以用于蒙皮以及复用顶点。(4.4节)

流输出返回值只使用浮点类型,所以会产生可观的内存消耗。流输出应用于图元,不是直接作用于顶点。如果网格发送到管线,每个三角片产生自己的三个输出顶点。原网格中的顶点共享将会丢失。因为这个原因,更常用的方式只是将顶点作为点图元传输。在OpenGL里面stream output 阶段被称作transform feed back,因为大部分用途都是用于转换顶点然后返回用作更多的用途。图元保证了输出的顺序与输入相同。

3.8 片元Shader —— The Pixel Shader

在顶点、细分以及几何shader处理之后,几何图元被裁剪并且进行了光栅化,之前也都讲过。这个管线阶段是相对固定的处理阶段。不能不可编程但是一定程度上可以配置,每个面片决定了所覆盖的像素。像素化也许也只是简单将面片覆盖的每一个像素区域计算了一下(5.4.2节)。完全被三角片部分或全部覆盖的像素被称为片元。

三角顶点的值,包含了z值用于zbuffer用于对三角面片上的每一个像素进行插值。这些值被传递到像素shader,用于处理片元。在OpenGL中像素shader称为fragment shader,或许是更好的名字。我们使用pixel shader在全书中只是为了一致性。点与线片元在管线中也会对其覆盖的像素创建片元。

三角面的插值类型通过像素shader程序制定。一般我们使用透视修正的插值,所以两个像素的世界坐标距离与物体距离相同。一个例子是渲染衍伸到地平线的铁路。铁路越远像素就更紧密。其他的插值选项也有,例如屏幕空间插值,将透视投影的因素忽略。DX11给与了更多对插值的控制。

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

在编程角度,顶点shader的输出,在三角片与线之间插值,作为像素着色器的输入是非常高效的。因为GPU的发展,其他的输入也开放了出来。例如片元的屏幕位置也可以获取,在shader modle3.0中以及更高版本。以及三角面的那一边是可见的。这个信息对于在同一pass中为表面的内外渲染不同的材质非常重要。

Real-time Rendering 第三章 图形处理单元_第14张图片
image

输入以外,一般像素shader的输出是片元的颜色,它也可以产生透明度以及可选的z深度修正。在合并过程中,这些值用于修改像素中储存的值。光栅化阶段生成的z深度值也可以在像素shader中进行修改。模板buffer不是经常可修改的,但是会传到合并阶段。DX11.3允许shader修改这个值。像雾这样的计算以及alpha testing从合并阶段可以移到像素shader计算阶段在Shader Model 4.0中。

Blythe, David, “The Direct3D 10 System,” ACM Transactions on Graphics, vol. 25, no. 3, pp. 724–734, July 2006. Cited on p. 29, 39, 42, 47, 48, 50, 249

像素shader也有独一无二的能力来裁剪片段,不进行任何输出。一个例子就是在图片中显示的那样。裁切的平面曾经是固定管线的可配置元素,后来指定到了顶点shader中。片段裁切可用之后这个功能能够通过我们自己想要的方式在像素着色器中实现,例如决定裁切面使用And或者Or或者一起起作用。

最早的时候像素着色器只能在合并阶段其效果,最后产生渲染结果。指令数量以客观的速度增长。这样的增长催生了多渲染目标(multiple render target ,MRT)。不仅仅是讲像素着色器的结果输出到颜色以及zbuffer,更多的值可以被浮于到buffer上面,每一个作为一个渲染目标。每个渲染目标有着相同的x以及y尺寸。一些构架要求渲染目标有相同的渲染深度,甚至可能是数据格式。取决于GPU,渲染目标的数量是四个或八个。

即使有这样的限制,MRT的功能也是非常强大的,让更多算法更加高效。一个单渲染pass可以产生一个颜色到一个目标,object本身到另外一个,世界空间距离在第三个。这样的能力也催生了一种不同的渲染管线,延迟渲染(deferred shading),将可见性与渲染分成两个pass。第一个pass储存了物体的位置以及材质,在每个像素。接下来的pass可以高效地进行光照以及其他效果。这种类型的渲染函数了20.1节进行描述。

像素shader的限制是只能写入到渲染目标对应的位置,你既不能从相邻像素读取也不能写。这是因为一个像素shader执行的时候不能直接输出到相邻像素,也不能获取其他最近的修改。它只能影响自己的像素。不过这样的限制也不是绝对的。一个输出图片,在一个pass中创建出来之后,你可以在下一个pass的像素shader中任意获取。相邻像素可以用于图片处理技术,在12.1章中有讨论。

不能获取或者改变相邻像素的规则也有意外。其中一个就是像素shader在计算渐变或者衍生值的时候就可以直接获取信息。像素shader提供了每像素的屏幕轴改变通过插值就可以计算。这样的值在贴图取样的时候非常有用。这些渐变在操作贴图纹理滤波的时候非常有用(6.2.2节),我们希望图片被多少像素覆盖。所有的当代GPU实现了这个特性,通过处理2×2一组的片元,成为quad。当像素shader获取一个渐变值,临近的片元区别便会返回。见图片。

Real-time Rendering 第三章 图形处理单元_第15张图片
image

一个通用核心有这样的获取临近数据的能力——在相同wrap中的不同thread——所以能够通过像素shader计算渐变。这个实现的一个重要结论是减半数据无法在动态控制流的shader中获取。一个if state或者loop有多个重复计算。所有的片元必须被相同的指令处理,所以这四个像素结果对于计算渐变都是是有意义的。这个限制甚至在离线渲染系统里面也是存在的。

Apodaca, Anthony A., “How PhotoRealistic RenderMan Works,” in Advanced RenderMan: Creating CGI for Motion Pictures, Morgan Kaufmann, Chapter 6, 1999. Also in SIGGRAPH Advanced RenderMan 2: To RI INFINITY and Beyond course, July 2000. Cited on p. 51

DX11介绍了一种buffer类型允许被任何地方获取,也就是unordered access view(UAV)原来只是用于像素以及compute shader,在DX11.1之后便对所有shader类型开放。

Bilodeau, Bill, “Vertex Shader Tricks: New Ways to Use the Vertex Shader to Improve Performance,” Game Developers Conference, Mar. 2014. Cited on p. 51, 87, 514, 568, 571, 798

OpenGL 4.3称之为 shader storage buffer object(SSBO)两个名字都有自己的描述方式。像素shader是任意顺序并行执行的,这个储存buffer在他们之间被共享。

一些装置需要避免data race condition(data hazard,数据冲突)也就是多个shader程序抢占影响同一个值,会产生一些随机的结果。例如两个调用同时对一个像素执行就会发生错误。两者都会获取原始值,同时修改,但是不管结果是什么样,总会被后者覆盖。GPU通过Shader获取作为原子操作避免这个问题。但是原子性意味着一些shader也许在获取内存地址进行读、写、修改的时候会陷入阻塞。

原子性避免数据冲突时,许多算法需要一定顺序执行。例如你希望在远处绘制一个蓝色透明面片,然后用红色覆盖它,红色在蓝色上面进行混合。这个可能造成一个像素中有两次的像素shader调用。在这里需要让蓝色先于红色进行执行。在标准管线中,片元结果在合并阶段处理之前是经过排序的。Rasterizer order views(ROV)在DX11.3中推出,强制执行顺序。有点像UAV,他们能够用同样的方式进行读写。他们的不同点是ROV保证了数据获取是以一个正确的顺序。这增加了这些buffer对于shader的可用性。举个例子,ROV是的像素shader编写自己的混合函数成为了可能,因为它可以直接获取并且写入任意位置的ROV,因此合并步骤不再被需要。

Bookout, David, “Programmable Blend with Pixel Shader Ordering,” Intel Developer Zone blog, Oct. 13, 2015. Cited on p. 52

这样做的代价是一个无序的访问操作如果被发现的话,像素shader的调用就会被阻塞,直到更早的三角绘制被执行。

3.9 合并阶段

就想2.5.2节描述的那样,合并阶段是讲深度以及独立片元的颜色合并到framebuffer中。DX将这个阶段称作output merge,OpenGL则叫做 per-sample-operations.在大部分的传统管线图中(包括我们自己的)这个阶段是模板buffer以及zbuffer操作发生的地方。如果片元可见,其他操作则是颜色混合。对于不透明表面实际上没有执行任何的混合,因为片元颜色就是普通替换了先前的颜色。实际的片元混合以及颜色储存则是对于透明以及合成操作而言的(5.5节)。

想象一个片元在光栅化生成之后经过了像素shader,当zbuffer使用的时候,发现被之前的渲染片元隐藏。那一切之前的操作都是无谓的。为了防止这样的浪费,在像素shader之前,许多GPU使用了一些合并测试。

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

片元的z深度(以及任意其他被使用的,例如模板buffer或者裁切)用于测试可见性。片元如果被隐藏,就会被提前剔除。这样的技术叫做early-z。

Mitchell, Jason L., and Pedro V. Sander, “Applications of Explicit Early-Z Culling,” SIGGRAPH Real-Time Shading course, Aug. 2004. Cited on p. 53, 1016

Sander, Pedro V., Natalya Tatarchuk, and Jason L. Mitchell, “Explicit Early-Z Culling for Efficient Fluid Flow Simulation,” in Wolfgang Engel, ed., ShaderX5, Charles River Media, pp. 553–564, 2006. Cited on p. 53, 1016

像素shader有能力去修改z深度或者裁切片元。如果这样的操作被发现在像素程序的话,那么early-z将无法起作用,经常使得整个管线更低效。DX11以及OpenGL4.2允许像素shader强制开启early-z测试,不过会伴随着一些限制。

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

23.7节可以看到更多关于early-z以及其他z-buffer的优化知识。高效使用early-z可以得到更高的效率,细节讨论在18.4.5.

合并阶段发生在固定阶段之间,例如片元安装它的操作是高度可配置的。颜色混合可以设置成大量种类。最常见的是multipliacation、addition以及subtraction涉及到颜色以及透明度的计算,其他操作也是有可能的,例如最小以及最大值,还有位运算。DX10添加了将pixel shader中的颜色值与framebuffer 中的颜色值进行混合。这个能力被称作dual source color blending并且不能和多渲染目标一起用。MTR也支持混合,DX10.1推出了不同buffer之间混合的能力。

在先前的小节中提到了11.3提供了ROV的方式来实现可编程混合,不过有一定的性能代价。ROV以及合并阶段都保证绘制顺序,输出不变形。不关心任意像素shader生成结果的顺序,这是可以保证结果有序并且以他们的输入顺序输出到合并阶段,object以及三角片逐一进行的API要求。

3.10 The Compute Shader

GPU不仅仅可以用于传统的图形管线,有需要无图形的用途在不同领域使用,例如纳斯达克指数以及神经网络与深度学习。这样的方式使用GPU被称作GPU computing。像CUDA以及OpenCL的平台用于控制GPU作为一个高度并行的处理器集合,不需要使用与获取图形功能。这些框架用C或者C++进行扩展,针对GPU进行制作。

在DX11中推出了compute shader用于GPU computing,这种shader帮助shader不再只局限于渲染管线。它绑定与渲染处理,因为涉及到了图形API。把顶点、像素以及其他shader放有一边。它去除一组shader处理器用于管线。这种shader就像其他的shader,因为它有一组输入数据,并且可以访问buffer,例如贴图用于输入以及输出。wrap以及tread对于compute shader更加可见。举个例子每一个调用能够后去一个线程索引。而且也有线程组的概念,包含了1到1024个线程,在DX11的实现中。这些线程组被指定了x、y、z坐标,大部分简单地在shader代码中用到。每一个线程有很小数量的内存用于线程共享。在DX11中这个数量是32kb。Compute shader被线程组执行,所以所有的线程保证了运行并行性。

Zink, Jason, Matt Pettineo, and Jack Hoxley, Practical Rendering & Computation with Direct3D 11, CRC Press, 2011. Cited on p. 47, 54, 90, 518, 519, 520, 568, 795, 813, 814, 914

一个重要的compute shader的又是是可以获取GPU生成的数据。发送数据到GPU到CPU是有一定延迟的,所以如果把数据保留在GPU会有更好的性能。

Pettineo, Matt, “A Sampling of Shadow Techniques,” The Danger Zone blog, Sept. 10, 2013. Cited on p. 54, 238, 245, 250, 265

后处理效果(Post-processing)将已经渲染的图片以一定方式进行修改,是一种常用的compute shader的应用。共享内存意味着从图片像素采样的中间结果可以在相邻线程之间进行共享。例如,使用一个compute shader来描述图片的分布规律或者平均亮度,被发现跑两次和一次像素shader处理的速度是一样的。

Giesen, Fabian, “A Trip through the Graphics Pipeline 2011,” The ryg blog, July 9, 2011. Cited on p. 32, 42, 46, 47, 48, 49, 52, 53, 54, 55, 141, 247, 684, 701, 784, 1040

Compute shader对于例子系统、网格处理例如面部表情,

Bentley, Adrian, “inFAMOUS Second Son Engine Postmortem,” Game Developers Conference, Mar. 2014. Cited on p. 54, 490, 871, 884, 904

剔除,

Wihlidal, Graham, “Optimizing the Graphics Pipeline with Compute,” Game Developers Conference, Mar. 2016. Cited on p. 54, 798, 834, 837, 840, 848, 849, 851, 908, 986

Wihlidal, Graham, “Optimizing the Graphics Pipeline with Compute,” in Wolfgang Engel, ed., GPU Zen, Black Cat Publishing, pp. 277–320, 2017. Cited on p. 54, 702, 784, 798, 812, 834, 837, 840, 848, 850, 851, 908, 986

图片纹理滤波,

Mah, Layla, and Stephan Hodes, “DirectCompute for Gaming: Supercharge Your Engine with Compute Shaders,” Game Developers Conference, Mar. 2013. Cited on p. 54, 518, 535

Story, Jon, “DirectCompute Accelerated Separable Filtering,” Game Developers Conference, Mar. 2011. Cited on p. 54, 518

改进深度精度,

Lauritzen, Andrew, Marco Salvi, and Aaron Lefohn, “Sample Distribution Shadow Maps,” in Symposium on Interactive 3D Graphics and Games, ACM, pp. 97–102, Feb. 2011. Cited on p. 54, 101, 244, 245

阴影,

Kasyan, Nikolas, “Playing with Real-Time Shadows,” SIGGRAPH Efficient Real-Time Shadows course, July 2013. Cited on p. 54, 234, 245, 251, 264, 585

景深

Hoobler, Nathan, “High Performance Post-Processing,” Game Developers Conference, Mar.

  1. Cited on p. 54, 536

以及任意其他GPU处理器可以起作用的任务。Wihlidal讨论了compute shader如何比tessellation hull shader更加高效。

Wihlidal, Graham, “Optimizing the Graphics Pipeline with Compute,” in Wolfgang Engel, ed., GPU Zen, Black Cat Publishing, pp. 277–320, 2017. Cited on p. 54, 702, 784, 798, 812, 834, 837, 840, 848, 850, 851, 908, 986

下图有更多应用。

Real-time Rendering 第三章 图形处理单元_第16张图片
image

在我们对渲染管线实现讨论的最后,还有很多方式在GPU功能可以用于渲染相关处理。相关理论以及算法是该书的中心目标之一,我们现在要将目光转移到变换以及着色。

你可能感兴趣的:(Real-time Rendering 第三章 图形处理单元)