大家好,又见面了,我是你们的朋友全栈君。
介绍
阴影以前只是一个变暗的纹理,通常是圆形的形状,它被投射到游戏中的字符或对象之下的地板上。一个人必须不知情或天真地认为,我们仍然可以在未来的3D游戏中摆脱这种粗暴的“黑客”。曾经是一个时间,阴影太贵了,无法实时渲染,但随着图形硬件的不断增加的力量,未能提供适当的阴影不再意味着平庸的实现,它接受犯罪罪未充分利用可用的图形硬件。
有许多不同的阴影技巧和方法来实施阴影,打下一个“最好的”解决方案是困难的。为了了解所有的方法,欣赏他们的差异,优势和劣势,我强烈建议您阅读任何有关3D影像的内容。我们不应该限制自己只是研究阴影体积技术; 任何影像技术都值得一看。[13]第6章对大多数已知的影像技术进行了精彩的高级别讨论。为了限制本文的范围,我们将仅讨论使用Microsoft的Direct3D API的模板阴影卷的理论和实现问题。这也是很好的理解,模具阴影体积只是不是“结束所有”阴影技术。
参考游戏设置,可以在[4]中找到关于不同阴影技术的优势的讨论。刚刚,Eric Lengyel [11]还在Gamasutra网站上提供了一个关于在OpenGL中实现阴影卷的非常完整的文章[17]。Lengyel的文章的数学推导可以在[12]中找到。回溯几年,有着名的“Cameack On Shadow Volumes”文本文件[6],
这不仅仅是来自John Carmack的id Software的电子邮件,Nvidia的Mark Kilgard关于深度故障阴影卷的推导实现。有趣的是,卡马克独立地发现了深度失败的方法,而比尔比洛德和麦克·松y [7]也提出了类似的阴影卷方法。因此,深度失败方法现在通常被称为“Carmack的反向”。
模具阴影卷概念
弗兰克·乌鸦[8]首先提出了在1977年使用阴影卷影的想法。Silicon Graphics的Tim Heidmann [5]通过在IRIX GL中狡猾地利用模板缓冲区进行阴影体积计数来实现了Crow的阴影体积。让我们来看看原始模具阴影卷技术的工作原理。
按照常规惯例,投射阴影的场景中的任何对象都称为封闭器。如上图1所示,我们有一个简单的二维视图(自上而下)的场景,一个球体作为闭塞器。球体右侧的矩形是影子接收器。为了简单起见,我们不考虑矩形创建的阴影卷。阴影区域表示由封堵器创建的2D中的阴影体积。阴影体积是将剪影边缘从光源的视点挤出到有限或无限远的结果。
图2示出了从光源的观察位置产生的球体的可能轮廓。轮廓简单地由每个由两个顶点组成的边缘组成。然后将这些边沿如源自光源的虚线箭头所示的方向挤压。通过挤出剪影边缘,我们有效地创建了阴影卷。在这个时间点应该注意,阴影体积挤出对于不同的光源是不同的。对于点光源,轮廓边缘精确地挤出点。对于无限定向光源,轮廓边缘拉伸到单个点。我们将介绍确定轮廓边缘和影子卷创建的细节。挤出的大小可以是有限的或无限的。从而,
图3显示了场景中玩家的众多可能的观看方向。箭头末尾的数字是渲染阴影卷后留在模版缓冲区中的值。具有非零模板值的片段被认为是阴影。在模板缓冲区中生成值是以下模板操作的结果:
- 渲染阴影卷的正面。如果深度测试通过,增加模板值,否则不执行任何操作。禁止绘制到帧和深度缓冲区。
- 渲染阴影卷的背面。如果深度测试通过,减少模板值,否则不执行任何操作。禁止绘制到帧和深度缓冲区。
上述算法也被称为深度通过模板阴影卷技术,因为我们仅在深度测试通过时操纵模板值。深度通常也被称为z-pass。
假设在上述模板操作之前,我们已经将对象渲染到帧缓冲区上。这意味着如果您喜欢,深度缓冲区将被设置为深度测试或z测试的正确值。来自眼睛位置的2个最左侧的光线不会影响阴影体积的任何部分(灰色),因此所得到的模板值为0,这意味着由该两条光线表示的片段不在阴影中。现在让我们从左边跟踪第三条光线。当我们渲染阴影卷的正面时,深度测试将通过,模板值将增加到1.当我们渲染阴影卷的背面时,深度测试将失败,因为阴影卷的背面位于封堵器后面。因此,由该光线表示的片段的模板值保持为1.这意味着片段处于阴影中,因为它的模板值不为零。
阴影卷计数是否适用于多个影子卷?是的,它确实。阴影卷计数是否适用于多个影子卷?是的,它确实。阴影卷计数是否适用于多个影子卷?是的,它确实。
上面的图4显示,即使对于多个相交的阴影卷,使用模板缓冲区的计数仍然可以工作。
有限体积与无限量
参考图1,您可以看到阴影体积应该拉伸到无穷大。这实际上并非严格要求。我们将阴影体积发送到无限远,以避免光源非常接近封堵器的尴尬局面。
在靠近物体A的光线上,有限的阴影体积可能不足以到达物体B.从眼睛到物体B的射线将以片段模板值0结束,实际上应该是非零!无限阴影卷将确保无论物体对闭塞器的接近程度如何,所产生的阴影卷将覆盖场景中的所有对象。我们将讨论如何将顶点拉伸到无限远。
Carmack的反向
为什么John Carmack,Bill Bilodeau和Mike Songy甚至打扰他们的头脑,出来一个替代的模板算法,因为深度传递技术似乎很好吗?深度传递真的很好,至少大部分时间。但是当眼睛点进入阴影体积时,所有的地狱都会松动。
如上图6所示,当眼点在阴影体积内时,深度通过技术完全失败。这意味着我们不可能有那么大的坏角子的收割者从你身后偷偷摸摸,而在他阴影的扩大的黑暗中吞没你。约翰·卡马克永远不会这样!以下是深度失败(也称为卡马克反向)算法:
- 渲染阴影卷的背面。如果深度测试失败,则增加模板值,否则不执行任何操作。禁止绘制到帧和深度缓冲区。
- 渲染阴影卷的正面。如果深度测试失败,减少模板值,否则不执行任何操作。禁止绘制到帧和深度缓冲区。
深度失败通常也称为z-fail。图7显示了即使眼点处于阴影中,深度失效技术也能正常工作。如果您考虑眼睛位置在阴影体积之外的场景,则深度失效技术也应该起作用。但是,真的,在某些情况下失败了。我们将尽快讨论这些情况; 只要记住,深度传递和深度失败技术都不完美。事实上,我们需要组合不同的方法来为影子卷提供强大的解决方案。[11]和[10]包含了一些关于强大的模板阴影体积解决方案的很好的讨论。
限制深度失败
为了将非零值放入模板缓冲区,深度故障技术取决于渲染阴影卷的失败“ 相对于眼睛位置的背面。这意味着阴影卷必须是封闭的卷; 阴影体积必须在前端和后端都加盖(即使后端处于无限远)。没有封顶,深度失效技术会产生错误的结果。令人惊讶的是,它可能听起来,但是,即使在无穷大的情况下,也可以遮住阴影体积。
如图8所示,前盖和后盖(粗线)创建一个闭合阴影卷。前盖和后盖都被认为是从两个眼睛位置的背面。使用深度故障模板操作,上限将创建正确的非零模板值。有几种方法可以创建前盖和后盖。Mark Kilgard [2]描述了创建前盖的一种不寻常的方法。该方法基本上涉及将闭塞器的背面几何形状投影到近夹子平面上,并使用这些几何形状作为前盖。或者,我们可以通过相对于光源重新使用正面三角形来构建前盖。然后可以将前盖中使用的几何形状挤出,并将其顺序颠倒,以产生后盖。反转排序是为了确保后盖从阴影卷向外。事实上,我们必须始终确保,在我们的例子中,定义整个阴影卷的三角形的图元是向外的,如图9所示。必须注意的是,渲染封闭的阴影卷比使用深度传递更昂贵阴影体积上限。除了阴影卷的较大的原始数量外,还需要额外的计算资源来计算前盖和后盖。我们会尽快详细介绍一下封影影子的细节。必须注意的是,渲染闭合阴影卷比使用深度传递而没有阴影卷封顶要贵一些。除了阴影卷的较大的原始数量外,还需要额外的计算资源来计算前盖和后盖。我们会尽快详细介绍一下封影影子的细节。必须注意的是,渲染闭合阴影卷比使用深度传递而没有阴影卷封顶要贵一些。除了阴影卷的较大的原始数量外,还需要额外的计算资源来计算前盖和后盖。我们会尽快详细介绍一下封影影子的细节。
把它
放在一起我们来整理我们已经学到的东西,并尝试在我们处理所讨论的技术的所有缺陷之前,提出所有需要的步骤去做模具阴影卷。实施模板阴影卷的步骤的一般列表将是:
- 使用环境照明和任何其他表面阴影属性渲染所有对象。渲染不应该依赖于任何特定的光源。确保深度缓冲区被写入。
- 从光源开始,清除模板缓冲区,并计算所有封堵器相对于光源的轮廓。
- 将剪影从光源拉出到有限或无限的距离,以形成阴影卷,并且如果使用深度失效技术,则生成封盖。(无限阴影体积挤压并不是强制性的)
- 使用所选技术渲染阴影卷。深度差或深度失败。
- 使用更新的模板缓冲区,执行对应于非零模板值的碎片的照明通道(使其变暗)。
- 对场景中的所有灯重复步骤2到5。
从上面的步骤列表中,应该显而易见的是,有更多的灯光意味着有更多的通过,这可以在你的帧率口袋里烧一个漂亮的洞。事实上,我们必须非常有选择性地决定哪些灯应用于投射阴影。文章[4]对由多个光源照亮的场景中选择阴影投射灯进行了很好的讨论。想象一下,您的游戏角色站在体育场的中间,四个巨大的电池照亮了现场。由于4个不同方向的阴影投射,应该至少有4个阴影的游戏角色在地板上形成十字。在这里只选择1个光源将使场景看起来很奇怪。拥有多个灯光可以让您获得漂亮逼真的柔和阴影,但还有其他方式可以伪造,而不需要使用多个光源。软阴影是一个巨大的话题,不在本文的范围内,所以让我们从这里放下吧。经验法则:始终选择场景中的主要光源。使用观察平截头体来选择光源可能非常危险,因为您的头顶部可能会有一个不错的巨大的1000兆瓦光子破坏点。这不是在你的视野中,但它将对您将在现场看到的最明显的阴影负责。只要记住,灯光数量越少,循环次数越多,渲染速度越快,可以节省其他视觉上更重要的效果。所以小心选择!
从即将推出的Doom3引擎的屏幕截图,我估计id软件将限制任何场景中的阴影投射灯的数量,最多可以说是4或5.那么我们会知道Doom3在明年打架。
剪影确定
构建阴影卷的第一步是确定封堵器的轮廓。模板阴影算法要求闭塞器闭合三角形网格。这意味着模型中的每个边缘只能由2个三角形共享,因此不允许任何会暴露模型内部的孔。我们只对面向光源的三角形共享的边缘和面向远离光源的另一个三角形感兴趣。有很多方法来计算轮廓边缘,这些方法中的每一个都是CPU周期饥饿的。假设我们正在使用索引的三角形网格。
图10示出了由具有一致的逆时针绕组的四个三角形组成的盒子的一侧。虚线表示冗余的内部边缘,因为我们只对形成框的轮廓的实线感兴趣。冗余内部边缘被两个三角形共享的索引两次。我们利用这个属性来提出一种确定轮廓边缘的简单方法。
- 循环遍历所有模型的三角形
- 如果三角形面向光源(点积> 0)
- 将三个边(一对顶点)插入边缘堆叠
- 检查每个边缘的上一次发生,或者在堆叠中反向
- 如果在堆叠中找到边或反向,则删除两个边
- 以新的三角形开始
上述算法将确保内部边缘将最终从堆栈中移除,因为它们被多于一个三角形索引。
Eric Lengyel [11]提出了另一种轮廓确定算法,利用一致的绕组(逆时针)顶点。该方法需要在模型的所有三角形上进行2遍,以便在三角形对共享的所有边缘中进行过滤。所得到的边缘列表然后进行点积运算,以获得由面向三角形和不面向光的三角形共享的边。
重要的是要注意,轮廓确定是模具阴影体积执行中两个最昂贵的操作之一。另一个是阴影卷渲染通过更新模板缓冲区。这两个领域是积极优化的主要候选者,我们将在本文的结尾部分详细讨论。
生成阴影体积上限
请记住,阴影体积上限仅适用于深度失败技术。进行阴影体积上限的目的是确保我们的阴影体积关闭,即使在无限远时也必须关闭阴影体积。有趣的是,点光源和无限定向光源的几何挤压是不同的。点光源将拉伸剪影边缘精确地指向点,而无限定向光源将所有剪影边缘拉伸到无限远的单个点。这意味着阴影体积的背封对于无限定向光源来说将是多余的,因为它已经关闭。
产生前盖和后盖的理想时间将是在轮廓生成期间,因为我们已经产生了光矢量和边缘之间的角度。对于前盖,我们只需要复制所有正面几何,并使用这些几何形状进行挤压以形成背盖。请注意,后盖仅适用于点光源。
图11示出了使用不同几何形状来关闭阴影体积的两组图像。第一行描绘了由正面和背面覆盖重复使用的几何形状的光形成的封闭阴影体积。第二行显示了一个封闭的阴影体积,前盖具有重复使用面向封堵器几何形状的光和由挤压轮廓边缘构成的三角形风扇后盖。应该使用三角形风扇后盖,因为它会导致较少的几何形状,因此需要更少的内存和渲染时间。当重复使用封堵器的正面几何形状时,我们应该非常小心渲染阴影体积,因为阴影体积的前盖几何体与封堵器的正面几何形状物理共面。多数情况下,精确问题将导致阴影卷的前盖几何形状呈现在封堵器前面几何形状的前面,导致整个封堵器被吞入其自身的阴影体积。我们可以利用的D3DRS_ZBIAS在Direct3D的标志D3DRENDERSTATETYPE迫使封堵器的前端面的几何形状,它的影子量前盖的面前呈现。只需在设置渲染状态时使用D3DRS_ZBIAS标志(例如pd3dDevice-> SetRenderState(D3DRS_ZBIAS,value))。我们将标志值设置为更高的封闭器几何形状值,其阴影卷的值越小。这将确保阴影卷的前盖呈现在封堵器前面几何的后面。
将几何挤压到无穷远
如前所述,我们需要将轮廓边缘拉伸到无穷远,以避免图5所示的情况,其中有限的阴影体积挤压不能覆盖场景中的所有阴影接收器。然而,如果我们能够确保图5中的情况从来没有发生在我们的场景中,那么将剪影边缘拉伸到无穷远是不是必须的。在实际的情况下,一个很大的价值通常是不够的。
Mark Kilgard [2]介绍了使用均匀坐标的w值渲染半无限顶点的技巧。在4D同构坐标中,我们用(x,y,z,w)表示一个点或向量,其中w是第四个坐标。对于点,w等于1.0。对于向量,w等于0.0。均匀符号对于转换两个点和向量非常有用。由于翻译只对点而不是向量有意义,所以w的值在仅转换点而不是顶点上起重要作用。这可以容易地推断,因为变换矩阵的平移值是在任一4 个列或4 个根据所述矩阵行惯例。通过将无穷大限制的顶点的w值设置为0.0,我们将均匀的表示从3D点的变化改为3D向量。矢量(w = 0.0)在剪辑空间中的渲染将是半无限的。重要的是要注意,我们只能在转换为剪辑空间后将w值设置为0.0。在Direct3D中,这意味着世界的联合转型,视图和投影矩阵。这是因为当我们将灵活的顶点格式设置为D3DFVF_XYZRHW时,我们绕过Direct3D的转换和照明管道。Direct3D假设我们已经转换并点亮了顶点。理想情况下,几何的挤出应该在顶点程序中完成,因为我们已经在顶点着色器中的剪贴空间中工作。事实上,顶点着色器和模板阴影卷是在天堂做的一个匹配。我们将在本文末尾讨论在顶点程序中执行阴影卷的好处。
当将几何体挤出很大的距离或无穷大有助于避免有限阴影体积覆盖的问题时,它也产生另一个问题。想象一下地牢“第一人称射击”(FPS)游戏中的两名玩家,在相邻的房间漫游,被一块坚实的砖墙隔开。台灯在其中一个房间中,其中一个玩家将阴影投射到分隔房间的砖上。另一个房间的玩家将看到台灯投下的阴影因为阴影体积被挤出到无限远。坚实的砖墙突然变得像一块薄薄的纸,上面有一个“鬼影”。幸运的是,我们可以通过使用遮挡剔除技术剔除阴影投射玩家的头像来避免这种情况。图12显示了一种更为尴尬的情况,即相机在地形的另一侧看到封堵器和封堵器的鬼影。这种情况是非常有可能的,特别是对于飞行模拟或空中作战游戏。避免有限阴影体积覆盖(图5)和鬼影(图12)的唯一可能的解决方案是对场景中光源和遮挡物的放置施加限制。
查看平坦度剪辑 – 终极邪恶
现在是时候面对模具阴影卷中最大的邪恶:查看截锥体剪辑。裁剪是任何3D渲染技术的潜在问题,因为我们依靠3D世界的透视投影视图。视锥体需要近剪切距离和远的剪切距离,用于创建近剪辑平面和远剪辑平面。深度通过和深度失败技术都受到视锥截面问题的困扰。如图13所示,深度传递技术在与相邻剪辑平面交叉之后剪切阴影体积时会遇到错误。红色箭头表示一种情况,由此,由于阴影卷的剪切,关联片段的模板值将错误前脸
另一方面,深度失败技术由于使用远剪辑平面剪切阴影体积而产生错误。由于远剪辑平面距离眼睛位置有一定距离,所以当阴影体积在远平面处被剪切时,深度失败技术几乎肯定会产生错误的结果。图14中的红色箭头表示深度失败技术将产生误差的情况,因为阴影体积的背面已经在远平面处被剪切。
我们可以通过调整裁剪平面来解决裁剪问题,但并不总是这样做。例如,移动近剪切平面将极大地影响深度精度,并可能对使用深度缓冲区的其他操作产生负面影响。
Mark Kilgard [2]提出了一个有趣的想法,即当阴影卷与近剪辑平面相交时,处理两种可能的情况。这个想法是“遮盖”近剪辑平面处的阴影体积,以便以前剪切的正面几何形状现在可以在近剪辑平面渲染。第一种情况是封闭器轮廓的所有顶点投射到近剪切平面。在这种情况下,从闭塞器轮廓内的所有正面顶点生成四边形环。然后将四边形环路投影到近剪辑平面上,从而形成阴影体积的封盖。
第二种情况发生在只有部分阴影卷投影到近剪辑平面上时。这证明比以前的情况要难得多。为了他的信用,Kilgard设计了一个精心制作的系统来滤除应该投影到近剪辑平面上的三角形顶点(面向远离光源),以遮盖阴影体积。靠近剪切平面处的阴影卷的封顶产生另一个问题:深度精度。靠近剪辑平面的渲染几何类似于沿着剃刀的边缘滚动硬币; 硬币可以轻松地放下两面。这意味着近平面仍然可以剪切旨在覆盖阴影卷的顶点。为了克服这个问题,Kilgard设计出另一种方法,从眼点到近平面构建深度范围“凸缘”。这个想法是将阴影体积从0.0到1.0的深度范围渲染,而正常的场景渲染发生在0.1到1.0的深度范围内。通过操纵透视投影矩阵,可以将凸起构建成视锥体。一旦到位,阴影卷的近剪辑平面覆盖的深度值为0.05,这是边框的一半。这个想法确实是原创的,但并不能完全解决问题。近平面阴影帽中的裂缝或“孔”非常频繁地发生,导致错误的结果。近剪辑平面问题的结论是确实没有微不足道的解决方案。至少,在撰写本文时,没有知道这个问题的万无一失的解决方案。这使得深度通过技术非常不希望。
幸运的是,有一个优雅的解决方案来解决深度故障技术的远距离切割问题。问题的解决办法就是使用无限透视图投影或简单的无限观察平截头体。通过将一个远的平面投影到无限远,当我们渲染阴影体积时,没有数学上的机会被远处的平面剪切。即使阴影体积被挤压到无限远,无穷远的远处仍然不会夹住它!Eric Lengyel在[11]中介绍了OpenGL透视投影矩阵的数学推导。我们将在这里处理Direct3D透视投影矩阵。
(1)
变量:
n:近平面距离
f:远平面距离
fov w:以弧度表示的水平视野
fov h:以弧度表示的垂直视野
无限远的平面意味着远平面距离需要接近¥。因此,当远平面距离达到无限极限时,我们得到以下透视投影矩阵:
(2)
公式(2)定义了从近平面到无穷远的远平面延伸的透视投影图。但是,我们是否绝对确定使用4D均匀矢量挤压到无穷远的顶点不会被限制在无穷远处?可悲的是,由于硬件精度有限,我们不能100%确定。实际上,图形硬件有时产生具有大于1的归一化z坐标的点。然后,这些值被转换成整数以用于深度缓冲器。由于我们的模板操作完全取决于深度值测试,这将会受到破坏。幸运的是,有这个问题的解决方法。解决方案是将我们的归一化设备坐标的z坐标值从[0,1]的范围映射到[0,1-e],其中e是一个小的正常数。这意味着我们试图将无限远点的z坐标映射到标准化设备坐标中略小于1.0的值。让Ð ž是原来的z坐标值和𢠞是映射z坐标。可以使用下面所示的等式(3)来实现映射:
(3)
现在,我们利用等式(2)将点A从相机空间(A cam)转换成剪辑空间(A 剪辑)。请注意,相机空间通常也称为眼睛空间。
哪个会给我们
(4)
让我们通过更换因素所需的范围映射到等式(3)ð Ž与 和𢠞用 :
(5)
通过使用等式(4)给出的值来简化方程(5),得到:
(6)
使用等式(6),我们可以将我们的范围映射强制到由等式(2)给出的投影矩阵P $中,得到以下结果:
(7)
因此,我们可以使用等式(7)给出的透视投影矩阵,而不用担心在无限远处发生的阴影体的远平面限幅!你可能会想,将视锥体积拉伸到无穷远是否会影响深度缓冲精度。答案是,它确实影响精度,但是精度的损失真的可以忽略不计。只有在将远平面扩展到无限远时,数值范围的数量损失量 。说我们原来的近夹子飞机是在0.1米,远的夹子是在100米。该范围对应于[-1.0,1.0]的深度范围。然后我们将远平面距离延伸到无限远。现在,从0.1米到100米的范围将对应于[-1,0.999]的深度范围。从100米到无穷远的范围将对应于[0.999,1.0]的深度范围。深度缓冲精度的损失根本不是很大的影响。n和f值之间的差越大,深度缓冲器精度的损失越小。你可以在Eric Lengyel的书中找到上面的推导和许多其他相关的数学推导[12]。应该注意的是,使用无限视角截锥体意味着我们必须绘制更多的几何。这可能会造成潜在的性能问题。
无限视角平截头体投影真的只是一个软件解决方案的远平面裁剪问题。Mark Kilgard和Cass Everitt [10]提出了一个解决问题的硬件解决方案,而不是使用无限视角截面。较新的图形硬件现在支持一种称为“深度钳位”的技术。实际上,深度夹紧延伸,NV_depth_clamp特别添加到Nvidia的GeForce3及以上显卡,以解决阴影卷的远平面裁剪问题。当活动时,深度夹紧将迫使所有超过远剪切平面的物体被绘制在具有最大深度值的远剪切平面处。这意味着我们可以将封闭的阴影体积投影到任意距离,而不用担心被远处的平面夹住,因为硬件将正确处理图形。通过图形硬件的自动支持,深度故障阴影卷变得非常容易实现。我们可以将阴影体积扩展到无限远,同时使用我们的有限视角截面呈现,并且仍然获得正确的深度失真模板值!那么权衡是硬件依赖。如果我们希望深度故障阴影卷适用于任何图形卡(具有模板支持),我们将不得不使用无限视角平截头体投影而不是深度夹紧扩展。
深度通过或深度失败
我们已经运行了深度传递和深度失败技术的大多数方法和实现问题,用于执行模板阴影卷。那么我们应该在游戏中使用哪种方法?让我们来看看这两种技术的利弊。深通 那么我们应该在游戏中使用哪种方法?让我们来看看这两种技术的利弊。深通 那么我们应该在游戏中使用哪种方法?让我们来看看这两种技术的利弊。
深通
- 优点
- 不需要卷影影子卷
- 较少的几何渲染
- 更快的两种技术
- 如果我们忽略近平面限幅问题,更容易实现
- 不需要无限透视投影
- 缺点
- 由于不可靠的近平面限幅问题而不健壮
深度失败
- 优点
- 强大的解决方案,远距平面裁剪问题可以优雅地解决
- 缺点
- 需要封顶才能形成封闭的阴影卷
-
由于封顶造成更多的几何图形
- 两种技术较慢
- 稍微更难实施
- 需要一个无限的透视投影
看来深度通过是两者的更好的技术,但是我们必须记住,当我们的相机进入一个阴影卷时,它将完全失败。直到有一个可行的解决方案为近平面裁剪问题,如果需要一个稳健的实现,仍然需要深度失败技术。两种技术之间的选择在很大程度上取决于我们正在开发的游戏的限制。如果需要阴影投射自顶向下或等距视图游戏,如暗黑破坏神,深度传球技术就足够了。另一方面,对于FPS游戏,几乎不可能避免相机进入阴影体的情况。在这种情况下,深度失效技术是唯一可行的解决方案。当然,我们也不要忘记其他的影子技术,比如阴影映射。在某些情况下,场景中的阴影脚轮太小,无法显示任何自我阴影,只需使用投影阴影映射就会更加明智。对于逼真的软阴影,也可以使用阴影贴图更便宜地完成。
总的来说,将其他技术与阴影卷结合起来可以实现更好的质量阴影。这种混合实现的一个例子是Power Render X [16]游戏引擎,它使用阴影卷生成阴影,然后通过使用投影纹理相对于与封闭器的距离淡出阴影。流行语:鲁棒性和效率在过去10年中,玩游戏的复杂性激增,游戏中现实和准确的阴影已经不够了。我们需要提供强大且高效的模板阴影卷实现。在鲁棒性的情况下,使用深度失败技术足以满足几乎任何可想象的情况。然而,硬件限制和较差的帧速率有时会将深度失败技术推广到我们的计算预算之外。有很多方法可以优化我们的影子卷执行,以创建漂亮的阴影,并将帧速率保持在所有重要的20fps基准之上。模板阴影体积实现中的真正瓶颈是轮廓确定和阴影体积渲染。
前者需要大量的CPU周期,如果遮罩体具有高多边形数量,则会恶化。
后者是无形填充率的巨大消费者。在剪影确定期间减轻CPU紧张的一个明显方法是使用闭塞器的下多边形模型。另一个有效的方法是每2-4帧确定一个新的轮廓。这是基于假设光的位置或封堵器的位置在2-4帧内不会非常显着地改变。这个假设在大多数情况下都是相当不错的。请记住,在深度故障技术中用于形成封闭阴影卷的额外封顶几何形状有助于深度失败是更昂贵的方法?我们可以大大减少封堵器的封盖几何形状,这些遮盖物在经常面向光源的表面上具有相对较少的细节。这里的细节意味着更少的几何细节,这意味着表面相当平坦,并且通常产生接近或完全凸起的轮廓外壳。如果是这种情况,我们经常可以创建一个三角形条,用作前盖来关闭阴影卷。我们应该注意到,这是一个近似值,因此会导致在某些角度不正确的阴影。然而,这种近似值对于小物体应该是非常好的。对于Direct3D实现,建议使用“焊接”网格。焊接网格只是意味着没有重复的顶点代表完全相同的点。要查看“未焊接”网格的示例,请打开网格查看器工具并创建多维数据集。查看多维数据集的顶点信息,您将看到有24个而不是8个顶点。这是不可避免的,因为Direct3D’ 顶点的s版本包含不同的脸部共享的颜色和正常信息,指的是同一点; 因此为不同的面产生额外的顶点。额外的顶点是多余的,但在轮廓计算期间不能被移除,而没有相当多的比较工作。因此,使用焊接网格来确定轮廓是比较明智的。
Direct3D网格浏览器实用程序提供了一个漂亮的选项来做到这一点。点击MeshOps然后焊接顶点,在焊接之前检查去除背靠背三角形,重新生成邻接和焊接所有顶点。或者,我们也可以使用网格函数D3DXWeldVertices来焊接网格。关于无形填充率,他们真的是不可避免的。但是,在渲染阴影卷之前,可以通过在Direct3D 中设置D3DRS_COLORWRITEENABLE渲染状态来减轻影响。我们可以使用它来关闭红色,绿色,蓝色和Alpha通道绘图,因为我们只想填充模板缓冲区。我们应该注意的另一个领域是在3D场景中管理阴影投射灯。光源的良好管理将不断有益于阴影体积生成过程。经验法则是在任何一个时间将影子投射光源的数量保持在最大值4以下。未来的硬件或改进的算法将使先前的语句无效,但是现在它作为一个很好的指导方针,至少可能在未来的两年内保持下去。光源管理的重要方面是用于选择哪些光源应该包含在阴影卷生成过程中的方法。应考虑的主要参数可能是强度,与观众的距离,与当前游戏的相关性,以及最后的视觉重要性。看看查尔斯布鲁姆关于选择影子投射光源的优秀文章[4]。让我们来讨论一些我们可以采用的高级优化,以进一步加快我们的影子量的游戏。当我们确定相机不在任何阴影卷中时,我们实际上可以利用深度传递技术。这可以通过形成近剪辑体积而相当容易地完成。光源的位置和近平面的四边用于定义金字塔。近平面关闭金字塔,从而形成近剪辑体积。如果闭塞器完全位于该体积之外,我们可以安全地使用深度通过技术,因为封堵器的阴影体积没有与近平面相交的机会。Eric Lengyel还描述了使用OpenGL剪刀矩形支持来减少渲染阴影卷和照明碎片的填充率损失。然而,DirectX 8.1中尚未提供全面的高级剪刀矩形支持。有关这两个优化的详细信息,请参阅[11]。最后,我们应该积极利用任何可用的硬件支持。未来的GPU将被期望支持双面模板测试,这将使我们能够将阴影卷的正面和背面呈现在一起。当通过将几何设置成本,顶点转换成本和几何传输成本减半来渲染阴影卷时,这将大大节省,因为我们只需要将影子体积几何图形推送一次。硬件将在同一套几何形状上进行两次模拟过程时自动清理前脸和后脸。还应使用硬件深度夹持支持,将阴影体积几何尺寸剪切到远处,无需额外费用。最后,让我们来看一下最重要的现代图形硬件功能,我们应该充分利用这些功能: 顶点着色器。
阴影卷由顶点着色器提供在商业图形硬件的整体改进之中,可编程顶点处理流水线(顶点着色器)的引入可能是任何实现阴影卷的最好的事情。在顶点程序中执行阴影卷的最大优点是,无论何时生成阴影卷几何,都不需要上传它们。整个阴影卷可以作为静态顶点缓冲区驻留在硬件内存上。保存的数据带宽可能相当大。此外,在可编程顶点硬件中完成的浮点运算非常快。但是,我们需要注意的是,使用顶点程序实现影子卷可能会在某些情况下降低性能。我们将在本节结尾处进一步说明。为了利用顶点着色器的力量,我们需要首先对封堵器的几何体进行预处理。当前的顶点着色器硬件不具备生成新顶点的能力。它是严格的1顶点和1顶点输出管道。这造成了一个问题,因为我们需要从轮廓边缘创建新的顶点,以形成阴影卷。
解决方案是创建预处理期间所需的所有附加顶点。一旦在顶点着色器中,我们使用这些附加顶点生成阴影卷。让我们看看如何做到这一点。我们需要为正好2个面共享的每个边(2个顶点)创建一个四边形。四边形可以看作是由两个不同的面共享的原始边缘形成的“退化”四边形。这两个面都与退化四边形有着相同的边缘。由于两个面的边缘是相似的,所以位置上,简并四边形是“零长度”。唯一的区别是边缘保持其各自面部的正常信息。
一旦在顶点程序中,我们点亮光矢量和顶点法线。如果结果为正,顶点将通过顶点程序。如果结果是负的,我们将它沿着光矢量的方向拉伸。这种技术将优雅地产生一个闭合的阴影体积,因为面向几何的几何形状保持不变以形成前盖,而面向远离光的几何形状被挤出以形成阴影体积和背盖的侧面。如果您不确定它是如何工作的,请尝试此示例。想象一下,在其左侧有一个点光源的球形网格。整个左半球面对光线,因此限定左半球的所有几何形状保持不变,以形成前盖。整个右半球却远离光线。因此,限定右半球的所有几何形状都被挤压形成后盖。阴影体积的侧面由沿着轮廓边缘的退化四边形自动神奇地形成。在这种情况下,剪影边缘在球体的中间正好形成一条垂直线。这样做是因为剪切边缘的每个退化四边形正好是1个边。
之前的退化四边形现在变成了定义阴影体积侧的正常四边形。Chris Brennan在[15]中提出了一个关于在顶点程序中实现阴影卷的挤压的简短文章。我们应该注意到,预处理需要创建大量额外的几何。事实上,只有沿剪影边缘的退化四边形是有用的。其余的只是休眠,但仍在推动通过处理管道。然而,现在可以在图形硬件上完全完成阴影卷生成,并且在大多数情况下,性能通常要好于非着色器实现。Mark Kilgard最近指出,如果封闭器具有高多边形数量或投射光源的阴影很多,则计算顶点着色器中的剪影边缘可能会对性能造成不利影响。由于我们需要将更多的顶点推入管道,所有这些都必须通过顶点着色器中的轮廓边缘测试,这个评估是严格的。
因此,具有高多边形数量的封堵器将产生大量的浪费顶点(退化四边形),并且测试所有这些额外顶点的成本可能无法覆盖使用顶点着色器获得的几何上传节省!更多的光源将进一步恶化这种顶点着色器的实现。因此,可以对可编程顶点硬件上的阴影体积进行彻底的测试,以确保我们在使用CPU时实现了超过实现的净性能提升。如果需要CPU用于重AI或游戏逻辑计算,则影子卷的顶点着色器实现可能会更有效。然而,在许多情况下,也可以使用顶点着色器作为辅助,而不是尝试在顶点着色器中执行所有操作。故事的道德是: 永远记得在游戏中打开一切(AI,物理,声音,输入,网络,渲染器等)并再次进行基准测试和基准测试。最后,关于模具阴影卷技术的更广泛和深入的文章将在即将出版的书籍ShaderX2(www.shaderx2.com)中提供。
本书中的文章深入了解模具阴影卷中涉及的算法,详细讨论了商业3D引擎中优化,工作流程和场景管理以及“秘籍”,以加速强大的阴影卷实施。还将有6个广泛的样品覆盖正常的CPU,使用新的高级着色器语言(DirectX9.0)进行汇编和GPU实现中的GPU实现。这本书是在现场工作的专业人士和工程师的许多先进的着色器技术的汇编。它将在2003年8月份可用,编辑是沃尔夫冈恩格尔先生。阴影卷在工作
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/137200.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...