非立方区域的 Morton 曲线是 2 的幂

Morton curve for non cubic areas that are a power of two

在优化光线追踪器时,我尝试使用 Morton space 填充曲线改进交叉数据结构的数据局部性,除了我的 3D space 不是立方体(例如 512x512x256)。所有边都是2的幂,但不是所有边都一样长。

我一直无法找到边为 2 的幂的非正方形 Morton 曲线的任何示例。如果重要的话,我可以保证 x/y 轴的大小相同,只是 z 轴的长度不同。


请注意宽度是 2x 高度,但它也可以是 3x 或 4x 或任何其他。我一直无法找到执行此操作的方法。

理想情况下,解决方案会很快,因为必须大量计算 Morton 代码。所以我的问题是:如何为非三次 spaces 生成 space 填充莫顿曲线?这是专门针对 GPU (Cuda) 的。

尺寸条件为:
x, y, z 是二的幂
x == y
x, y >= z
或者如果更容易
x, y > z

它可能会因为 nz=11 而中断,但是对于 XY 正方形的一半大小它似乎对我有用

#include <cstdint>
#include <iostream>

static inline uint32_t spread(uint32_t x)
{
    x = (x | (x << 10)) & 0x000F801F;
    x = (x | (x << 4)) & 0x00E181C3;
    x = (x | (x << 2)) & 0x03248649;
    x = (x | (x << 2)) & 0x09249249;

    return x;
}

static inline uint32_t morton(const uint32_t x, const uint32_t y, const uint32_t z)
{
    return spread(x) << 0 | spread(y) << 1 | spread(z) << 2;
}


auto main() -> int {
    int nx = 32;
    int ny = 32;
    int nz = 16;

    for (int iz = 0; iz != nz; ++iz)
    {
        for (int iy = 0; iy != ny; ++iy)
        {
            for (int ix = 0; ix != nx; ++ix)
            {
                auto m = morton(ix, iy, iz);

                std::cout << m << '\n';
            }
        }
    }

    return 0;
}

更新

如何使 Morton 代码适用于 256x256x64(8 位*8 位*6 位):您必须非等距离地展开 X 和 Y,同时考虑 Z 中的位数。基本上,对于您展开的立方体均匀地:每一位都在位置上 0, 3, 6, 9, 12, 15, 18, 21, 24, 为来自正交轴的其他两位留下 space。

所以立方体是等距分布的。但是对于从 Z 插入只有 6 位的情况,您必须有 6 个 3 的距离,但最后一个间隙没有 Z 位,因此 X 和 Y 扩展的最后一个间隙应该只有 1 位宽。因此,X 和 Y 的非等距分布。

沿线的东西:如果 Nx=Ny 是 XY 平面中的位数,并且 Nz!=Nx 或 Ny 是沿 Z 轴的位数, Nz 位的扩展间隙应为 2 位,剩余的间隙应为 1 位。所以有两个传播例程 - 一个用于具有非等距传播的 X&Y,现在取决于 Nz,以及用于 Z 轴的现有传播函数。

好的,这是一个工作版本,似乎做对了

#include <cstdint>
#include <iostream>

#define func auto

func spreadZ(uint32_t v) -> uint32_t { // 2bit gap spread
    v = (v | (v << 10)) & 0x000F801F;
    v = (v | (v << 4)) & 0x00E181C3;
    v = (v | (v << 2)) & 0x03248649;
    v = (v | (v << 2)) & 0x09249249;

    return v;
}

func spreadXY(const uint32_t v, const uint32_t bitsZ) -> uint32_t {
    uint32_t mask_z = (1U << bitsZ) - 1U; // to mask bits which are going to have 2bit gap

    uint32_t lo{ v & mask_z }; // lower part of the value where there are Z bits
    lo = spreadZ(lo);          // 2bit gap spread

    uint32_t hi = v >> bitsZ; // high part of the value, 1bit gap

    // 1bit gap spread
    hi = (hi ^ (hi << 8)) & 0x00ff00ffU;
    hi = (hi ^ (hi << 4)) & 0x0f0f0f0fU;
    hi = (hi ^ (hi << 2)) & 0x33333333U;
    hi = (hi ^ (hi << 1)) & 0x55555555U;

    return lo + (hi << 3*bitsZ); // combine them all together
}

func morton(const uint32_t x, const uint32_t y, const uint32_t z, const uint32_t bitsZ) -> uint32_t {
    return spreadXY(x, bitsZ) << 0 | spreadXY(y, bitsZ) << 1 | spreadZ(z) << 2;
}

func ispowerof2(const uint32_t n) -> bool {
    return n && (!(n & (n - 1u)));
}

func bit_pos(const uint32_t n) -> uint32_t {
    if (!ispowerof2(n))
        throw -1;

    uint32_t i{ 1u }, pos{ 1u };

    while (!(i & n)) { // Iterate through bits of n till we find a set bit, i&n will be non-zero only when 'i' and 'n' have a same bit         
        i = i << 1; // unset current bit and set the next bit in 'i' 

        ++pos; // increment position 
    }

    return pos;
}

func main() -> int {
    int nx = 256;
    int ny = 256;

    int nz = 256; //256...128...64...32...16...8...4...2...1 all works
    int bitsZ = bit_pos(nz) - 1; // should be doing try/catch

    for (int iz = 0; iz != nz; ++iz)
    {
        for (int iy = 0; iy != ny; ++iy)
        {
            for (int ix = 0; ix != nx; ++ix)
            {
                auto m = morton(ix, iy, iz, bitsZ);

                std::cout << m << '\n';
            }
        }
    }

    return 0;
}