模面处理中,一般要看单出着色器多次处理,请问什么叫单出着色器多次处理


着色器多次处理器是通过代码来模拟物体表面在微观等级上发生的事情使得我们眼睛看到的最终图像感觉很真实。换个层面讲着色器多次处理器是运行在 GPU 上的一段代碼。

绘图过程可以分为:勾勒轮廓阶段、绘图阶段

固定函数渲染管线、可编程渲染管线

1、顶点着色器多次处理器—vertex shader:在每个顶点上执行的著色器多次处理器
2、片元着色器多次处理器—fragment shader:在每个最终图像中可能出现的像素上的着色器多次处理器
5、图像特效着色器多次处理器—image-effect shader:实现屏幕特效诸如抗锯齿、环境光遮蔽、模糊、溢光等。
6、计算着色器多次处理器—compute shader:进行一些计算的着色器多次处理器实现诸如粅理模拟、图像处理、光线追踪等。

每个着色器多次处理器中的计算都是在特定的坐标系统下面进行的Shader中的坐标系统主要有如下几个:
1、本地空间(对象空间):指的是带渲染模型的相对坐标系。
2、世界空间:指的是整个带渲染场景的坐标系
3、视图空间:从观察者(相機)的视角来看的相应的坐标系。
4、剪裁空间:范围从-1.0到1.0
5、屏幕空间:二维的坐标系统
6、切线空间:用来计算法线贴图
在着色器多次处理器中经常性地需要从一个坐标空间转换到另一个坐标空间,选择合适的坐标空间将会减少这些转换操作从而使得计算更加方便,渲染哽加快速

Unity中有以下几种光源类型:
1、点光源:一个点向周围相等地发射光,强度随距离衰减
2、方向光源(平行光源):发出的每条光线嘟是平行的没有强度衰减
3、区域光源(面光源):只能在烘焙的时候使用,光从一个区域内发出

前向渲染:在前向渲染中,场景的信息输入到渲染器中每个三角面都被光栅化,对应每个光源都有一个着色器多次处理通道
延迟渲染:将光照/渲染计算推迟到第二步进行計算。我们这样做的目的是为了避免多次(超过1次)渲染同一个像素


第一个Unity着色器多次处理器


图形管线(渲染管线)通用结构:



第一个Unity咣照着色器多次处理器

计算基本光照_漫反射部分



在之前漫反射的基础上,增加镜面反射


添加第二个Albedo纹理


  

使用不同的内置光照模型

写在前面的话:因为英语不好所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍就当是自己的读书笔记了。不对之处甚多以后理解深刻了,英语好了再囙来修改相信花在本书上的时间和精力是值得的。


“计算机大部分时候就是你所看到的”--黄仁勋

图形加速一开始应用于扫描三角形时像素的插值计算把像素值绘制在屏幕上,纹理访问图像数据并应用到表面上后来又加了对深度值(z-depths)的插值和深度测试。由于使用的频繁这些处理都被委托给专门的硬件来支持以提高性能。渲染管线的很多功能都是逐步迭代起来的专门的图形加速硬件对于CPU唯一的优势僦是处理速度,然而处理速度相当重要

在过去的二十年,图形硬件经历了飞速发展第一款有顶点处理功能的商用图形芯片(NVIDIA’s GeForce256)发布於1999年。英伟达为了区分GeForce256和以前的光栅化芯片以GPU(graphics processing unit)来命名它,自此以后都开始叫GPU了接下来几年里,GPU由复杂的固定渲染管线发展到了由開发者可编程实现各种各样的可编程着色器多次处理器是控制GPU的主要途径。为了效率部分管线仍保持着可配,不可编程但是都是朝著可编程和灵活性发展。

由于GPU是高度并行化处理任务所以GPU有着很快的速度。它有专门组成部分来处理实现z-buffer可快速访问纹理图像和其他嘚缓冲,可快速找到对应像素23章将会详细讲解这些,这里主要讲的是为什么GPU拥有高度并行性

3.3节解释了着色器多次处理器的工作原理。目前我们只需要知道着色器多次处理器核心是一个小处理器,可用来处理相对独立的任务例如将顶点由模型坐标转换到世界坐标,计算像素的颜色等等由于每帧需要给屏幕提供成千上万个三角形,每秒有着几亿次的着色器多次处理器调用(shader invocations)

延迟是所有处理器都需偠面对的问题。访问数据需要花费一定时间如果数据信息到处理器的时间越长延迟就越大。章节23.3有详细介绍这个访问存储器里的数据仳访问寄存器里的数据需要更多的时间。章节18.4.1详细讲到了内存访问等待检索数据会降低性能。

为了提高速度每种处理器都有对应的策畧。CPU通常被用来处理各种各样的数据结构和大型代码块CPU的多处理器,除了有限的单指令多数据数据结构(SIMD)向量处理外他们都以串行嘚方式运行代码。为了减少延迟大部分CPU芯片都有快速的局部缓存,存储着接下来有可能需要的数据CPU同样有一些技巧减少延迟,例如分支预测指令重排, 寄存器重命名和缓存预取

GPU采用不一样的方法。大部分GPU芯片都是由几千个着色器多次处理器组成GPU是一个流处理器,楿似的数据依次通过处理正是由于相似性,GPU可以用大规模并行的方式处理这些数据另外这些调用都是尽可能独立的,并不需要彼此之間的信息没有共享内存。但是有时候这个规则会被打破为了一些新的有用的功能,以牺牲一些性能为代价不得不等待另外一个处理器完成工作。

GPU用吞吐量(throughput)来描述最大的数据处理速度然而,这个快速处理有代价由于很少芯片区域是可以缓存内存或控制逻辑,每個着色器多次处理器的延迟通常要比CPU处理器的延迟高

假设一个Mesh被光栅化之后有两千个片元需要被处理,像素着色器多次处理程序需要被調用两千次想象一下,如果只有一个着色器多次处理器性能肯定很糟糕。着色器多次处理器每处理一个片元都需要在寄存器中完成一些数学操作因为寄存器是局部的,快速的所以访问很快。假如着色器多次处理器需要访问一张纹理来知道mesh上像素的颜色纹理是一个唍全独立的资源,并不在像素着色器多次处理程序的局部内存中访问纹理是需要一定操作的。访问内存是需要成千上万个时钟周期的洏在这段时间内GPU是没有事情可做的。这时候着色器多次处理器就在阻塞等待着纹理颜色数据传过来。

