【问题标题】:Compute gradient for voxel data efficiently有效计算体素数据的梯度
【发布时间】:2014-02-11 21:56:15
【问题描述】:

对于固定大小的体素数据,计算梯度的最有效方法是什么,例如下面的源代码。请注意,我需要空间中任何点的渐变。梯度将用于估计行进立方体实现中的法线。

#import <array>

struct VoxelData {
    VoxelData(float* data, unsigned int xDim, unsigned int yDim, unsigned int zDim)
    :data(data), xDim(xDim), yDim(yDim), zDim(zDim)
    {}

    std::array<float,3> get_gradient(float x, float y, float z){
        std::array<float,3> res;
        // compute gradient efficiently
        return res;
    }

    float get_density(int x, int y, int z){
        if (x<0 || y<0 || z<0 || x >= xDim || y >= yDim || z >= zDim){
            return 0;
        }
        return data[get_element_index(x, y, z)];
    }

    int get_element_index(int x, int y, int z){
        return x * zDim * yDim + y*zDim + z;
    }

    const float* const data;

    const unsigned int xDim;
    const unsigned int yDim;
    const unsigned int zDim;

};

更新 1 可以在此处找到该问题的演示项目:

https://github.com/mortennobel/OpenGLVoxelizer

目前输出如下图(基于 MooseBoys 代码):

更新 2 我正在寻找的解决方案必须提供相当准确的渐变,因为它们在可视化中用作法线,并且必须避免像下面这样的视觉伪影。

更新 2 用户示例的解决方案是:

【问题讨论】:

  • 我很感兴趣为什么你需要任何点的梯度。看来这将是任何容易优化的机会。此外,点的规则间距(与三角形大小变化的典型阴影相反)可能比逐点 get_gradient 提供更好的方法。您是否考虑过这种可能性?
  • 有一个潜在的性能优化,因为我只需要渐变立方体生成的每个顶点,这些顶点总是有两个位于整数的轴(换句话说,这可以简化计算到两个线性插值和一个三线性插值)。
  • 如果每个网格点都有梯度(向量 [x,y,z]),那么(梯度的)三线性插值将变为沿一个轴的线性插值(非积分一)。另一方面,您可以将梯度计算为插值密度场的导数。我不知道它有什么优点/缺点,但它会更慢。你打算用哪种方式?
  • 您从哪里获取数据?通过使用生成体素的原始数据源而不是体素本身,我得到了惊人的平滑结果。前提是数据采用可以区分的形式,例如平滑字段的总和,这会产生漂亮的结果。
  • @AndyNewman:我处理的体素数据是基于 FEM(有限元方法)的计算(更具体地说是 3D 中的多网格拓扑优化)的结果。 AFAIK 梯度,必须使用插值模式基于体素计算结果。我主要关心的是如何使用合理的快速方法获得最佳的视觉效果(我实际上不认为性能会是一个大问题)。

标签: c++ raytracing voxel marching-cubes


【解决方案1】:

以下产生线性插值梯度场:

std::array<float,3> get_gradient(float x, float y, float z){
    std::array<float,3> res;
    // x
    int xi = (int)(x + 0.5f);
    float xf = x + 0.5f - xi;
    float xd0 = get_density(xi - 1, (int)y, (int)z);
    float xd1 = get_density(xi, (int)y, (int)z);
    float xd2 = get_density(xi + 1, (int)y, (int)z);
    res[0] = (xd1 - xd0) * (1.0f - xf) + (xd2 - xd1) * xf; // lerp
    // y
    int yi = (int)(y + 0.5f);
    float yf = y + 0.5f - yi;
    float yd0 = get_density((int)x, yi - 1, (int)z);
    float yd1 = get_density((int)x, yi, (int)z);
    float yd2 = get_density((int)x, yi + 1, (int)z);
    res[1] = (yd1 - yd0) * (1.0f - yf) + (yd2 - yd1) * yf; // lerp
    // z
    int zi = (int)(z + 0.5f);
    float zf = z + 0.5f - zi;
    float zd0 = get_density((int)x, (int)y, zi - 1);
    float zd1 = get_density((int)x, (int)y, zi);
    float zd2 = get_density((int)x, (int)y, zi + 1);
    res[2] = (zd1 - zd0) * (1.0f - zf) + (zd2 - zd1) * zf; // lerp
    return res;
}

【讨论】:

  • 没有更准确的估计梯度的方法吗?我尝试实施您的建议,但对所找到的渐变质量感到非常失望。
  • @Mortennobel 你对准确性的定义是什么?有多种方法可以在离散数据点之间插入值,线性是一种常见的方法。另一种是三次插值,它产生更平滑的结果(一阶和二阶导数都是连续的),但会导致过冲。
  • 嗯,我只是在寻找一种能够提供视觉上令人愉悦的结果(即更平滑的结果)的方法。
  • @Mortennobel 您要求一种有效的方法,而不是一种准确的方法(或者您称之为视觉上更流畅),也许您可​​以将其添加为原始问题的评论?
【解决方案2】:

在许多实现中用于优化的一项重要技术涉及时间/空间权衡。作为建议,任何可以预先计算和缓存结果的地方都可能值得一看。

