Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

大世界坐标

备注

大世界坐标主要用于 3D 项目;2D 项目很少会用到。此外,与 3D 渲染不同,启用大世界坐标时,2D 渲染目前无法从精度提升中获益。

为什么要使用大世界坐标?

在 Godot 中,物理模拟和渲染都依赖于浮点数。然而,在计算机中,浮点数的精度和范围是有限的。这可能在太空类或星球尺度模拟游戏等拥有庞大世界的游戏中产生问题。

浮点数的精度在值接近 0.0 时最高。随着值远离 0.0(增大或减小),精度会逐渐降低。每当浮点数的指数增大时,也就是浮点数的值越过 2的幂(2、4、8、16……)时,就会发生这种情况。此时,数值的最小步长会增大,导致精度降低。

在实践中,这意味着当玩家远离世界原点(2D 游戏中的 Vector2(0, 0) 或 3D 游戏中的 Vector3(0, 0, 0))时,精度会下降。

精度丢失可能导致远离世界原点的对象看上去在“抖动”,因为模型的位置会吸附到浮点数所能表示的最接近的值。这还可能导致只有在玩家远离世界原点时才会出现的物理故障。

范围决定的是数值所能存储的最小值和最大值。如果玩家尝试移出这个范围,尝试将会失败。然而在实际情况下,在范围限制造成问题之前,几乎一定会先遇到浮点数精度问题。

范围和精度(两个指数区间之间的最小步长)取决于浮点数类型。单精度浮点数的理论范围允许存储极高的值,但精度很低。在实践中,无法表示所有整数值的浮点数类型并不是很有用。在极值附近,它的精度会变得非常低,低到甚至无法区分两个不同的整数值。

以下是浮点数能够表示独立整数值的范围:

  • 单精度浮点数范围(表示所有整数):-16,777,216 和 16,777,216 之间

  • 双精度浮点数范围(表示所有整数):-9 千万亿和 9 千万亿之间

范围

单精度步长

双精度步长

注释

[1; 2]

~0.0000001

~1e-15

接近 0.0 时精度会变得更高(本表已省略部分内容)。

[2; 4]

~0.0000002

~1e-15

[4; 8]

~0.0000005

~1e-15

[8; 16]

~0.000001

~1e-14

[16; 32]

~0.000002

~1e-14

[32; 64]

~0.000004

~1e-14

[64; 128]

~0.000008

~1e-13

[128; 256]

~0.000015

~1e-13

[256; 512]

~0.00003

~1e-13

[512; 1024]

~0.00006

~1e-12

[1024; 2048]

~0.0001

~1e-12

[2048; 4096]

~0.0002

~1e-12

第一人称 3D 游戏的最大推荐单精度范围,不会出现渲染伪影或物理故障。

[4096; 8192]

~0.0005

~1e-12

第三人称 3D 游戏的最大推荐单精度范围,不会出现渲染伪影或物理故障。

[8192; 16384]

~0.001

~1e-12

[16384; 32768]

~0.0019

~1e-11

俯视角 3D 游戏的最大推荐单精度范围,不会出现渲染伪影或物理故障。

[32768; 65536]

~0.0039

~1e-11

所有 3D 游戏的最大推荐单精度范围。超过这个点后,通常需要双精度(大世界坐标)。

[65536; 131072]

~0.0078

~1e-11

[131072; 262144]

~0.0156

~1e-10

> 262144

> ~0.0313

~1e-10(0.0000000001)

超过这个值之后,双精度的精度仍然远高于单精度。

使用单精度浮点数时,虽然有可能超出建议的范围,但此时会出现更多可见的渲染伪影,物理故障也会变得更常见(例如玩家无法在某些方向上沿直线移动)。

参见

详见 Demystifying Floating Point Precision 一文。

大世界坐标的工作原理

大世界坐标(也称为双精度物理)可以提升引擎内所有浮点数计算的精度级别。

默认情况下,GDScript 中的 float 是 64 位的,但 Vector2Vector3Vector4 是 32 位的。这意味着向量类型的精度受到更大限制。为了解决这个问题,可以增加向量类型中用于表示浮点数的位数。这样一来,精度就会指数级增长,这意味着值的精度最终不仅仅提高了一倍,而是在数值较大时也可能提高数千倍。从单精度浮点数切换到双精度浮点数,可表示的最大值也会大幅增加。

为了避免远离世界原点时出现模型吸附问题,Godot 的 3D 渲染引擎在启用大世界坐标时会提高渲染操作的精度。出于性能原因,着色器不使用双精度浮点数,但会使用替代方案来使用单精度浮点数模拟双精度渲染。

备注