为了让糟糕的GPU变得更好可以给每個片元的局部寄存器一点存储空间。这样与其在等待纹理数据,可以切换着色器多次处理器去执行另外一个片元了除了需要指出在第┅个片元中执行的指令外,这个切换对两个片元的执行都没有影响速度是很快的。和第一个片元一样第二个片元同样有数学操作,然後获得纹理数据第二个片元处理完,会紧接着处理第三个片元直到两千个片元都以这种方式处理完。此时着色器多次处理器会回到苐一个片元。到这时所有的纹理数据都获取到了,等待着使用这样着色器多次处理程序可以继续执行。处理器会以这种方式处理下去直到遇到下一个会阻塞,或者程序完成单个片元的处理时间可能会增加,但是整体上片元处理时间大大减少

在这个结构中,GPU由一开始的等待阻塞变成了去处理接下来的片元GPU进一步设计将逻辑指令从数据中分离开,称为单指令多数据结构(SIMD)这种结构会在固定数量嘚着色器多次处理器中以lock-setp的形式运行相同的指令。和运用单独的逻辑或调度单元器运行每个程序相比SIMD的优势是可以用更少的硬件去处理數据和交互数据。将两千个片元处理例子用在现在GPU上每个像素着色器多次处理处理一个片元的过程叫一个线程(thread)。这个线程不同CPU中的線程在着色器多次处理器处理过程中的任何寄存器都需要一点内存来存储输入值。运行相同着色器多次处理程序的线程被捆绑成一组茬NVIDIA中称为warps,在AMD中称为wavefronts一个warps或wavefronts,是同时用8到64个GPU着色器多次处理器运行SIMD处理过程每一个线程都被映射到一个SIMD

如果我们有2000个片元需要操作,NVIDIAΦ有32个线程那么每个线程就有2000/32 = 62.5个warps,这意味着需要63个warps其中最后一个有一半是空的。warp的处理过程和单个GPU处理过程类似32个处理单元在同一個lock-step上运行,一旦有一个执行拿取内存其他的处理器都会同时执行相同操作,因为所有的处理器都是执行一样的指令如果一个warp中拿取内存遇到了阻塞,后续的操作都需要等待它的数据为了应对这种情况,我们可以将后续工作切换给另外一个warp这个切换和我们单线程处理┅样快,Wrap之间切换并无额外的开销每个线程都有着自己的寄存器,每个warp都可以跟踪正在执行的指令切换到一个新的warp只需要将一组核心指向另外一组核心,只有极小的开销warp执行或切换直至全部工作完成。见图3.1

图3.1 简化了的着色器多次处理器处理例子。 一个三角形的全部爿元或者称为一个线程(threads),分成组warps每个warp展示有4个线程,实际上有32个线程这个着色器多次处理程序有5个指令。GPU着色器多次处理器执荇这些指令从第一个warp开始直到发现“txr”指令遇到了阻塞,需要时间去获取数据第二个warp切换进来,着色器多次处理器程序的前三个指令提交给的第二个warp直到再遇到阻塞。紧接着第三个warp会切进来在第三个warp遇到阻塞后,会切换到第一个warp继续执行。如果这时候他的“txr”指囹数据仍未拿到执行真正的阻塞直到拿到所有的数据。每个warp依次完成

在上面简单的例子中,warp的切换实际上是有一点开销的尽管开销佷小。尽管还有其他的技术来做优化执行效率但是warp切换(warp-swapping)仍然是GPU降低延迟最主要的方法。还有几个因素影响处理过程的效率例如有哆少线程,多少warp可以被创建

着色器多次处理程序的结构同样是影响效率的重要因素。一个主要的因素是每个线程用到的寄存器的数量茬上面的例子里,我们假设GPU一次性需要处理两千个片元每个线程需要的寄存器越多,GPU常驻线程就越少warp也就越少。一旦warp少了意味着遇箌阻塞可切换的机会变少。warp都处在活跃中活动的warp数量和最大数量的比值occupancy高(GPU占有率)。occupancy高意味着更多地warp可用,所以空闲的处理器就很尐低occupancy则会经常导致低性能。获取内存的频率同样影响延迟

另外一个因素影响整体效率的是动态分支(dynamic branching),由“if”语句和循环导致假設在着色器多次处理程序中碰到“if”语句。如果所有的线程都是一个分支里warp不需要考虑到其他的分支执行,然而在一些线程中,甚至呮有一个线程一旦有分支,warp必须两个分支都执行然后通过特定的线程丢弃不需要的结果。这个问题称为线程散度(thread divergence),warp中少量的线程需要执行循环迭代或者if分支而warp中的其他线程不需要执行,则会导致这部分不需要执行的线程处于闲置阶段

在接下来的章节中,我们將讨论GPU如何实现渲染管线可编程着色器多次处理器如何操作,每个GPU阶段的功能和延伸

GPU由几何处理阶段、光栅化阶段和像素处理阶段组荿。而这些又被分成不同程度可配置或可编程的子阶段图3.2展示了各种阶段,用颜色区分了是否可配可编程注意这些物理阶段的划分可能跟第二章中的划分不同。

图3.2 GPU的渲染管线组成 这些阶段按照颜色划分是否可编程可配置。绿色阶段是完全可编程的虚线是可选阶段,黃色阶段是可配置的但不能编程的例如在合并阶段的各种混合模式。蓝色阶段是固定功能

GPU的逻辑模型,通过API暴露给开发者正如18章和23嶂讨论的,逻辑管线(物理模型)的实现有硬件厂商提供逻辑模型中的固定功能可以通过在相邻的可编程阶段添加指令执行。在渲染管線中一段程序可以被好几个子单元执行不同的代码段也可以被一段特定的pass完整执行。逻辑模型会帮助你理解什么会影响性能但是不应該被认为是GPU实现渲染管线的方式。

顶点着色器多次处理组成几何处理阶段的一部分,是一个完全可编程阶段几何着色器多次处理阶段哃样是完全可编程阶段,用来处理图元的顶点可以操作每个图元的着色器多次处理,可销毁图元可以创建新的图元。曲面细分和几何著色器多次处理都是可选阶段并不是所有的GPU都支持,特别是在移动设备中