【讨论】:

    【解决方案3】:

    一般而言,Sobel 滤波器提供的结果比简单的集中趋势略好,但计算时间更长(Sobel 本质上是一个结合集中趋势的平滑滤波器)。经典的 Sobel 需要加权 26 个样本,而集中趋势只需要 6 个。但是,有一个技巧:使用 GPU,您可以免费获得基于硬件的三线性插值。这意味着您可以计算具有 8 个纹理读取的 Sobel,这可以在 GPU 上并行完成。以下页面说明了使用 GLSL 的这种技术 http://www.mccauslandcenter.sc.edu/mricrogl/notes/gradients 对于您的项目,您可能希望在 GPU 上计算梯度并使用 GPGPU 方法将结果从 GPU 复制回 CPU 以进行进一步处理。

    【讨论】:

      【解决方案4】:

      MooseBoys 已经发布了一个组件式线性插值。但它在 y 和 z 组件中是不连续的,无论 (int)x 从一个值更改为下一个值(其他组件也是如此)。这可能会导致您看到的画面如此粗糙。如果您有足够的性能,您可以通过不仅考虑(int)x 还考虑(int)(x+1) 来改进这一点。这可能如下所示

      std::array<float,3> get_gradient(float x, float y, float z){
          std::array<float,3> res;
      
          int xim = (int)(x + 0.5f);
          float xfm = x + 0.5f - xi;
          int yim = (int)(y + 0.5f);
          float yfm = y + 0.5f - yi;
          int zim = (int)(z + 0.5f);
          float zfm = z + 0.5f - zi;
          int xi = (int)x;
          float xf = x - xi;
          int yi = (int)y;
          float yf = y - yi;
          int zi = (int)z;
          float zf = z - zi;
      
      
          float xd0 = yf*(          zf *get_density(xim - 1, yi+1, zi+1) 
                          + (1.0f - zf)*get_density(xim - 1, yi+1, zi))
                      +(1.0f - yf)*(zf *get_density(xim - 1, yi  , zi+1) 
                          + (1.0f - zf)*get_density(xim - 1, yi  , zi));
          float xd1 = yf*(          zf *get_density(xim,     yi+1, zi+1) 
                          + (1.0f - zf)*get_density(xim,     yi+1, zi))
                      +(1.0f - yf)*(zf *get_density(xim,     yi  , zi+1) 
                          + (1.0f - zf)*get_density(xim,     yi  , zi));
          float xd2 = yf*(          zf *get_density(xim + 1, yi+1, zi+1) 
                          + (1.0f - zf)*get_density(xim + 1, yi+1, zi))
                      +(1.0f - yf)*(zf *get_density(xim + 1, yi  , zi+1) 
                          + (1.0f - zf)*get_density(xim + 1, yi  , zi));
          res[0] = (xd1 - xd0) * (1.0f - xfm) + (xd2 - xd1) * xfm;
      
          float yd0 = xf*(          zf *get_density(xi+1, yim-1, zi+1) 
                          + (1.0f - zf)*get_density(xi+1, yim-1, zi))
                      +(1.0f - xf)*(zf *get_density(xi  , yim-1, zi+1) 
                          + (1.0f - zf)*get_density(xi  , yim-1, zi));
          float yd1 = xf*(          zf *get_density(xi+1, yim  , zi+1) 
                          + (1.0f - zf)*get_density(xi+1, yim  , zi))
                      +(1.0f - xf)*(zf *get_density(xi  , yim  , zi+1) 
                          + (1.0f - zf)*get_density(xi  , yim  , zi));
          float yd2 = xf*(          zf *get_density(xi+1, yim+1, zi+1) 
                          + (1.0f - zf)*get_density(xi+1, yim+1, zi))
                      +(1.0f - xf)*(zf *get_density(xi  , yim+1, zi+1) 
                          + (1.0f - zf)*get_density(xi  , yim+1, zi));
          res[1] = (yd1 - yd0) * (1.0f - yfm) + (yd2 - yd1) * yfm;
      
          float zd0 = xf*(          yf *get_density(xi+1, yi+1, zim-1) 
                          + (1.0f - yf)*get_density(xi+1, yi  , zim-1))
                      +(1.0f - xf)*(yf *get_density(xi,   yi+1, zim-1) 
                          + (1.0f - yf)*get_density(xi,   yi  , zim-1));
          float zd1 = xf*(          yf *get_density(xi+1, yi+1, zim) 
                          + (1.0f - yf)*get_density(xi+1, yi  , zim))
                      +(1.0f - xf)*(yf *get_density(xi,   yi+1, zim) 
                          + (1.0f - yf)*get_density(xi,   yi  , zim));
          float zd2 = xf*(          yf *get_density(xi+1, yi+1, zim+1) 
                          + (1.0f - yf)*get_density(xi+1, yi  , zim+1))
                      +(1.0f - xf)*(yf *get_density(xi,   yi+1, zim+1) 
                          + (1.0f - yf)*get_density(xi,   yi  , zim+1));
          res[2] = (zd1 - zd0) * (1.0f - zfm) + (zd2 - zd1) * zfm;
          return res;
      }
      

      这可能写得更简洁一些,但也许这样你仍然可以看到正在发生的事情。如果这仍然不够平滑,您将不得不研究三次/样条插值或类似的方法。

      【讨论】:

      • 我执行了你的建议,但结果看起来很糟糕。 (有关屏幕截图,请参阅问题更新)。
      • @Mortennobel uff。它至少应该和 mooseboys 版本一样好。可能是某种符号错误。下班回家后让我查看我的代码。
      • @Mortennobel 在 yd0 的计算中缺少“-1”。不幸的是,我无法在没有太多努力的情况下编译您的代码 - 但我在代码中找不到任何其他错误。希望这是导致工件的错误...
      • 修复了工件。但是,我没有发现视觉效果比 MooseBoys 建议的解决方案更好。
      猜你喜欢
      • 2017-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-14
      • 2013-07-20
      • 1970-01-01
      • 1970-01-01
      • 2019-11-01
      相关资源
      最近更新 更多