Speckle 中的 3D 数据具有各种形状和大小(范围从 1 毫米到 100 公里),并且与“世界”原点 (0, 0, 0) 的距离各不相同。 大多数创作软件都有专门的显示管道来处理这个问题——取得了多方面的成功,但有明显的局限性(例如不能在月球上建模 😀)。
当在 Web 浏览器中渲染内容时,我们常用的 WebGL 库 Three.js 并没有很好地处理这个问题:我们得到了我们亲切地称之为“速度需求”效果的显示伪影,通常称为空间抖动。 这个问题在来自土木工程师的模型中最为明显——路段、铁路等——或 GIS 定位的元素(它们往往离臭名昭著的 Null Island 很远)。 📍
推荐:用 NSDT场景设计器 快速搭建3D场景。
1、为什么会出现空间抖动?
浮点格式的数字表示 3D 空间中的位置。 使用 WebGL,我们只能使用单精度浮点格式。 后来的图形 API 允许双精度,但无论如何,所有浮点格式都是近似值。 随着数字变大,精度变小(查看此处的表格)。 浮点格式的这种行为是空间抖动的根本原因。
例如,如果有一些顶点距原点数千公里,并且世界单位以米为单位,则 GPU 将使用大浮点数进行数学运算。 因此,当你将这些顶点投影到屏幕上时,由于大值浮点数的精度较低,从一帧到下一帧就会得到不稳定的结果。 这将导致图像抖动,因此称为空间抖动。 当你的相机靠近渲染对象时,情况会变得更糟,因为它会占据屏幕的更大部分,这意味着你需要更高的精度才能获得准确和稳定的投影结果,但这种精度不会存在。
2、解决方案
受本文的启发,我们尝试使用两种不同的方法来解决这个问题:RTC 和 RTE。 💡
- 相对中心法 (RTC)
这种方法适用于一定的世界大小。 如果我们想变得更大,我们需要别的东西。 此外,RTC 不太适合 Speckle 为用户制定的未来计划:完全改变批处理系统并积极地将几何图形组合在一起。
RTC 实现需要在网格的本地原点定义顶点的位置属性。 这很有效,因为要恢复原始世界空间中的几何体,只需要一个简单的变换。 然而,因为我们将对几何体进行批处理,我们将无法使用任何额外的转换将它们带入原始世界空间。 找到批次的质心,并烘焙与其相关的位置可以使这项工作成功——但这是我们既不希望也不需要的复杂情况。
- 相对人眼法 (RTE)
接下来,我们转向 RTE,也称为浮动原点技术。 使用 RTE,不需要以任何方式改变原始顶点位置。 相反,你将观看者(相机)视为空间的静止原点,其他一切都围绕它移动。 Unity 使用这种方法来渲染大世界。 从本质上讲,RTE 所做的是在相机相对靠近受影响的(抖动)网格时减少典型 GPU 转换管道中涉及的数字的大小。 当相机距离很远时,数字再次变大,人们会认为几何体会抖动,但问题是:当相机距离很远时,网格在屏幕上的投影要小得多,这意味着固有精度 你需要的也小得多,因此,不再抖动。
3、案例分析
以下是抖动问题最初的表现方式:
现在让我们使用简单的 RTE。 我们需要将顶点程序从 three’s stock 更改为:
// Note: This is a simplification. I kept only what's relevant for example's sake.
// There's more stuff going on, but not relevant for this.
attribute vec3 position; // The vertex position attribute
uniform vec3 uViewer; // The camera position
// vec4 mvPosition = vec4(position.xyz, 1.); Three.js stock
vec4 mvPosition = vec4(position.xyz - uViewer.xyz, 1.) // RTE. Inverse translate the world
mvPosition = modelViewMatrix * mvPosition;
gl_Position = projectionMatrix * mvPosition;
此外,我们需要一个 modelView 矩阵,它在位置 0,0,0 处保存观察者矩阵。 请记住,相机是 RTE 中的世界原点。
// Indices 12, 13, 14 hold the translation component
object.modelViewMatrix.elements[12] = 0
object.modelViewMatrix.elements[13] = 0
object.modelViewMatrix.elements[14] = 0
让我们看看我们得到了什么:
这会减少抖动,但是,抖动并没有完全消失! 我们需要扩充简单的 RTE 方法。 根据主要参考,我们可以更进一步。 我们可以通过将双精度值编码为两个浮点数来提高我们位置的精度。 为了做到这一点,我们必须改变更多的东西。
首先,我们需要将位置属性拆分为两个单独的属性,low 和 high,对应于文章中计算的两个浮点数。 我们还需要使用两个浮点数来计算相机位置。
// Note: This is a simplification. I kept only what's relevant for example's sake.
// There's more stuff going on, but not relevant for this.
attribute vec3 position_low; // The vertex position low attribute
attribute vec3 position_high; // The vertex position high attribute
uniform vec3 uViewer_low; // The camera position low component
uniform vec3 uViewer_high; // The camera position high component
// vec4 mvPosition = vec4(position.xyz, 1.); Three.js stock
vec3 highDifference = vec3(position_high.xyz - uViewer_high);
vec3 lowDifference = vec3(position_low.xyz - uViewer_low);
vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.);
mvPosition = modelViewMatrix * mvPosition;
gl_Position = projectionMatrix * mvPosition;
通过此更改,我们得到:
抖动完全消失了!
4、额外的
使用将双精度值编码为两个浮点数的方法有一个轻微的缺点:你增加了内存占用,因为你为位置使用了两个顶点属性。 增加并不大,因为查看器总共使用相对较少的顶点属性:位置、法线、UV,而通常图形应用程序会使用更多属性,如第二个 UV 集和切线。
此 RTE 实施带来的变化具有普遍的功能性。 与此一样,无论世界是大是小,查看器都能正确渲染。 然而,RTE 将工作到一定的距离值。 除了最大值之外,还有精度问题,可以通过减小最大值来提高精度。 如果需要,我们可以调整它。
这是另一个更相关场景中无 RTE 与 RTE 的示例:
原文链接:WebGL空间抖动问题 — BimAnt