非立方区域的莫顿曲线,是 2 的幂

Posted

技术标签:

【中文标题】非立方区域的莫顿曲线,是 2 的幂【英文标题】:Morton curve for non cubic areas that are a power of two 【发布时间】:2021-03-26 12:57:01 【问题描述】:

在优化光线追踪器时,我尝试使用 Morton 空间填充曲线改善相交数据结构的数据局部性,但我的 3D 空间不是立方的(例如 512x512x256)。所有边都是 2 的幂,但并非所有边的长度都相同。

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

请注意宽度如何是 2x 高度,但它也可以是 3x 或 4x 或任何其他。我一直无法找到如何做到这一点。

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

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

【问题讨论】:

你知道宽度是否总是是高度的 2 倍吗? 另外,这个计算是由 GPU 完成的吗? (它会对性能产生巨大影响) @Jean-BenoitHarvey y x、y、z 的大小在编译时是已知的。 x/y 和 z 之间的关系是可变的。有时 x 是 z * 2,有时是 z * 4 或更高。这是在 GPU 上是的。我还编辑了主要帖子以突出显示这一点。 关于您的上一条评论,这是否意味着x 总是是最小的变量(您在编辑中提到“或任何其他”...宽度可以是 0.5 倍高度)? (或者有没有办法知道哪个变量最小)? x, y >= z。如果更容易,我也可以保证 x,y > z。 x == y也是这种情况。 【参考方案1】:

它可能会在 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 位):考虑到 Z 中的位数,您必须非等距地分布 X 和 Y。基本上,对于立方体,您均匀分布:位置的每一位 0, 3, 6, 9, 12, 15, 18, 21, 24, 为正交轴上的其他两位留出空间。

所以立方体是等距分布的。但是对于从 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;

【讨论】:

感谢您尝试解决方案,但这是一条常规的莫顿曲线。如果最后一个组件维度是其他维度的一半,则标准 morton 算法可以正常工作,因为它只是丢弃了最高有效位。我正在寻找一种莫顿曲线算法,其中最后一个分量可以是任意两个数的幂。 @Eejin 我同意,但你从 512x512x256 开始,我查看了我的代码,发现它正在工作。我同意对于其他 Z 尺寸应该有变化。我会调查的 @Eejin 我想我明白了,请检查更新。暂时没有代码

以上是关于非立方区域的莫顿曲线,是 2 的幂的主要内容,如果未能解决你的问题,请参考以下文章

如何将纬度和经度转换为莫顿码(z 阶曲线)

侧面带有可点击区域的 3D 可旋转立方体

零元学Expression Blend 4 - Chapter 37 看如何使用Clip修出想要的完美曲线(上)

编写一个跨步的x86基准测试

d3 曲线区域填充

Geogebra里给带有曲线和直线混合边界的封闭区域填充颜色