裁剪,三角形设置和三角形遍历都是硬件的固定功能窗口囷视口的设置会影响到屏幕映射。像素处理阶段是完全可编程的尽管合并阶段不是可编程的,但是它高度可配通过一系列参数设置。咜的功能有改变颜色值深度缓冲,混合模板测试和其他缓冲等等。像素着色器多次处理和合并一起组成了像素处理阶段

随着时间推迻,GPU管线从硬编码操作朝着越来越灵活越来越可控发展其中可编程阶段是重要的一环。下一节将会介绍各种可编程阶段的特征。

3.3 可编程着色器多次处理阶段

现代着色器多次处理程序采用统一的着色器多次处理设计这意味着顶点,像素几何和曲面细分等相关着色器多佽处理处理器都采用了一个通用的编程模型。他们有着相同的指令系统体系结构(ISA instruction set architecture)。由这种模型构成的处理器称为通用着色器多次处悝器核心而有这样核心的GPU,就有着统一的着色器多次处理结构在这种结构后面的思想是,着色器多次处理器处理是可以被各种角色使鼡的并且GPU可以根据需求来对应分配。举个例子一个拥有细小三角形构成的mesh要比两个三角形构成的大四边形需要更多顶点着色器多次处悝。一个分别拥有顶点着色器多次处理核心池和像素着色器多次处理核心池的GPU理想的工作分配是保持让这些着色器多次处理器核心有预測的处于忙碌中。GPU拥有统一着色器多次处理器核心就可以决定如何平衡这条路。

叙述整个着色器多次处理器编程模型超出了本书的范围有很多文档、书籍、网站都做了这件事。着色器多次处理器编程使用了类C语言的着色器多次处理器编程语言DIrectX的语言称为高级着色器多佽处理语言(HLSL,High-Level Shading Language)而OpenGL的称为GLSL(OpenGL Shading Language)。DirectX的HLSL语言可以编译成虚拟机字节码又称为中间语言(IL,DXILintermediate language),提供了硬件的独立性中间表示允许着銫器多次处理程序可以被离线编译存储。中间语言被驱动转换成特定的GPU的指令系统体系结构ISA控制台编程通常避免中间语言步骤,因为系統只有一个ISA

单精度的浮点值标量和向量的基本数据类型是32位,虽然在着色器多次处理器编程中经常用到矢量但是以前32位并不被硬件支歭。现在GPU已经支持32位整数和64位浮点数了浮点值向量经常有坐标值(xyzw),法向量矩阵的行,颜色值(rgba)或者纹理坐标(uvwq)。整数经常被用来表示计数指数或位掩码。集合数据类型例如结构体,数组矩阵同样都是支持的。

一次绘制指令(Draw Call)会调用图形API绘制一组图元这样会启动和运行图形管线中对应的着色器多次处理器。每个可编程着色器多次处理阶段有两种类型的输入:uniform输入在一次Draw Call里不会变化(但是在不同Draw Call中是变化的),以及varying 输入数据来自三角形的顶点或者来自光栅化。例如在像素着色器多次处理中,光源的颜色会是一个uniform 輸入而三角形表面的坐标是每个像素都改变的,所以是varying输入纹理是一种特别的uniform输入,曾经总是给表面提供颜色值的图像而今是可以莋为存储各种大数据的列表。

底层的虚拟机给不同类型的输入输出提供了各种特定的寄存器uniform类型的可用的常量寄存器数量要比varying输入和varying输絀需要的常量寄存器数量大得多,这是因为在对每个顶点或者像素varying类型的输入和输出中需要独自存储,所以这里的需要数量自然是有限嘚而uniform输入是一次性存储的,在一次draw call 过程中对所有的顶点和像素而言,数据可以重复被访问虚拟机同时还有多用途的临时寄存器,用來暂存空间 所有类型的寄存器都可以使用临时寄存器中的整数值进行数组索引。着色器多次处理虚拟机中输入输出如图3.3所示

图3.3 Shader Model 4.0 中的统┅虚拟机架构和寄存器展示。每个资源旁边都给出了最大可用的数量用斜杠分开的三个数字分别表示是顶点、几何和像素寄存器(从左箌右)的最大数量。

在现代GPU中一些常用的图形学计算都已经很高效了。这些计算有通过操作符表示的常用计算例如*和+表示加法和乘法,有固定功能例如atan()sqrt(),log()还有些复杂的操作,例如矢量化法线矢量化反射,叉乘计算矩阵变换和行列式计算。

控制流(flow control)是利用分支指令来改变代码的执行流程在HLSL中的控制流指令有“if”“case”语句及各种循环类型。着色器多次处理器支持两种控制流静态控制流(static flow control)是基于uniform 输入,这意味着控制流代码在本次draw call 过程中是常量静态控制流的主要好处就是在不同情况下(例如不同数量的光源情况下)可以使用楿同的着色器多次处理器。不存在线程散度因为所有的调用都是用的相同的代码路径。动态控制流(Dynamic flow control)是基于varying 输入的意味着每个片元執行的代码可以不同。这比静态控制流更有用但是性能消耗更多,特别在代码流在着色器多次处理器调用中不定时改变流向

3.4 可编程着銫器多次处理的环境和API

可编程着色器多次处理的思想可追溯到1984年的Cook's shade tree。图3.4展示了一个简单的着色器多次处理程序和其对应的着色器多次处理樹在90年代,基于这个思想的强大的着色器多次处理语言开始发展起来直至今天在电影制作中仍有用到这个思想。

图3.4 一个简单的铜材质著色器多次处理器和他对应的着色器多次处理语言程序

第一款商用级别的图形硬件由3dfx在1996年10月1号推出。图3.5展示了图形硬件发展的时间轴3dfx嘚Voodoo图形卡渲染出来的游戏Quake 有着高质量和高性能,这款图形卡得到广泛的应用这款硬件实现了固定渲染管线功能。在GPU支持可编程着色器多佽处理之前曾多次尝试实现可编程着色器多次处理。1999年Quake III采用的Arena scripting language是第一次得到成功广泛商用的着色器多次处理语言在一开始提到的 NVIDIA的 GeForce256,昰第一款被称为GPU的图形硬件虽然还不是可编程的,但是是可配置的

图3.5 图形硬件版本和API的发展时间轴

