0%

图形学学习笔记—详解屏幕空间下的线性插值

在渲染中,我们常常会使用透视投影,将物体从相机空间转换到投影空间,再通过透视除法等操作,最终变换到屏幕空间下。然而,在经过了这些变换后,屏幕空间已经不是线性空间了,那我们还能在屏幕空间中进行坐标、属性的插值运算吗?本文将针对该问题展开讨论。

问题描述

我们常常会遇到需要在屏幕空间下对属性进行插值的情况,比如在光栅化时,需要对顶点属性进行插值;又如在后处理或延迟渲染阶段,我们会对屏幕空间纹理进行插值。那我们能像在相机空间中那样,根据坐标进行线性插值吗?

答案是不行,可以用下图进行解释[1]:

我们将相机空间的线段AB投影到屏幕空间的线段ab上,取屏幕空间上的中点c,将其反投影回相机空间。发现点C并不落在AB的中点处。也就是说,如果我们在屏幕空间上采用线性插值,会导致属性值与其真正所投影的位置不一致,从视觉效果上看,就是原本的直线变为了曲线。

要实现透视正确的插值,我们的目标是使得插值后的值反投影后,仍然落在原先的直线上。下文将展开说明其解决方法。

透视正确的插值

z坐标的插值

我们首先解决坐标的插值问题,如果坐标都不能插值了,光线追踪之类的算法也就不能在屏幕空间中计算。

再次回到图1,这次我们的点c为ab之间的任意插值点,我们需要通过插值系数s计算出反投影点C的z坐标,也就是理论上的投影正确的插值坐标,从而得到屏幕空间插值在相机空间下的映射。

首先根据相似性,得到下面的等式:

再根据线性插值公式,得到下面的式子:

(4)(5)代入(3):

(1)(2)代入(7):

(6)代入(8):

(10)代入(6),简化:

式(12)就是最终得到的投影正确的Z坐标插值,不难发现,等式两边取倒数后,该式就变为了1/Z的线性插值,也就是说,1/Z在屏幕空间中是可以线性插值的,注意,这里的Z仍然是相机空间中的Z值。

现在我们将目光转移到屏幕空间坐标,经过投影变换后,屏幕空间中的z坐标z’与相机空间大概成如下关系:

结合之前的两个结论,z的倒数在屏幕空间中可以线性插值,而屏幕空间z’与相机空间z的倒数线性相关,这就说明,屏幕空间中的坐标值是可以像正常的线性坐标系一样进行线性插值的

透视变换的性质

对于上述结论,我们可以借用透视变换的性质进行简单解释,透视变换后,直线仍然是直线,这就是为何我们可以对屏幕空间坐标进行线性插值,并且插值结果在相机空间下仍然是线性的。

拓展思考

在大部分的屏幕空间光线追踪算法中,都能够发现深度纹理保存屏幕空间深度,而不是相机空间深度,这就是因为在屏幕空间下,屏幕空间深度是能够进行线性插值的。

从直观上理解,光线追踪是基于“固定直线”的计算,也就是说在屏幕空间和相机空间中,我们所定义的“直线”应当具有等价性,而唯有使用屏幕空间深度,才能够提供这种等价性。

另一方面,经过透视变换后,原本平行的直线不再平行,意味着所有不同直线间的相对关系失效。对于向量反射等计算,我们不能在屏幕空间中进行。但是可以在相机空间中先计算反射向量,再确定该向量起点和终点,将这两个点投影到屏幕空间中,这样就可以得到屏幕空间中的反射向量。

总而言之,由于屏幕空间坐标具有线性插值的性质,任何能够规约到线性空间下坐标插值的算法,都能够在屏幕空间下执行。而对于其他算法,我们先要在线性空间下计算,再将结果投影到屏幕空间。

属性插值

与线性的坐标插值不同,在屏幕空间下我们不能进行线性的属性插值。

再次参考这张图,其中点AB上各有属性值I1、I2,根据线性插值,我们有:

将(10)(12)代入(13),简化后,可以得到:

上式就是在屏幕空间下的属性插值公式。不难发现,我们可以将I/Z作为一个整体进行线性插值,在屏幕空间坐标下,我们可以使用IZ’作为一个整体线性插值。

OpenGL中的属性插值

在实际编写GLSL着色器时,一般有两种情况我们会在屏幕空间下进行属性的插值。一个是在光栅化时,对于顶点属性的插值;二是在后处理或者延迟渲染时,我们在采样屏幕空间纹理时,可能会进行插值。

对于光栅化而言,它是GPU中的固定管线,但OpenGL提供了插值设置的接口。我们可以在顶点着色器和片段着色器的out和in变量前加interpolation限定符,有以下三种限定符:

  • smooth: 执行本文中的透视正确的插值。
  • noperspective: 执行线性插值。
  • flat: 不进行插值。

使用例: noperspective in vec3 normal;

如果不进行任何设置,OpenGL会贴心地为我们默认设置为smooth,也就是默认是透视正确的。

对于屏幕空间纹理采样而言,首先,对于屏幕空间深度纹理,如前文所述,其本身就是可透视正确插值的;对于颜色纹理,有时候我们会进行插值滤波,但这么做本身就是基于感知而非基于物理的,因此线性插值即可;如果一定要对世界坐标、法线这些GBuffer进行插值,如果追求严谨的话,应该还是要通过深度进行透视正确的插值的。

结论

  1. 屏幕空间坐标可以线性插值
  2. 屏幕空间光线追踪算法要基于屏幕空间深度值
  3. 属性值在屏幕空间下需要乘上屏幕空间深度后才能进行线性插值(透视正确插值)
  4. OpenGL在光栅化阶段默认是透视正确插值的

参考资料

[1] Perspective-Correct Interpolation.Kok-Lim Low.2002