启用大世界坐标会带来性能和内存使用方面的开销,这在 32 位 CPU 上尤为明显。所以只有在确实需要时才应该启用大世界坐标。

此功能专为中端/高端桌面平台量身定制。大世界坐标在低端移动设备上可能表现不佳,除非你采取措施通过其他方式减少 CPU 使用率(例如降低每秒的物理周期次数)。

在低端平台上,可以使用原点移位方法来实现大场景,而无需使用双精度物理和渲染。原点移位使用单精度浮点数,但它会给游戏逻辑带来更多复杂性,尤其是在多人游戏中。因此,本页不会详细介绍原点移位。

谁需要使用大世界坐标?

大世界坐标通常用于 3D 太空类或星球尺度的模拟游戏。这还能延伸到其他需要在支持非常快速移动的同时,也要支持非常慢并且精确的移动的游戏。

另一方面,只有在确实需要时才使用大世界坐标是很重要的(出于性能考虑)。以下情况通常并不需要使用大世界坐标:

  • 2D 游戏,因为 2D 游戏中的精度问题通常不太明显。

  • 世界规模较小或中等的游戏。

  • 世界很大,但分为不同关卡并通过加载序列分隔的游戏。你可以将每个关卡的内容放置在其世界原点周围,从而在不产生性能开销的情况下避免精度问题。

  • 步行游玩区域不超过 8192×8192 米(以世界原点为中心)的开放世界游戏。如上表所示,即使对于第一人称游戏,该范围内的精度水平仍然可以接受。

如有疑虑,那么你的项目可能并不需要使用大世界坐标。作为参考,大多数现代 3A开放世界游戏并没有使用大世界坐标系统,而是仍然依靠单精度浮点数来处理渲染和物理。

启用大世界坐标

此过程需要重新编译编辑器以及你打算使用的所有导出模板二进制文件。如果只打算在发布模式下导出项目,则可以跳过调试导出模板的编译。无论如何,你都需要编译一个编辑器构建版本,以便你测试大精度世界时不必每次都导出项目。

请参阅编译部分,了解每个目标平台的编译说明。编译编辑器和导出模板时,需要添加 precision=double SCons 选项。

生成的二进制文件将以 .double 后缀命名,以区别于单精度二进制文件(没有任何精度后缀)。然后,你可以在“导出”对话框的导出预设中将二进制文件指定为自定义导出模板。

单精度和双精度构建之间的兼容性

当使用 ResourceSaver 单例保存二进制资源时,如果该资源是使用双精度构建保存的,则文件中会存储一个特殊标志。因此,每当你切换到双精度构建并保存资源时,磁盘上的二进制资源都会发生变化。

单精度和双精度构建都支持对使用此特殊标志的资源使用 ResourceLoader 单例。这意味着单精度构建可以加载使用双精度构建保存的资源,反之亦然。基于文本的资源不存储双精度标志,因为它们不需要这种标志也可正确读取。

已知的不兼容问题

  • 在网络多人游戏中,服务器和所有客户端都应使用相同的构建类型,以确保各客户端之间的精度保持一致。使用不同的构建类型或许也能运行,但可能会出现各种问题。

  • 在双精度构建中,GDExtension API 发生了不兼容的变化。这意味着扩展必须重新构建才能在双精度构建中工作。在扩展开发者的角度,当使用 precision=double 构建 GDExtension 时,会启用 REAL_T_IS_DOUBLE 宏定义。在单精度构建中,real_t 可用作 float 的别名;在双精度构建中,可用作 double 的别名。

限制

由于 3D 渲染着色器实际上并不使用双精度浮点数,因此在 3D 渲染精度方面存在一些限制:

  • 三平面映射无法从精度提升中获益。当远离世界原点时,使用三平面映射的材质会出现可见的抖动。

  • 禁用了 Local CoordsGPUParticles3D 节点无法从精度提升中获益。这可能导致在远离世界原点时出现可见的粒子吸附现象。启用了 Local Coords 的节点,以及 CPUParticles3D 节点,仍然可以从精度提升中获益。

  • 使用 skip_vertex_transformworld_vertex_coords 渲染模式的着色器无法从精度提升中获益。

  • 在双精度构建中,无法在着色器的 fragment() 函数中从视图空间重建世界空间坐标,例如:

    vec3 world = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
    

    相反,你可以在 vertex() 函数中计算世界空间坐标,并使用 varying 传递它们,例如:

    varying vec3 world;
    void vertex() {
        world = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
    }
    

2D 渲染目前无法从启用大世界坐标带来的精度提升中获益。这可能导致在远离世界原点时出现可见的模型吸附现象(在典型缩放级别下,从几百万像素开始)。不过 2D 物理计算仍然可以从精度提升中获益。