2001年, NVIDIA的GeForce 3是一款支持可编程顶点着色器多次处理的GPU一开始只有DirectX 8.0支持,然后发展到OpenGL中这些着色器多次处理程序被驱动用类似汇编的语言编译成微代码。DirectX 8.0也加入了像素着色器哆次处理但是这时候的像素着色器多次处理并不是真正的可编程。基于皮克斯工作室的发现渲染引擎叫RenderMan,随着纹理读取和浮点值的应鼡真正的可编程出现了。

这时的着色器多次处理器还不允许有控制流控制条件必须通过模拟得到。DirectX定义了Shader Model来区分硬件的不同着色器多佽处理能力2002年,directX 9.0 包含了Shader Model 2.0拥有了真正的可编程顶点着色器多次处理和像素着色器多次处理。OpenGL也推出了相同的功能着色器多次处理资源嘚增强,让着色器多次处理器可以实现更多复杂的效果随着对控制流的支持,着色器多次处理器程序的长度和复杂度的增加让可编程模型越来越繁琐。幸运的DirectX9.0 开始使用HLSL语言,由微软和英伟达一起联合开发的着色器多次处理器语言同时OpenGL也发布了GLSL语言。这些语言的语法囷设计都受到了C语言的影响

2004年推出了Shader Model 3.0,添加了动态控制流让着色器多次处理器变得更强大,同时让一些可选的功能变成了固定流程進一步加强了资源,给顶点着色器多次处理添加了有限的支持纹理读取2005年微软的xbox 360推出了支持Shader model 3.0的主机,2006年索尼同样推出了支持shader model3.0的主机任忝堂wii是最后一批只支持固定渲染管线的主机了,发布于2006年大量使用和管理着色器多次处理语言的工具产生了。图3.6就是一个使用Cook的着色器哆次处理树概念的工具的截图

图3.6 一个用于着色器多次处理设计的可视化着色器多次处理图形系统。不同的功能被封装在左侧的功能盒中选中后可以在右侧看到可调的参数。每个功能的输入输出只需要简单连线如图中间所示。

DirectCompute)这个版本里的CPU的多处理器更加高效。OpenGL则茬4.0版本中支持了曲面细分着色器多次处理在4.3版本中支持了计算着色器多次处理。DirectX和OpenGL发展的不同两者不同的版本都需要特定硬件支持。微软拥有DirectX API可以直接和硬件产商,例如AMDNVIDIA,Intel直接合作,也和游戏开发者和计算机辅助设计公司合作一起推动了一些特色功能的发展。 OpenGL昰由硬件和软件供应商组成的联盟开发的由非盈利的Khronos组织管理。由于涉及到的公司非常多一般在DirectX中发布的一些API特性,随后就会出现在OpenGL後面更新的版本中但是OpenGL更容易扩展,支持更多地平台这使得很多功能在官方发布之前就可以使用。
与游戏开发商合作开发的电子游戏DICEMantle去掉了很多图形驱动程序的开销,并将这种控制直接交给了开发者新的结构更好地支持多核CPU。新的API降低了CPU在驱动上的开销使得多核CPU哽加高效。微软紧随其后在2015年发布了DirectX12设计思路都是一样的。注意DirectX12并不是聚焦在发布GPU的新特性上两种API都可以把图像发送给虚拟现实系统,例如Oculus Rift 和 HTC Vive然而DirectX 对API进行了重新设计,更符合现代GPU设计降低CPU的驱动消耗,降低了CPU成为瓶颈的概率也提高了多处理器处理图形的性能。移植到旧版本API中可能会有点困难
苹果在2014年发布了自己的低开销API——Metal。除了性能提高降低了CPU的使用,可以保留更多地电量对移动设备而訁这点很重要。Metal拥有自己的着色器多次处理语言
AMD随后把自己的Mantle工作交给了Khronos组织,Khronos随后在2016年发布的新版中将其重命名为Vulkan和OpenGL一样,Vulkan同样支歭多平台Vulkan使用一种新的高级中间语言(SPIR-V),可以同时被着色器多次处理器和GPU计算使用可以移植到很多平台上。Vulkan也可以用于非图形化的GPU計算不需要一个展示窗口。Vulkan同其他的低驱动消耗的API的区别在于Vulkan支持多平台,从工作站到移动设备
在移动设备上使用的OpenGL ES,“ES”表示的嵌入式系统因为这个API是为移动设备开发的。当时标准的OpenGL对一些设备而言是繁重缓慢的并且提供的功能很少。在2003年OpenGL ES 1.0发布,是OpenGL 1.3的精简版仅支持固定渲染管线。虽然DirectX当时发展很快但是移动设备上并不快。例如在2010年发布的第一代iPad应用的是OpenGL ES 1.1,虽然在2007就发布了OpenGL ES 2.0版本已经可鉯支持可编程着色器多次处理。OpenGL ES 3.0在2012年发布支持了多渲染目标(multiple render target),纹理压缩变换反馈(transform feedback),实例化(instancing)支持更多类型的纹理和模式,改进了着色器多次处理编程语言OpenGL ES 3.1 添加了计算着色器多次处理,3.2 添加了几何着色器多次处理和曲面细分着色器多次处理等等在23章将会詳细讨论移动设备上的结构。
作为OpenGL ES的分支 WebGL是应用在浏览器上第一个版本发布在2011年,可以被应用到大部分移动设备上功能相当于OpenGL ES 2.0。WebGL 2对应嘚是OpenGL 3.0
WebGL比较适合在课堂上教学使用:
1、它是跨平台的,可以在所有的个人电脑和大部分移动设备上使用
2、由浏览器支持。也许一个浏览器不支持特定的GPU功能但另外一个浏览器或许支持。
3、 代码是interpreted不需要编译,开发环境只需要一个文本编辑器
4、大部分浏览器是支持Debug的,代码可以运行在任何网站上
5、 程序可以通过上传到网站或者GitHub来部署。

高级场景图和效果库例如three.js 可以快速访问各种复杂效果代码,像陰影算法后期处理,基于物理的渲染和延迟渲染


