【发布时间】:2014-04-15 08:44:08
【问题描述】:
我一直在研究阴影贴图,我看到有些人将平方距离写入阴影通道中的深度纹理,而有些人使用实际深度值。有什么理由比另一个更喜欢一个吗?仅使用实际深度值似乎更快?
【问题讨论】:
我一直在研究阴影贴图,我看到有些人将平方距离写入阴影通道中的深度纹理,而有些人使用实际深度值。有什么理由比另一个更喜欢一个吗?仅使用实际深度值似乎更快?
【问题讨论】:
在其中一个答案的 cmets 中,我注意到您引用了我写的另一个答案,我想确保您理解该答案的原始上下文。
虽然到目前为止的答案都集中在实际比较本身的复杂性上,但他们没有考虑更改存储在硬件深度缓冲区中的值对阴影贴图构造的性能有何影响。您担心对早期 Z 拒绝的影响,这是一个有效的担忧,但它只会影响阴影贴图的性能构造,我将在下面解释。
另外,请记住answer you are referring to 与基于立方体贴图的阴影贴图有关。由于构建和采样的独特方式,它们有自己的一系列挑战要处理,这就是为什么比较与您在其他情况下看到的略有不同的原因。
这种压缩不会节省存储空间(事实上,它会增加一点额外的存储开销),但它的作用是大大更快地清除缓冲区并获取。无需将相同的颜色或深度写入缓冲区中的每个像素,而是可以将每个图块标记为“清晰”并赋予清晰的颜色/深度。当需要获取像素的颜色/深度时,首先会查看像素所属的图块,如果整个图块清晰,则返回图块的颜色,而不是通过获取的麻烦内存中的实际像素。
其实很多。这种分层内存结构非常适合一次拒绝大量片段,因为可以在单个专门的内存操作中确定整个图块像素值的最小/最大深度。这确实意味着写入颜色/深度缓冲区要复杂得多(必须更新标志和此类每个图块),但硬件专门设计为以这种方式工作,而且很多时候您不必这样做任何可以从中受益的特别之处。
现在,尽管光栅化器有一个简单的固定功能工作要做,但只要分层 Z 缓冲 (Hi-Z) 可用,它就会做一些非常聪明的事情。鉴于所有基元都是平面的,如果光栅化器可以保证片段着色器不会改变深度,它可以执行粗粒度(例如在压缩深度缓冲区)使用最小/最大深度值进行深度测试,并在着色/混合之前杀死多个片段。如果粗粒度测试通过,或者如果片段着色器写入自己的深度,则必须对每个片段进行着色,然后然后在单独的基础上针对深度缓冲区进行测试.现在,在您的情况下,片段着色器非常简单,因此不必要地着色片段的费用不会像通常那样多,并且混合也不是仅深度通道中的一个因素。
但是,必须对完全被遮挡的图元进行每个片段的后期深度测试是浪费时间,Hi-Z 本来可以避免的。仅深度渲染的许多可衡量的开销实际上是由绘制调用本身(状态验证、命令序列化等)引起的前端 CPU 开销。假设您的仅深度通道被有效地批处理,您可以通过提高深度测试的效率来提高性能。只是不要期望看到性能有很大的提高,上面描述了为什么 Hi-Z 更适合更传统的渲染的原因有很多。
顺便说一句,如果你想对我刚才解释的大部分内容进行视觉总结,请查看here。
最后,在构建阴影贴图期间正确利用分层 Z 缓冲不会产生巨大的性能提升,但它可以超过通过减少数量来获得的收益比较深度所需的算术指令。这主要取决于您更新阴影贴图的频率。一方面,如果您只执行一次(静态),硬件填充阴影贴图的效率实际上并不重要。另一方面,如果您必须为每帧的每个灯光绘制 6 个独立的阴影贴图,那么如果您可以减少绘制每个阴影所需的时间,那么性能将会得到真正可衡量的提升。
这里没有考虑的问题是首先从阴影贴图中获取深度所需的时间(比您的比较多得多)。您可以随心所欲地加快阴影贴图的构建和比较,但一些最大的好处来自提高重新构建(采样)性能。
例如,抗锯齿 VSM 阴影可以使用传统的纹理过滤来完成,而不是使用其他技术必须执行的多个卷积采样和比较。这使得从 VSM 进行抗锯齿重建更加有效。因为VSM是基于方差的,所以不需要存储透视深度……你可以用线性距离,这个算法没什么区别。尽管构造(存储 d 和 d2)比较复杂,但如果需要抗锯齿,效率会更高。
显然没有万能的,您在阴影贴图中存储的内容很大程度上取决于您的算法。
【讨论】:
实际上使用平方值需要更少的计算量。要确定向量的长度,请执行sqrt(r·r)。如果你比较两个向量的长度,这将是sqrt(r_0·r_0) > sqrt(r_1·r_1),但sqrt 是一个严格的单调函数,所以r_0·r_0 > r_1·r_1 即比较平方值同样有效,但它节省了平方根的计算。
【讨论】:
关于距离平方与实际深度,这仅取决于您根据我的经验的要求:如果我使用任何涉及平方根的东西,我更喜欢使用平方根,因为平方根的计算量很大,而平方很便宜.另一方面,如果我没有涉及平方根的数学,那么我使用直深度值。我会试着解释一下:
渲染场景之前:在预处理步骤中使用相机在灯光的位置进行渲染,以从灯光的 POV 生成场景。从该位置可见的所有像素都不能处于阴影中,因此通过消除,所有其他像素都必须处于阴影中。
如果我们能够以某种方式标记光线到达的所有像素,那将很容易,但这可能会很麻烦并且会占用大量内存。
方法一:(深度检查)
所以我们将每个被照亮的像素到灯光的距离存储在深度缓冲区中。渲染场景时,我们计算任何渲染像素到灯光位置的距离。如果该距离等于该像素位置的存储深度,我们就知道同一像素在光线下是可见的,因此被照亮。如果距离较远,我们知道该像素从光线的 POV 看不到,并且处于阴影中。
方法2:(阴影贴图)
现在,根据实现细节,您可以使用浮点存储阴影贴图的像素数据,也可以将像素数据打包为颜色贴图的整数 (reference 1),并使用链接的 GPUGems 书中的算法,但是对于正常的深度测试阴影贴图,可以使用简单的距离 (reference 2)。
归根结底就是您希望阴影映射有多详细,以及您愿意进行多少计算:对于实时渲染(游戏、简单的 cad 渲染等),花在细节上的时间越少并且附加值更好,因此我们使用诸如使用平方值而不是平方根之类的技巧(维基百科上有一个很好的复杂性基本文本顺序(我知道,通常不会使用它作为来源)here)。
我已尽量保持简短,因此可能遗漏了一些内容,如果您需要更多信息,或者我的观点没有意义,请告诉我,我会更新更多细节/说明:)
【讨论】: