Super Mario 64 反编译中的浮点运算:f32 与定点数

Super Mario 64 反编译中的浮点运算:f32 与定点数

【免费下载链接】sm64 A Super Mario 64 decompilation, brought to you by a bunch of clever folks. 【免费下载链接】sm64 项目地址: https://gitcode.com/gh_mirrors/sm6/sm64

在经典游戏《超级马里奥64》的反编译过程中,数值计算的实现细节一直是开发者关注的焦点。本文将深入解析游戏如何在Nintendo 64的硬件限制下,巧妙平衡f32(32位浮点数)与定点数运算,实现流畅的物理效果与视觉表现。

数据类型基础:f32与定点数的定义

Super Mario 64的反编译代码中,f32类型通过typedef明确声明,作为游戏中主要的浮点计算类型。在include/types.h中可以看到:

typedef f32 Vec2f[2];
typedef f32 Vec3f[3]; // X, Y, Z, where Y is up
typedef f32 Vec4f[4];
typedef f32 Mat4[4][4];

这些向量和矩阵类型广泛应用于角色位置、摄像机变换等需要高精度计算的场景。与之对应的是定点数表示,游戏中主要通过s16(16位有符号整数)实现,如碰撞检测中的地形数据:

typedef s16 Collision; // Collision data is limited to -32768 to 32767
typedef TerrainData Vec3Terrain[3];

运算场景对比:何时使用f32,何时选择定点数?

1. 3D空间与物理模拟:f32的主场

游戏引擎核心模块大量使用f32进行三维空间计算。以src/goddard/gd_math.c中的向量运算为例:

f32 gd_vec3f_magnitude(struct GdVec3f *vec) {
    return gd_sqrt_f(SQ(vec->x) + SQ(vec->y) + SQ(vec->z));
}

void gd_normalize_vec3f(struct GdVec3f *vec) {
    f32 mag = gd_sqrt_f(SQ(vec->x) + SQ(vec->y) + SQ(vec->z));
    vec->x /= mag;
    vec->y /= mag;
    vec->z /= mag;
}

这些函数负责计算向量模长和归一化,直接影响马里奥的移动轨迹和碰撞检测精度。矩阵运算同样依赖f32,如视角变换矩阵的创建:

void gd_mat4f_lookat(Mat4f *mtx, f32 xFrom, f32 yFrom, f32 zFrom, f32 xTo, f32 yTo, f32 zTo,
                     f32 zColY, f32 yColY, f32 xColY) {
    // 计算摄像机观察矩阵的实现
    // ...
}

2. 资源受限场景:定点数的优势

在内存和计算资源受限的场景,如碰撞地形数据,游戏采用定点数存储。include/types.h中定义的Surface结构体展示了这种混合使用策略:

struct Surface {
    /*0x00*/ TerrainData type;       // 定点数:地形类型
    /*0x02*/ TerrainData force;      // 定点数:地面摩擦力
    /*0x04*/ s8 flags;
    /*0x05*/ RoomData room;
    /*0x06*/ TerrainData lowerY;     // 定点数:高度范围下限
    /*0x08*/ TerrainData upperY;     // 定点数:高度范围上限
    /*0x0A*/ Vec3Terrain vertex1;    // 定点数:三角形顶点
    /*0x10*/ Vec3Terrain vertex2;
    /*0x16*/ Vec3Terrain vertex3;
    /*0x1C*/ struct {                // f32:法线向量(用于光照计算)
        f32 x;
        f32 y;
        f32 z;
    } normal;
    /*0x28*/ f32 originOffset;       // f32:原点偏移量
};

这种设计既节省了存储空间(每个定点数分量仅占2字节,是f32的一半),又通过法线向量的f32表示保证了光照计算精度。

转换桥梁:f32与定点数的相互转换

游戏中存在大量两种数值类型的转换场景。例如在src/goddard/dynlist_proc.c中,地形数据从定点数转换为f32用于渲染:

tri.p0.x = (f32)(*halfarr)[0] * allocMtxScale;
tri.p0.y = (f32)(*halfarr)[1] * allocMtxScale;
tri.p0.z = (f32)(*halfarr)[2] * allocMtxScale;

反之,在物理碰撞检测前,需要将f32位置转换为定点数:

// 伪代码示例:f32位置转定点数碰撞坐标
s16 pos_to_collision(f32 world_pos) {
    return (s16)(world_pos * COLLISION_SCALE_FACTOR);
}

性能优化实践:f32运算的精度控制

为平衡精度与性能,反编译代码中实现了多种f32优化技巧。src/goddard/gd_math.c中的平方根计算通过双精度转单精度实现:

f32 gd_sqrt_f(f32 val) {
    return (f32) gd_sqrt_d(val); // gd_sqrt_d使用double精度计算
}

而向量归一化则通过条件判断避免除零错误:

s32 gd_normalize_vec3f(struct GdVec3f *vec) {
    f32 mag = SQ(vec->x) + SQ(vec->y) + SQ(vec->z);
    if (mag == 0.0f) {
        return FALSE;
    }
    mag = gd_sqrt_f(mag);
    if (mag == 0.0f) {
        vec->x = 0.0f;
        vec->y = 0.0f;
        vec->z = 0.0f;
        return FALSE;
    }
    // 执行归一化
    return TRUE;
}

总结:硬件约束下的数值计算艺术

Super Mario 64的反编译代码展示了N64时代开发者的智慧:在32位浮点运算能力有限的硬件上,通过f32与定点数的精妙配合,既实现了流畅的3D游戏体验,又严格控制了内存占用和计算开销。现代开发者可以从中学习到:

  1. 类型选择原则:空间位置和物理模拟用f32,UI元素和碰撞数据用定点数
  2. 精度权衡策略:关键计算保留更高精度,次要数据适当降低精度
  3. 硬件特性利用:针对N64的FPU特性优化运算顺序

这些经验对于嵌入式系统开发、低功耗设备编程等场景仍具有重要参考价值。

扩展阅读

【免费下载链接】sm64 A Super Mario 64 decompilation, brought to you by a bunch of clever folks. 【免费下载链接】sm64 项目地址: https://gitcode.com/gh_mirrors/sm6/sm64

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值