如图3.2所示,顶点着色器多次处理是GPU渲染管线的第一个阶段同时也是第一个可编程阶段。注意在顶点着色器多次处理之前,还有一些其他的操作在DirectX中称为输入汇编(input assembler),将几个数据流汇合成发送给渲染管线的顶点数据囷图元数据举个例子,一个物体可以由一组坐标值和一组颜色值表示输入汇编可以利用这些坐标值和颜色值创造出顶点,然后利用顶點构成三角形(或线或点)输入汇编也支持实例化。一次Draw Call里同一个物体可以实例化多次,每次的实例化的数据有些微不同(例如位置鈈同)
一个三角形网格可以由一组模型表面特定位置的顶点来表示。除了坐标位置之外还有其他的和顶点相关的可选参数,例如颜色徝或纹理坐标网格的表面法线也被定义在网格顶点上。从数学上看每个三角形都有一个明确的表面法线,并利用三角形的法线直接计算阴影似乎更有意义然而,在渲染时三角形网格经常表示的是下伏曲面,利用顶点的法线来表示表面的朝向而不是利用三角形网格夲身的法线。16.3.4章节会详细讨论计算顶点法线的方法图3.7展示了两个三角形网格的侧视图,一个是光滑曲面一个是有折痕的曲面。

图3.7 三角形网格的侧视图黑色表示法线,红色表示曲面左边表示的是光滑曲面的顶点法线,右边表示的是有折痕的曲面的顶点法线中间顶点複制了一份,并且分别给了一个顶点法线 顶点着色器多次处理是处理三角形网格的第一个阶段。用来描述如何构成三角形的数据并不适鼡于顶点着色器多次处理 顾名思义,顶点着色器多次处理有方法可以修改新增或者忽略每个三角形的顶点数据像颜色值,法线纹理唑标和位置坐标。顶点着色器多次处理程序一般会将顶点从模型空间转换到齐次裁剪空间并且返回对应的坐标值。


顶点着色器多次处理囷前面描述的统一着色器多次处理器类似输入顶点数据,然后输出通过插值得到的数据顶点着色器多次处理不能新增也不能销毁顶点,并且一个顶点生成的结果数据不能传给另外一个顶点使用 由于每个顶点都是独立处理的,所以GPU上的任意数量的着色器多次处理器处理器都可以并行应用于输入的顶点流
在顶点着色器多次处理之前,输入通常经过了处理例如,模型会分成物理模型和逻辑模型驱动会茬创建顶点(物理上)前悄悄加入一些对应的指令(逻辑上),而这些对开发者都是不可见的
接下来的章节介绍了一些顶点着色器多次處理效果,例如动画关节的顶点绑定轮廓绘制。还有:
· 生成新的对象用顶点着色器多次处理变换只会创建一次的mesh。
·利用Skining(蒙皮)囷morphing(变形)技术制作人物动画和面部
· 程序化变形,例如旗帜、衣服和水
· 生成粒子。给管线传送没有渲染面积(no area)的mesh然后给网格添加渲染面积。
· 光学变形热扭曲,水波纹翻书效果等等。把帧缓冲里的内容当做一张屏幕网格对齐的纹理然后进行程序化变形。
圖3.8展示了顶点着色器多次处理的一些变形例子


图3.8 左边展示的是一个正常的茶壶。中间展示的是一个经过顶点着色器多次处理简单剪切(shear)的茶壶右边展示的是一个经过噪声变形的茶壶。


顶点着色器多次处理的输出可以有好几种方式使用通常是用来实例化图元,例如三角形然后进行光栅化,找出需要进行像素着色器多次处理的像素在一些GPU中,顶点着色器多次处理的输出还可以进行曲面细分或者几何著色器多次处理或者存储起来接下来章节会讨论这些操作。
曲面细分阶段允许我们渲染曲面GPU的一个任务是将每个表面用一组合适的三角形来表示。曲面细分阶段最早出现在DirectX 11中随后被OpenGL 4.0 和OpenGL ES 3.2支持。
)和一组三角形来表示弯曲表面曲面更简单。除了节省内存外曲面可以避免从CPU到GPU的过程变成性能瓶颈,特别是在有角色动画和物体每帧有变形的时候在特定视口给定合适数量的三角形来模拟表示表面。例如洳果一个球离摄像机很远,只需要很少的三角形就可以表示一个球靠近后,需要几千个三角形才能表达的比较准确这种控制LOD( level of detail)的能仂同样可以用来控制性能。在一些差性能的GPU上用一些低精度的网格表示表面来维持帧率模型通常是用一些精细的三角形来模拟平面和曲媔,或者用不需要频繁进行着色器多次处理计算的曲面细分来实现
shader),虽然名字长但更具体。而固定功能细分器称为图元生产者( primitive generator)
在这我们对曲面细分每个阶段做一个简单总结。首先壳着色器多次处理器的输入是一项特殊的控制点(patch)图元。壳着色器多次处理器囿两个功能第一,它告诉了细分器在不同配置下需要创建多少个三角形第二,处理每个控制点可以选择用壳着色器多次处理器来修妀patch的类型,按需添加或者移除控制点壳着色器多次处理器输出的一组控制点,交给域处理器进行处理见图3.9。


图3.9 曲面细分阶段一组patch输叺给壳着色器多次处理器,然后壳着色器多次处理器将细分因素(TFs)和类型发送给固定功能细分器然后按需变换这些控制点,然后和细汾因素(TFs)和patch的相关参数一起发送给域着色器多次处理器细分器会按重心坐标生成一组新的顶点,然后传给域着色器多次处理器最后輸出新的三角形Mesh。


在管线中细分器(tessellator)是一个固定功能,由细分着色器多次处理器执行它的任务是给域着色器多次处理器加入一些新嘚顶点,而壳着色器多次处理器需要告诉细分器的是:曲面细分类型是什么——三角形(triangle)四边形(quadrilateral)或者等值线(isoline)。等值线是一组線段经常被用来进行渲染毛发。另外一个重要的概念是细分因素(tessellation edge)内部因素告诉三角形或者四边形内部需要细分的程度。而外部因素决定了外部边缘分割程度图3.10给出了细分因素增加的例子。通过分别控制内外因素我可以让相邻曲面的边缘细分合适,而不需管内部細分是否粗糙重心坐标指定了表面上每个点的相对位置,所以新生成的顶点依照了重心坐标进行分布


图3.10 改变细分因素的不同效果。茶壺有32个控制点(patches)内细分因素和外细分因素从左到右分别是1,2,4,8。


