Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compute gradient for voxel data efficiently

What is the most efficient way of computing the gradient for fixed sized voxel data, such as the source code below. Note that I need the gradient at any point in space. The gradients will be used for estimating normals in a marching cubes implementation.

#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;

};

Update 1 A demo project of the problem can be found here:

https://github.com/mortennobel/OpenGLVoxelizer

Currently the output is like the picture below (based on MooseBoys code):

Update 2 The solution that I'm looking for must give fairly accurate gradients, since they are used as normals in a visualisation and visual artefacts like the ones below must be avoided.

enter image description here

Update 2 Solution from the user example is:

enter image description here

like image 275
Mortennobel Avatar asked Jan 22 '14 02:01

Mortennobel


3 Answers

The following produces a linearly interpolated gradient field:

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;
}
like image 144
MooseBoys Avatar answered Oct 18 '22 03:10

MooseBoys


One important technique for optimization in many implementations involves time/space trade off. As a suggestion, anywhere you can pre-calc and cache your results may be worth looking at.

like image 23
Brad Thomas Avatar answered Oct 18 '22 04:10

Brad Thomas


In general Sobel filters provide slightly nicer results than simple central tendency, but take longer to compute (the Sobel is essentially a smooth filter combined with central tendency). A classic Sobel requires weighting 26 samples, while central tendency only requires 6. However, there is a trick: with GPUs you can get hardware-based trilinear interpolation for free. That means you can compute a Sobel with 8 texture reads, and this can be done in parallel across the GPU. The following page illustrates this technique using GLSL http://www.mccauslandcenter.sc.edu/mricrogl/notes/gradients For your project you would probably want to compute the gradients on the GPU and use GPGPU methods to copy the results back from the GPU to the CPU for further processing.

like image 2
user1677899 Avatar answered Oct 18 '22 03:10

user1677899