您需要 登录 才可以下载或查看,没有账号?注册
x
注: 01-目录下高亮显示为本期内容 02-有些概念不同文章解释有些不同本质一样,所以同时记录两种解释为:理解二 目 录
1.1综述 1.1什么是渲染流水线(概念流水线)1.2渲染的流程
2.2渲染流水线概览2.2.1应用阶段=CPU读取数据并放入显存------准备场景数据、粗粒度剔除、数据加载到缓存(延伸阅读):什么是前向渲染、什么是延迟渲染 (延伸阅读):什么是Draw Call 如何优化Draw Call 2.2.2几何阶段=GPU读取数据并逐顶点变换到屏幕空间中 ------顶点着色器:(坐标变换、正交投影、透视投影、着色) ------几何着色器 *2.3.4片元着色器Fragment Shader ------Blinn-Phong光照模型
------锯齿和抗锯齿(超级采样、多重采样)
*2.3.5逐片元操作(测试和混合阶段)
------Alpha测试 ------模板测试
------隐藏面消除
------入口剔除 ------Alpha混合 ------颜色空间
------抖动处理 文章正文
当GPU从CPU那里得到渲染命令后,就会进行一系列流水线操作,最终把图元渲染到屏幕上。 在上一节中,我们解释了在应用阶段,CPU是如何和GPU通信,并通过调用Draw Call来命令GPU进行渲染。GPU渲染的过程就是GPU流水线。 对于概念阶段的后两个阶段,即几何阶段和光栅化阶段,开发者无法拥有绝对的控制权,其实现的载体是GPU。GPU通过实现流水线化,大大加快了渲染速度。虽然我们无法完全控制这两个阶段的实现细节,但GPU向开发者开放了很多控制权。在这一节中,我们将具体了解GPU是如何实现这两个概念阶段的。 几何阶段和光栅化阶段可以分成若干更小的流水线阶段,这些流水线阶段由GPU来实现,每个阶段GPU提供了不同的可配置性或可编程性。图如下展示了不同的流水线阶段以及它们的可配置性或可编程性。
从图中可以看出,GPU的渲染流水线接收顶点数据作为输入。这些顶点数据是由应用阶段加载到显存中,再由 Draw Call 指定的。这些数据随后被传递给顶点着色器。
(1)顶点数据 顶点数据在DirectX中成为输入装配阶段(Input Assembler State)。是渲染管线数据的主要来源,输入的数据可以包括顶点坐标、顶点颜色、顶点法线、纹理坐标等数据,利用这些输入数据我们可以在片段着色器计算片段的光照信息,最终输出到颜色缓冲器。顶点数据在流水线中以图元的方式进行处理,常见的图元有:点、线和三角面。在OpenGl中可以使用 glGenVertexArrays(),glGenBuffers(),glBindBuffer(),glBindVertexArray(),glVertexAttribPointer()等API从应用程序传入数据,并设置顶点对应的属性信息和内存布局。 四边形的顶点数据如下所示,包括了顶点坐标、顶点颜色和顶点的纹理坐标。这里我们是手动设置了四边形四个顶点的属性值,试想一下,对于一个具有几万个三角面的复杂模型,我们如何设置这些属性值呢?此时我们就需要使用到建模工具了,常见的3D建模工具有:Maya、Blender、3Ds Max等。我们可以在程序中加载建模工具产生的这些数据,提高开发效率。 我们用三角形网格来近似表示物体,用指定的3个顶点来定义三角形。由于相邻三角形会存在顶点共用的情况,尤其在物体网格非常复杂的情况下,冗余数据会非常多。我们可以通过使用索引来避免共享顶点间数据的多余,也就是使用顶点缓存对象(Vertex Buffer Object,VBO)
(2)顶点着色器(完全可编程)
顶点着色器(Vertex Shader)是流水线的第一个阶段,它的输入来自于CPU。顶点着色器的处理单位是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如,我们无法得知两个顶点是否属于同一个三角网格。但正是因为这样的相互独立性,GPU可以利用本身的特性并行化处理每一个顶点,这意味着这一阶段的处理速度会很快 延伸阅读
顶点着色器是如何工作的?
顶点着色器可以深度自定义,开发者需要根据显存中的原始数据(raw data)输出顶点颜色和顶点的齐次裁剪空间坐标。
束管理器将第一批顶点数据(如果一束中有32个线程,则存入32个顶点)以及其它需要的参数存入寄存器,然后将顶点着色器代码存入指令缓存,并要求指令分派单元从指令缓存中读取指令分派给SP。这个过程结束后,寄存器中必须至少保存着所有顶点的目标位置和顶点颜色。如果还有其它需要留给其它流水线部分的数据,也可以存放在寄存器中。在这之后,SM中顶点着色器阶段产生的运算结果会被存入L1缓存,然后由GPU将多个SM的结果组合冲入位于SM结构外的L2缓存。
在硬件中,将齐次裁剪空间坐标通过透视除法转化为NDC(Normalized Device Coordinates,归一设备坐标)。不同显卡的NDC格式可能不同,其中OpenGL中的NDC是左手坐标系,位于坐标范围在(-1,-1,-1)至(1,1,1)的立方体中 顶点着色器需要完成的工作主要有:坐标变换(MCS - WCS - VCS - CS)和逐顶点光照。当然,除了这两个主要任务外,顶点着色器还可以输出后续阶段所需的数据。图展示了在顶点着色器中对顶点位置进行坐标变换并计算顶点颜色的过程。
【GPU在每个输入的网格顶点上都会调用顶点着色器。顶点着色器必须进行顶点的坐标变换,需要时还可以计算和输出顶点的颜色。例如,我们可能需要进行逐顶点的光照】 顾名思义,就是对顶点的坐标(即位置)进行某种变换。顶点着色器可以在这一步中改变顶点的位置,这在顶点动画中是非常有用的。例如,我们可以通过改变顶点位置来模拟水面、布料等。但需要注意的是,无论我们在顶点着色器中怎样改变顶点的位置,一个最基本的顶点着色器必须完成的一个工作是,把顶点坐标从模型空间转换到齐次裁剪空间。想想看,我们在顶点着色器中是不是会看到类似下面的代码:o.pos = UnityObjectToClipPos(v.position); 类似上面这句代码的功能,就是把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(Normalized Device Coordinates , NDC)。图展示了这样的一个转换过程。 【顶点着色器会将模型顶点的位置变换到齐次裁剪坐标空间下,进行输出后再由硬件做透视除法得到NDC下的坐标】 需要注意的是,上图图给出的坐标范围是OpenGL同时也是Unity使用的NDC,它的z分量范围在[-1,1]之间,而在DirectX 中,NDC的z分量范围是[0,1]。顶点着色器可以有不同的输出方式。最常见的输出路径是经光瓯化后交给片元着色器进行处理。而在现代的ShadetModel中,它还可以把数据发送给曲面细分着色器或几何着色器
顶点着色器用来处理输入的顶点数据,主要用来进行顶点坐标变换以及顶点着色。我们知道,从输入的顶点局部坐标到最终的屏幕坐标需要经过一系列的坐标变换,才能最终显示到屏幕上。下面的图片展示了顶点坐标的一系列变换过程。我们从建模工具得到的是物体的局部坐标(Local Coordinate),局部坐标通过模型矩阵Model变换到世界坐标(World Coordinate),世界坐标通过观察矩阵View变换到观察坐标(View Coordinate),观察坐标经过投影矩阵Projection变换到裁剪坐标(Clip Coordinate),裁剪坐标经过透射除法(Perspective Division)得到标准设备空间(Normalized Device Coordinates,NDC),NDC坐标通过视口变换(Viewport Transformation)变换到窗口坐标进行显示。看到这么多变换,是不是一下子有点懵了!我们接下来看下每个变换都做了什么
我们知道,光照计算一般都是在世界空间进行的,所以输入的顶点坐标需要通过乘以模型矩阵变换到世界空间。如果物体变换有非均匀缩放,那么在变换法线时就要注意了。我们不能简单的通过乘以模型矩阵来将法线变换到世界空间。下图展示了法线变换可能产生的问题。如果只是存在平移变换(Translation)我们无需对法线进行变换;如果只存在平移和旋转变换(Rotation)我们只需要乘上渲染矩阵;如果存在非均匀缩放变换(Scaling)我们需要使用矩阵的逆的转置来变换法线。关于该过程的推导可以参考OpenGL Normal Vector Transformation这篇文章。
虚拟摄像机定义了我们的观察空间。世界空间和观察空间的关系如下所示,虚拟摄像机的位置是坐标的原点,观察方向沿着Z轴的负方向。我们可以通过摄像机的位置EyePosition、观察目标点FocusPosition和向上的方向向量UpDirection来构建观察矩阵。OpenGL和DIrectX都有对应的API。该方法的实现比较简单,只需要通过两次向量的叉乘就可以构建该矩阵
介绍裁剪空间之前,我们需要先来看一个重要的概念:视椎体(Frustum)。视椎体可以通过上下左右远近六个平面来定义。我们通过投影矩阵将物体从观察空间变换到裁剪空间,裁剪空间是一个以原点为中心的立方体,不在该裁剪空间的图元都会被裁剪。根据投影方式的不一样,我们可以定义不同的投影矩阵,常见的投影方法有:正交投影和透视投影。两种不同投影对应的视椎体如下图所示。我们可以看到正交投影的视椎体是长方体,而透视投影的视椎体是台体。我们可以通过近平面(Near)、远平面(Far)、垂直视场角(Vertical Field of View, FOV)和屏幕纵横比(Aspect Ratio,也叫作屏幕宽高比)四个参数来定义视椎体 正交投影又叫平行投影。投影视椎体是一个长方体,物体在投影平面的大小与距离远近没有关系。在OpenGL中我们可以通过glm:ortho() 这个函数来创建一个正交投影矩阵。正交投影其实是使用如下所以的GL_PROJECTION矩阵进行变换。其中,变量r、l、t、b、n和f 是视椎体的上下左右远近平面的边界变量。通过正交矩阵变换后,我们得到了裁剪空间。建筑蓝图绘制和计算机辅助设计需要使用到正交投影,因为这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。正交投影的示意图如上图右图所示
根据我们的生活经验我们会发现这样的现象,离你越远的物体看起来越小,随着距离的增大,最终会消失在视野中,成为灭点。为了实现这种近大远小的效果,我们需要引入透视投影。在OpenGL中我们可以通过glm:perspective()这个函数来创建一个透视投影矩阵。投影变换使用的是齐次坐标,因为在透视除法阶段需要将XYZ的值除以W分量来获取NDC坐标空间。透视除法可以实现近大远小的视觉效果,该过程由硬件自动执行。这也是正交变换和透视变换最主要的区别,我们在后面会进行具体讨论。下图所示的矩阵是透视投影矩阵,和正交投影一样都是用来进行坐标空间变换的。透视投影示意图如上图左图所示。
由于我们在坐标变换过程中涉及到了很多矩阵:Model、View、Projection。我们可以通过矩阵的乘法将它们联系起来,得到从局部空间到裁剪空间的坐标变换矩阵,这里需要注意矩阵的运算顺序是从右到左。
根据光照计算的不同,可以将着色方式分为平面着色、高洛德着色和冯氏着色。其中,最简单的着色方式就是平面着色。平面着色是使用一个顶点颜色来代表整个三角面的颜色,默认是使用索引中第一个顶点的颜色,例如,第一个顶点是红色,那么整个三角面都是红色的。上图最左边的图像就是利用平面着色渲染得到的,我们可以看到平面着色几乎无法展示高光效果。 高洛德着色就是在顶点着色器中计算顶点的光照信息,所以也叫作逐顶点着色。高洛德着色是平面着色和冯氏着色的折中方案,该方法会在顶点着色器中计算三个顶点的光照信息,然后在光栅化阶段插值得到三角形内部各个片段的光照信息。上图中间的图像就是利用高洛德着色渲染得到的,我们可以看到在高光部分高洛德着色表现的并不是很令人满意,原因在于高光并非是线性变换的,所以通过插值得到的效果比较差。 冯氏着色就是在片段着色器中计算每个片段的光照信息,所以也叫作逐片段着色。冯氏着色是三种着色方式中效果最好的,也是性能消耗最大的着色方式。冯氏着色会在根据输入的顶点法线信息在光栅化阶段插值得到各个片段的法线信息,然后在片段着色器中利用法线、纹理坐标、位置等信息计算每个片段的光照信息。开销最大,同时效果也是最好的,我们可以从上图右边的图像看到冯氏着色渲染得到的,通过对比高洛德着色和冯氏着色的高光部分,我们看发现冯氏着色处理得更加自然真实。 从上面的讨论我们发现:这三种着色方案中平面着色效果最差(计算量最少),高洛德着色其次(计算量其次),冯氏着色最好(计算量最多)。我们需要根据实际的需求选择对应的着色方式,需要在画面效果和性能开销之间进行取舍。
(3)曲面细分着色器(Tessellation Shader)曲面细分是利用镶嵌化处理技术对三角面进行细分,以此来增加物体表面的三角面的数量,是渲染管线一个可选的阶段。它由外壳着色器(Hull Shader)、镶嵌器(Tessellator)和域着色器(Domain Shader)构成。我们不创建高模(high-poly)来丰富网格信息主要是考虑到以下几个原因: 第一,基于GPU可以实现动态的LOD技术,可以根据物体距离摄像机的远近来调整多边形网格的细节,比如说,若物体距离摄像机比较远,则按照高模的规格对它进行渲染会造成浪费,因为我们根本看不清网格的所有具体细节。随着物体和摄像机之间距离的拉近,我们可以实现连续镶嵌化处理,增加物体的细节; 第二,节省内存。我们可以在各种存储器中保存低模网格信息,再根据需求用GPU动态地增加物体表面的细节。 延伸阅读
曲面细分着色器是如何工作的? 曲面细分着色器又被译为镶嵌着色器,它是一个可编辑着色器,在显卡上的一个名为**视口变换器(Viewport Transform)**的硬件模块上运行实现,一个显卡上可能有1~4个视口变换器集成电路,它们有些是可编程的有些是不可编程的,在具有GPC结构的显卡中,视口变换器一般位于GPC外
视口变换器可以从L2缓存中获取它需要的信息,并将L2缓存作为它与显存沟通的桥梁。
曲面细分着色器将复杂的曲面转换为简单的点、线、三角形。曲面细分着色器可以递归的增加网格细度,并在细分后的顶点上生成插值色彩。由于它处理了邻接顶点的信息,它处理的结果会更加平滑,而不会产生跳跃间隙。
通过细分,模型外观会更加平滑,且颜色的过度也会更加自然。
在比较老旧的API版本中曲面细分着色器由硬件实现无法编程。在DirectX11以上或OpenGL4.x以上的版本中,可以通过API编辑这个着色器的工作任务
(4)几何着色器(Geometry Shader)几何着色器(Geometry Shader)属于渲染管线的一个可选阶段,位于曲面细分(Tessellation)和光栅化(Rasterization)之间。顶点着色器以顶点数据作为输入数据,而几何着色器则以完整的图元(Primitive)作为输入数据。例如,以三角形的三个顶点作为输入,然后输出对应的图元。与顶点着色器不能销毁或创建顶点不同,几何着色器的主要亮点就是可以创建或销毁几何图元,此功能让GPU可以实现一些有趣的效果。例如,根据输入图元类型扩展为一个或更多其他类型的图元,或者不输出任何图元。需要注意的是,几何着色器的输出图元不一定和输入图元相同。几何着色器的一个拿手好戏就是将一个点扩展为一个四边形(即两个三角形)。几何着色器通常用来实现一种叫做公告栏(BillBoards)的视觉效果,几何着色器的一些应用如下图所示。
延伸阅读
几何着色器是如何工作的? 几何着色器是一个可选着色器,也在视口变换器上实现 几何着色器位于顶点和片元着色器之间,它以一个或多个顶点或三角形等基本图形作为输入,输出另外的一个顶点序列或三角形序列,以此改变图形的形状。仅仅是改变形状那它的作用就和顶点着色器没区别了,几何着色器最大的特点是能改变顶点的数量。对于每一个输入,几何着色器可以返回0到40个输出,但如果一个输入对应的输出数大于27,性能会明显的下降。
以HLSL为例,典型的几何着色器定义如下: 函数的第一个参数类型决定了可以输入的基本图形,它们包括: point:每批处理一个顶点,声明格式为point v2g input[1]
line:每批处理一条线端上的两个顶点,声明格式为line v2g input[2]
triangle:每批处理一个三角形上的三个顶点,声明格式为triangle v2g input[3]。最常用的输入类型就是triangle
lineadj:每批处理一条线端及其两端的两条线端,共三条线端四个顶点,声明格式为lineadj v2g input[4]
triangleadj:每批处理一个三角形及其邻接的三个三角形,共四个三角形六个顶点,声明格式为triangleadj v2g input[6]
函数的第二个参数决定了几何着色器的输出类型,它们可以是TriangleStream、LineStream和PointStream,即HLSL中的流输出对象类型(Stream-Output Object),它们分别对应了3个、2个和1个顶点。
通过标签[maxvertexcount(N)]来定义几何着色器单次调用输出的最大顶点个数,这个输出顶点个数应当是输出类型对应的顶点数的整数倍。在几何着色器中,通过使用outstream.Append(o)来将一个g2f顶点输出,每次调用几何着色器的输出个数应当等于maxvertexcount定义的顶点个数。如果在运行过程中想要丢弃本次运算输出的顶点,在return前使用outstream.RestartStrip()来保证输出流的格式正确。 (5)图元组装(Primitive Assembly)图元组装将输入的顶点组装成指定的图元。图元组装阶段会进行裁剪和背面剔除相关的优化,以减少进入光栅化的图元的数量,加速渲染过程。在光栅化之前,还会进行屏幕映射的操作:透视除法和视口变换。 关于透视除法和视口变换到底属于流水线的那个阶段并没有一个权威的说法,某些资料将这两个操作归入到图元组装阶段,某些资料将它归入到光栅化过程,但对我们理解整个渲染管线并没有太大的影响,我们只需要知道在光栅化前需要进行屏幕映射就可以了,所以我们这里将屏幕映射放到了图元组装过程。这两个操作主要是硬件实现,不同厂商会有不同的设计
只有当图元部分或全部位于视椎体内时,我们才会将它送到流水线的下个阶段,也就是光栅化阶段。而完全位于视椎体外部的图元会被裁剪掉,不会对它们进行渲染。对于部分位于视椎体的图元,我们需要对他们进行所谓的剪裁操作,例如,线段的两个顶点一个位于视椎体内而另一个位于视椎体外,那么位于外部的顶点将被裁剪掉,而且在视椎体与线段的交界处产生新的顶点。通过之前顶点着色器的投影变换后,视椎体是一个立方体,这有利于我们进行裁剪操作,使得该操作变得更加容易和一致。这里我们顺便说下帧缓存中的裁剪,也就是说我们可以将裁剪操作推迟到屏幕坐标中进行,使用一种叫做裁切(Scissoring)的技术在帧缓存中执行裁剪操作。不过,对于几何实体一般在帧缓存之前进行裁剪更好一些,帧缓存中的裁剪一般只适合于光栅对象,比如像素块 理解二: 由于我们的场景可能会很大,而摄像机的视野范围很有可能不会覆盖所有的场景物体,一个很自然的想法就是,那些不在摄像机视野范围的物体不需要被处理。而裁剪(Clipping)就是为了完成这个目的而被提出来的。
一个图元和摄像机视野的关系有3种:完全在视野内、部分在视野内、完全在视野外。完全在视野内的图元就继续传递给下一个流水线阶段,完全在视野外的图元不会继续向下传递,因为它们不需要被渲染。而那些部分在视野内的图元需要进行一个处理,这就是裁剪。例如,一条线段的一个顶点在视野内,而另一个顶点不在视野内,那么在视野外部的顶点应该使用一个新的顶点来代替,这个新的顶点位于这条线段和视野边界的交点处。
由于我们已知在NDC下的顶点位置,即顶点位置在一个立方体内,因此裁剪就变得很简单:只需要将图元裁剪到单位立方体内。图2.9展示了这样的一个过程。 【图2.9只有在单位立方体的图元才需要被继续处理。因此,完全在单位立方体外部的图元(红色三角形)被舍弃,完全在单位立方体内部的图元(绿色三角形)将被保留。和单位立方体相交的图元(黄色三角形)会被裁剪,新的顶点会被生成,原来在外部的顶点会被舍弃】 延伸阅读
裁剪阶段是如何工作的? 裁剪也由视口变换器实现,但它完全不可编辑。 GPU会将完全留在视野范围内的三角形保留,将完全在视野范围外的三角形抛弃,将部分留在视野范围内的三角形修正为新的几何体,即生成新的顶点,抛弃原来在视野外的顶点
背面剔除指的是剔除那些背对摄像机的图元,如下图所以,图元t1背向摄像机,需要被剔除,而图元t2需要被保留。我们利用三角形顶点的环绕顺序(Winding Order)来确定所谓的正面(front-face)和背面(back-face)。通常情况下,三角形的3个顶点是逆时针顺序(couter-clockwise,ccw)进行排列时,我们会认为是正面,而顺时针(clockwise,cw)排序时,我们会认为是背面。例如t2的三个顶点顺序为逆时针的(v1,v2,v3),所以是正面,需要保留。将t1和t2投影到XY平面后,我们可以清楚的看到它们顶点的环绕顺序。 从上图可以看到,这里我们可以使用行列式(determinant)来确定投影后的2D三角形到底是CW还是CCW顺序。行列式的第一行由顶点v1和v2坐标确定,而第二行由顶点v1和v3坐标确定。如果行列式的值为负数,那么该三角面是背面朝向;如果为正数,则是正面朝向。 背面剔除的技术默认是不开启的。我们可以通过glEnable(GL_CULL_FACE)函数来开启背面剔除的优化。开启后,我们还可以通过glCullFace()函数来配置剔除的是正面还是背面。参数为GL_FRONT、GL_BACK(默认值)和GL_FRONT_AND_BACK。虽然背面剔除可以大概减少50%的渲染图元,但是在渲染半透明或不透明物体时,不能使用该技术,否则会出现穿帮的情况,因为半透明或不透明物体可以看到物体背后的东西。 这一步输入的仍然是三维坐标系下的坐标,这一阶段是不可配置和编程的阶段,它负责把每个图元的x、y坐标从(-1,1)范围转换到屏幕的坐标系中(屏幕坐标系是一个二维的坐标系,这个坐标系和我们用于显示画面的分辨率有很大的关系)。屏幕映射得到的坐标决定了这个顶点对应屏幕上的哪个像素及距离这个像素有多远。 我们把场景渲染到一个窗口上,窗口的范围是从最小窗口坐标(x1,y1)窗口左下角到嘴的窗口坐标(x2,y2)窗口右上角,其中x1<x2且y1<y2。由于我们输入的坐标范围是在(-1,1)之间的,因此屏幕映射的过程实际上是一个缩放的过程。需要注意屏幕映射不会对z坐标进行任何的处理。而实际上屏幕坐标和z坐标一起构成了一个坐标系——窗口坐标系。这些值会一起被传递到光栅化阶段。 【屏幕映射将x、y坐标从(-1,1)范围转到屏幕坐标系中】 屏幕映射得到的屏幕坐标决定了这个顶点对应屏幕上哪个像素以及距离这个像素由多远 有一个需要引起注意的地方是,屏幕坐标系在OpenGL 和 DirectX之间的差异问题。OpenGL把屏幕的左下角当成最小的窗口坐标值,而 DirectX则定义了屏幕的左上角为最小的窗口坐标值。图2.11显示了这样的差异。 【OpenGL和 DirectX的屏幕坐标系差异。对于一张512*512大小的图像,在OpenGL 中其( 0,0)点在左下角,而在 DirectX中其(0,0)点在左上角】 产生这种差异的原因是,微软的窗口都使用了这样的坐标系统,因为这和我们的阅读方式是一致的:从左到右、从上到下,并且很多图像文件也是按照这样的格式进行存储的。 不管原因如何,差异就这么造成了。留给我们开发者的就是,要时刻小心这样的差异,如果你发现得到的图像是倒转的,那么很有可能就是这个原因造成的。 理解二: 只有(已经裁剪的)图元在视锥体内部的才会传递给屏幕映射阶段,并且这一步的坐标仍然是三维的。每个图元的x轴和y轴的坐标会被转换到屏幕坐标screen coordinates。屏幕坐标和z轴的值也合称为窗口坐标windows coordinates。假设一个场景要被渲染到一个以(x1,y1)和(x2,y2)为对顶点的窗口上,x1<x2 y1<y2。然后屏幕映射就是一个缩放操作。新的x和y值叫做屏幕坐标。z坐标(OpenGL是[-1,+1]DirectX是[0,1])也会被映射到[z1,z2],默认值z1=0 z2=1。但是这些会随着API而改变。窗口坐标和映射后的z值会被传递给光栅化阶段。屏幕映射的过程在图2.7中描述。
【经过投影变化后的图元位于便准立方体内,屏幕映射过程负责寻找出现在屏幕上的坐标】延伸阅读
屏幕映射是如何工作的? 屏幕映射紧跟在裁剪阶段之后,也由视口变换器负责,不可编辑。
屏幕映射简单来说就是将NDC中的坐标转换到屏幕坐标系(Screen Coordinate)。所谓屏幕坐标系,就是将范围在(-1,1)的x和y轴坐标,缩放到与目标分辨率相同大小,而z坐标则不做处理。注意,目标分辨率不一定等同于屏幕分辨率。这个阶段将输出屏幕坐标系下的顶点坐标、顶点深度、顶点法线方向、视角方向等顶点属性。
在OpenGL中,屏幕的左下角为屏幕坐标系原点,右上角为坐标最大值。而在DirectX中,屏幕左上角为最小屏幕坐标,而右下角为最大屏幕坐标。这个差异是OpenGL和DirectX很多不兼容性产生的源泉 - 透视除法 (Perspective Division)
上面的流程图展示了顶点数据的变换过程。从前面讨论的顶点变换我们知道:经过模型矩阵Model、观察矩阵View和投影矩阵Projection变换后,局部空间被变换到了裁剪空间(-Wc <= XYZ <= Wc)。执行透射除法后(除以w分量),我们可以得到标准设备空间,该空间一般也称作标准视体(Canonical View Volume,CVV)。执行透视除法是为了实现透射投影中近大远小的视觉效果,经过了投影矩阵Projection的变换后,W分量保留了观察空间中物体Z坐标的信息,所以透视除法才能够根据距离摄像机的远近正确实现透视效果。我们注意到透射除法是由硬件自动执行的,也就是说透视除法在正交投影和透视投影中都会被执行,只不过正交投影变换并没有改变W分量的值(W分量的值仍是1),所以透视除法并没有实际的效果。我们从这里也明白了使用齐次坐标的意义,其实就是为了正确记录下投影变换前(观察空间)中物体的深度信息,也就是Z坐标的值。
如果对坐标变换、投影变换、齐次坐标以及透视除法不是很清楚的话,可以参考我之前翻译的这两篇文章:OpenGL变换和OpenGL投影矩阵 - 视口变换 (Viewport Transform)
通过透视除法后,我们得到了NDC坐标,获得NDC坐标是为了实现屏幕坐标的转换与硬件无关。经过视口变换后,我们可以得到窗口坐标(Window Coordinates)。除了窗口坐标,还有屏幕坐标(Screen Coordinates)。一般来说,屏幕坐标是2D的概念,只用于表示屏幕XY坐标,而窗口坐标是2.5D的概念,它还带有深度信息,也就是经过变换后的Z轴的信息。我们可以用glViewport()进行视口变换。Z坐标的数值在OpenGl和DirectX中略有不同,Z坐标在OpenGL中会被映射到[-1,1],在DirectX中会被映射到[0,1],我们使用glDepthRange()来进行深度映射。NDC坐标映射到窗口坐标需要经过平移和缩放两个变换,视口变换矩阵如下所示,其中width、height、farVal和nearVal分别表示视口的宽高远近。关于视口矩阵的推导可以参考YouTube的这个视频。 https://www.youtube.com/watch?v=puSWkVXLoQA 从上面视口变换的示意图我们注意到屏幕空间和Viewport其实是不一样的。我们可以通过glViewport()来设定视口的坐标和宽高。如果视口小于屏幕空间,那么会造成多余的像素被渲染。例如,glClear()会为整个屏幕空间设定指定颜色值。我们可以通过裁切测试 (Scissor Test)来指定渲染的区域,避免上面出现的渲染浪费的问题。我们会在后面的内容具体讨论裁切测试这种技术。 与投影变换和视口变换相反的一种变换是:拾取(Picking)。也就是根据屏幕坐标反算出对应的3D对象。我们需要做逆于投影视口变换的操作,将屏幕坐标变换到3D坐标。拾取变换的过程如下所示: - 通过视口变换矩阵逆矩阵将屏幕坐标变换到NDC坐标
- 然后通过乘以W分量(投射除法的逆变换)将NDC坐标变换到裁剪坐标
- 通过投影矩阵逆矩阵将裁剪坐标变换到观察坐标
- 求出经过原点O以及点的拾取射线
- 拾取射线位于观察空间,通过将拾取射线变换到局部空间进行相交行检测 (这里将拾取变换到局部空间是为了减少运算量,将物体的每个图元变换到世界坐标效率较低)
原帖地址:计算机图形渲染管线/几何阶段【TA技术美术必修基础知识篇】(第二期/共四期) (qq.com)
|