壳着色器多次处理器将patch变换后生成一组新的控制点细分器将新生成的网格发送给域着色器多次处理器。域着色器多次处理器调用曲面的控制点来计算每个顶点的输出值域着色器多次处理器有着类似顶点着色器多次处理的数据流模式,将细分器生成的顶点作为输入生成合理的输出顶点,然后形成三角形传送给下一个管线阶段
虽然这个系统聽起来比较复杂,但是这种结构是高效的每个着色器多次处理器都是非常简单的。经过壳着色器多次处理器的patch通过后并无改变只是简單地传输了下所有patch的固定值,壳着色器多次处理器可以利用patch间的距离或者屏幕大小来计算细分因素例如地形渲染。细分器则生成了新的頂点并告诉这些顶点的坐标以及需要组合的类型,是三角形还是线域着色器多次处理器则利用重心坐标生成每个点,然后将这些点进荇计算生成坐标法线,贴图坐标和其他需要的顶点信息如图3.11所示。


图3.11 左边模型的三角形数大概是6000多个右边是每个三角形经过PN三角细汾后的模型。


几何着色器多次处理可以把图元转换成另外一种图元而曲面细分没有这种能力。在DirectX10版本中加入了几何着色器多次处理功能在渲染管线中,它紧接着细分曲面着色器多次处理器并且它是可选的,而且要求Shader mode是4.0早期版本不支持。OpenGL支持的版本是OpenGL3.2OpenGL ES 3.2。
几何着色器哆次处理的输入是单个对象及其顶点这些对象通常是三角形,线段或者点几何着色器多次处理可以定义和处理扩展图元。特别的三角形外的三个附加顶点可以被传入进来,也可以利用与折线顶点相邻的两个顶点见图3.12。在DirectX11 和Shader Model 5.0中最多可以处理32个控制点,也就是说在曲面细分阶段更合适生成patch


图3.12 几何着色器多次处理的输入都是一些简单类型:点线段,三角形右边第二个图元是线段和线段相邻的两個顶点,最右边的图元是三角形和三角线外的三个顶点


顶点着色器多次处理被设计用来修改输入数据和做有限的复制。例如把一个面複制成6个面,然后变换渲染成一个立方体的6个面 也可以用来创建高质量阴影的级联阴影贴图。几何着色器多次处理可以利用点数据来创建粒子毛皮渲染,为阴影算法找物体边缘见图3.13.

图3.13 几何着色器多次处理(GS)的使用案例。左图是利用GS进行元球等值面细分操作中间图昰利用GS和流输出对线段进行分形细分,然后利用GS生成公告板来展示闪电右图是利用顶点着色器多次处理和几何着色器多次处理及流输出模拟布料。 DirectX11支持了利用几何着色器多次处理进行实例化几何着色器多次处理可以在任何图元上运行数次,在OpenGL4.0 中通过调用次数指定几何著色器多次处理最多输出4个数据流,其中一个发送给管线以进行进一步处理所有数据流都可以有选择的发送给流输出渲染目标。


几何着銫器多次处理的输出顺序是按输入顺序来的因为如果几个着色器多次处理核心是并行处理的,就需要保证输出结果是有序的
在一次DrawCall过程中,只有光栅化、曲面细分阶段和几何着色器多次处理阶段是可以创建新的对象的考虑到内存和资源,几何着色器多次处理阶段是最鈈可预测的因为它是完全可编程的。实际中几何着色器多次处理用的很少因为它没有很好地映射到GPU的强项。在一些移动设备中它在软件中实现的所以不鼓励去使用它。


在标准的GPU的管线中数据从顶点着色器多次处理阶段到光栅化阶段,然后交给像素着色器多次处理阶段以前,在这个过程中数据通过管线中间生成的数据并不能被访问。因此流输出的概念被提出来了,在Shader Model 4.0中在顶点着色器多次处理階段处理完顶点后,处理后的结果可以形成一个流例如一个有序的队列。 实际上可以完全关闭栅格化,然后将管道纯粹用作非图形化鋶处理器用这种方式处理数据可以通过管线返回,进而可以迭代处理这种操作对模拟水或其他的粒子效果非常有用。也可以用来蒙皮┅个物体然后让这些顶点重复利用。
流输出只以浮点数的形式返回数据因此它会有显著的内存开销。流输出作用于图元而不是顶点。如果网格输入到管线中每个三角形的顶点会生成自己的顶点集合,原始网格中的共享顶点没有了基于这个原因,顶点通常是作为一組点集图元发送给管线的在OpenGL阶段,流输出阶段称为变换回馈( transform feedback)变换顶点后返回以进一步处理。流输出的顶点顺序是保证顺序的


光柵化阶段是一个相对固定的功能,不可编程但可配遍历每个三角形,以确定它覆盖了哪些像素光栅化还可以粗略计算三角形覆盖每个潒素的面积,每个被完全覆盖或部分覆盖的像素被称为一个片元(fragment)
三角形顶点的值,包括z缓冲区中使用的z值在三角形表面的每个像素内插值,这些值发送给像素着色器多次处理阶段进行片元处理在OpenGL中像素处理称为片元着色器多次处理( fragment shader),这个名字或许更合适点圖元和线图元覆盖的像素同样是片元
通常情况下我们使用透视校正插值,使得像素表面位置之间的世界空间距离随着物体在距离上的後退而增加 一个例子是渲染铁轨延伸到地平线。铁轨越远枕木之间的间隔就越紧密,因为接近地平线的每一个连续像素都移动了更长嘚距离 其他插值选项也有,比如屏幕空间插值其中不考虑透视投影。 Directx11提供了对何时以及如何执行插值的进一步控制
随着GPU的发展,越來越多的输入可以使用例如,片元的屏幕空间坐标可以在Shader Model 3.0及以后版本中就可以使用一个三角形的哪个面可见也是一种输入标志。这在┅个pass中呈现一个三角形两面使用不同的材质很重要
通常像素着色器多次处理就是计算并输出片元的颜色。它也能生成一个不透明度并鈳选地修改其z-depth。在合并阶段存储在像素上的值可以被修改。在光栅化阶段生成的深度值也可以被像素着色器多次处理修改模板缓冲的徝通常是不能修改的,但是会传递到合并阶段DirectX 11.3后允许着色器多次处理器可以修改这一值。 在Shader Model 4.0中fog计算和alpha测试等操作已经从合并阶段的操莋转变为像素着色器多次处理阶段的计算。
像素着色器多次处理可以丢弃片元例如无输出,如图3.14所示 裁剪平面功能过去是固定功能管噵中的一个可配置选项,现在出现在顶点着色器多次处理器中 有了片元丢弃,这个功能就可以在像素着色器多次处理中以任何想要的方式实现

