计算固定大小的体素数据的梯度的最有效方法是什么,例如下面的源代码。请注意,我需要在空间的任何位置使用渐变。渐变将用于估计行进立方体实现中的法线。
#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 用户示例的解决方案是:
答案 0 :(得分:2)
一般来说,Sobel滤波器提供的结果比简单的集中趋势更好,但需要更长的时间来计算(Sobel本质上是一个平滑的滤波器,结合了集中趋势)。经典Sobel需要加权26个样本,而集中趋势只需要6个。但是,有一个技巧:使用GPU可以免费获得基于硬件的三线性插值。这意味着您可以计算具有8个纹理读取的Sobel,这可以在GPU上并行完成。以下页面使用GLSL说明了这种技术 http://www.mccauslandcenter.sc.edu/mricrogl/notes/gradients 对于您的项目,您可能希望计算GPU上的渐变并使用GPGPU方法将结果从GPU复制回CPU以进行进一步处理。
答案 1 :(得分: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;
}
答案 2 :(得分:1)
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;
}
这可能写得更简洁,但也许这样你仍然可以看到发生了什么。如果这仍然不够平滑,你将不得不考虑立方/样条插值或类似。
答案 3 :(得分:1)
在许多实现中优化的一种重要技术涉及时间/空间权衡。作为建议,任何可以预先计算和缓存结果的地方都值得一看。