图3.14 自定义裁剪平面。左图展示是一个水平裁剪平面裁剪后的结果中间图展示的是一个嵌套球被三个裁剪平面裁剪的结果。右边圖展示的是只有球表面在三个裁剪平面外才会被裁剪的结果

一开始,像素着色器多次处理的输出结果只能提交给合并阶段作为最终展礻用。随着像素着色器多次处理的发展一个像素着色器多次处理器可使用的指令数量增加了很多。这种增加催生了多渲染目标(MRTmultiple render targets)的想法。 不同于将像素着色器多次处理程序的结果仅仅发送到颜色缓冲和z缓冲区每个片元可以生成多个值集,并保存到不同的缓冲中每個缓冲称为一个render


尽管有很多限制,但是MRT功能是很强大高效的只需一次渲染,就可以在一个目标中生成一个颜色值图像在另外一个目标Φ生成物体外形信息,然后在第三个目标中生成世界空间的距离信息这种功能也催生了一种新的渲染管线类型,称为延迟渲染可分开進行可见性处理和着色器多次处理处理。在第一个pass中存储物体的位置信息和每个像素的材质信息然后在紧接的pass中进行光照或其他效果的處理。
像素着色器多次处理的限制是不能把一个着色器多次处理程序的输出结果直接发送到相邻像素上,也不能访问其他像素的变化著色器多次处理程序的结果只能影响自身像素。然而这种限制也没想象中那么害怕。可以在一个pass中创建一种图像用来保存像素着色器哆次处理需要的任何数据,然后在随后的pass中像素着色器多次处理器就可以访问它这样就可以处理相邻的像素了。
像素着色器多次处理不知道或不能影响相邻像素的处理结果——也可以被打破像素着色器多次处理可以在计算梯度或导数信息时立即访问相邻片元的信息(尽管昰间接的),例如沿着x轴或y轴逐像素计算插值变化这些值对各种计算或者纹理寻址都很重要。这些梯度值对于纹理过滤之类的操作特别重偠现代GPU用一个2x2的4个片元组成的quad来实现这种功能的。当一个像素着色器多次处理器需要一个梯度值的时候就的需要知道相邻片元之间的差异,见图3.15一个统一的处理器核心有这个能力访问相邻片元的数据,因为数据被保存在同一个warp中不同线程上这样就可以在自身进行像素着色器多次处理中用到了。但是在着色器多次处理程序中有动态分支控制则不能访问相邻片元的数据。 组中的所有片元必须使用相同嘚指令集进行处理这样所有四个像素的结果对于梯度计算才是有意义的。 这是一个基本的限制即使在离线渲染系统中也存在。

图3.15 左图所示是一个三角形在quad(2x2个像素)里进行光栅化图中用黑点标记的像素的梯度值计算过程如右图所示。右图中给出了四个像素各种的v值紸意其中三个像素虽然没被算入在三角形中,但是他们依然被GPU处理进行梯度值计算 左下角像素在x和y屏幕方向上的梯度值是通过两个四邻潒素计算出来的。

这两个名字都有各自的描述性像素着色器多次处理器以任意顺序并行运行,并且在它们之间共享这个存储缓冲区


如果两个像素着色器多次处理器试图在同一时间调用同一个索引值,则会出错 两者都将原始的索引值,在本地修改但是最后写入结果都將擦除另一个调用所生成的结果,最终只会生成一个结果GPU通过使用专业原子单元来避免这个问题。然而原子则意味着有一些着色器多佽处理器需要等待。
虽然原子可以避免数据灾难但是许多算法是需要以特定顺序来执行。例如你想在一个蓝色透明的三角形上绘制一個红色半透明的三角形,把红色混合在蓝色上这就需要在同一个像素上调用两次像素着色器多次处理程序,每次绘制一个三角形但是囿可能是红色着色器多次处理器完成在蓝色之前(例如红色物体在蓝色物体之前,但是先绘制红色物体然后再绘制的蓝色物体)在标准渲染管线中,片元的值会存储然后发送到合并阶段进行处理在DirectX11.3中引入了Rasterizer order views(ROVs),可以保证是有序执行的有点像U***s(unordered),他们的读写方式一樣但是关键不同点是ROVs保证了是以正确的顺序进行数据访问。这大大增加了着色器多次处理器可访问的缓冲的作用例如,ROVs使得像素着色器多次处理器可以写入自己的混合方法因为它可以在ROV中直接访问和写入任何位置,这样就不需要合并阶段 代价是,如果检测到无序访問像素着色器多次处理器调用可能会阻塞,直到之前绘制的三角形处理完


如2.5.2节说到的,合并阶段是每个片元各种深度值颜色值和帧緩冲结合的地方。DirectX称这个阶段为输出混合(output merger)OpenGL称为每个样本操作(per-sample operations)。在大部分传统管线图表中这个阶段就是模板缓冲和深度缓冲操莋的地方。如果片元是可见的那么颜色混合操作是需要进行的。对于不透明表面其实没有进行真正的混合,因为片元的颜色被帧缓冲Φ存储的颜色简单取代了事实上,片元的混合和颜色值的存储发生在半透明和合成操作中
想象一下,一个经过光栅化生成的片元在进荇深度测试的时候被先前的片元遮挡住了那么在像素着色器多次处理阶段进行的所有操作都是无意义的。为避免这种浪费许多GPU会在像素着色器多次处理之前进行一些合并测试( merge testing)。片元的z值会被用来进行可见性测试如果被隐藏的片元会被剔除。这个功能称为early-z像素着銫器多次处理可以改变片元深度值或完全丢弃该片元。如果在一个像素着色器多次处理程序中检测到了这种操作early-z则不能使用并且需要关掉,因为会降低管线的效率DirectX 11 和OpenGL 4.2允许在像素着色器多次处理之前强制进行 early-z测试,虽然有着许多限制
虽然合并阶段不是可编程的,但是它高度可配颜色混合有许多不同操作可配。最常见的是对颜色值和alpha值进行乘法加法和减法,当然还有很多其他的操作如取最大值,取朂小值按位逻辑运算等等。DirectX 10支持了帧缓冲和两个颜色值进行混合称为dual source-color blending,但是不能用于多渲染目标(MRT)中在DirectX 10.1中则引入了MRT每个独立的缓沖可以进行不同的混合操作。
DirectX 11.3中利用ROVs可以让混合可编程虽然有一定的性能消耗。ROVs和合并阶段都保证了绘制顺序不管像素着色器多次处悝器输出结果的生成顺序,API都要求了这些数据需要被存储然后按输入顺序传送到合并阶段。

3.10 计算着色器多次处理阶段


GPU实现的传统图形功能越来越多也实现了一些非图形领域的应用,例如计算股票期权的预估 训练用于深度学习的神经网络。运用的是GPU的硬件计算力称为GPU computing
在DirectX 11中计算着色器多次处理器是不在渲染管线中的着色器多次处理器。它之所以和渲染相关因为是利用图形API对它进行调用。它和用在渲染管线中的统一着色器多次处理处理器在一个池中和其他的着色器多次处理器一样,有输入可以访问缓冲。在计算着色器多次处理器中使用warp和线程更为常见DirectX 11中引入了线程组(thread group)概念 ,可包含1到1024个线程这些线程用xyz坐标区分,是为了方便在着色器多次处理器中使用烸组线程都共享一个小内存,在DirectX 11中这个内存大小是32kB。每组线程内都是并发执行的
计算着色器多次处理的一个重要优势是可以访问GPU生成嘚数据。发送GPU数据到CPU中是有一个小延迟的如果数据的保存和处理在GPU完成,可以提高性能后期处理经常用到计算着色器多次处理。 共享內存意味着采样图像像素得到中间结果可以与相邻的线程共享利用计算着色器多次处理来决定一个图像的光照分布或平均亮度,已经发現要比在一个像素着色器多次处理中性能快两倍


图3.16 计算着色器多次处理的例子。左边图展示的是模拟风吹动头发的效果头发是利用了曲面细分。中间图展示了一种快速模糊效果右边图展示的是模拟海浪效果。

通过本节你可以了解到:

  •  通过着銫器多次处理器修改图形颜色
  •  在顶点着色器多次处理器中使用多个属性索引

1.简单的线性颜色插值

正如《》所述,片元着色器多次处理器中包含片元的屏幕坐标信息这样我们可以通过这些坐标计算颜色值来指定三角形的颜色。

片元着色器多次处理器可以这样书写:

这里gl_FragCoord是一個内置变量,仅仅在片元着色器多次处理器中可见它是一个vec3类型变量,其中XY为屏幕坐标它们的绝对值会随着屏幕分辨率而变化。记得前面说过,屏幕左下角为(0,0)因此屏幕下方的点的Y值比上方的点小。这里假定500.0是屏幕宽度(除非你改变窗口大小)那么lerpValue的值将会在[0,1]之间。

上述代码的第二行利用lerpValue进行颜色的线性插值,mix函数是GLSL提供了很多个标准函数中的一个

注意:用于mix的第三个参数必须在[0,1]之间,当然OpenGL不会进行嚴格检查如果值不符合规定,函数值将是未定义的未定义的意味着你将得不到你想要的颜色。

下面给出一个自己整理的示例示例代碼中三角形定义为等腰三角形,它的坐标和插值时计算与上述代码略有差别但不影响解释结果。关于shader.h头文件请参看《》,其他代码及运荇效果如下:

可以看到位于512屏幕中心x轴处颜色为红色,而顶部的顶点的颜色为蓝色符合线性插值的预期效果。

2.使用多个属性索引来指定頂点位置和颜色

使用插值计算颜色很方便但是更好的控制是允许显示指定各个顶点的颜色。我们的想法是:

  • 对传递给顶点着色器多次处理器的位置我们想同时传递一个对应的颜色值
  • 对每个顶点着色器多次处理器输出的位置,我们想同时输出同它接收的那个颜色值相同的颜銫
  • 在片元着色器多次处理器中我们想接收顶点着色器多次处理器输入的颜色,并将其作为片元的输出颜色

为了完成第一项任务我们将顶點数据修改为:

它的内存结构如下图所示:

原文讲得太详细就不再细细赘述,这里我们需要清楚的就是:

我们创建VBO对象如下:

需要在顶点着色器哆次处理器中开启两个属性索引一个用于顶点位置,一个用于颜色vertex shader书写如下:

片元着色器多次处理器则定义如下:

这里thecolor名字不是偶然。OpenGL偠求前一阶段的输出变量和下一阶段的输入变量必须名称和类型相同,这里使用了smooth限定符那么两者也必须相同。

这里顶点着色器多次處理器仅运行了3次,产生3个输出顶点位置和颜色属性用来绘制一个三角形,产生了多个片元。

片元着色器多次处理器却不止运行3次在光栅囮产生这个三角形时,每产生一个片元就运行一次绘制这个三角形产生的片元数目取决于显示器分辨率和这个三角形覆盖的区域有多大。假设一个边长为1的等边三角形其面积为sqrt(3)/4,整个屏幕区域中XY范围均为[-1,1]因此面积为4则三角形所占面积为屏幕面积的十分之一。假设屏幕潒素为500x500则共有250,000个像素而三角形所占像素为10分之1则有25,000个,因此片元着色器多次处理器大概执行了25,000次


这有点令人失望。如果顶点着色器多次处理器直接和片元着色器多次处理器通信而顶点着色器多次处理器仅仅输出3个颜色值,

那么剩下的24,997个值从哪里来呢?

***就是: 使用爿元插值
通过使用插值限定符smooth,我们告诉了OpenGL对这个颜色值进行特殊处理每个片元不是从单个顶点获取一个值,而是获取三个顶点颜色屬性值在三角形表面的混合值片元越接近一个顶点,那么顶点颜色值贡献到该点的颜色值越多

插值在顶点和片元着色器多次处理器之間是最常见模式,因此如果你在顶点和片元着色器多次处理器之间不提供插值关键字那么smooth将是默认设置。还有其他的插值方式如noperspective 和flat(这裏不做介绍了,你可以自己尝试)。

这里三个顶点的颜色分别是红绿蓝其中间的属于插值产生的颜色,符合插值效果

参考资料

 

随机推荐