Generating seamless repeated 3D-textures (repeated voxel objects)

When creating 3D textures (voxel objects), it's may be required to make it "seamless repeated", that means that if the textures placed joined together, the gap between them are smooth (invisible).
For example, it's useful for filling the whole 3D space with repeated texture in a raymarching-based project.

Hint: Such whole 3D space filling by a repeated 3D texture gives feeling of repeating in the space and may be desirable. But, if you need to eliminate such repeatng feeling, for example, for rendering clouds using 3D-texture, you may mix two same textures but with different scales (1 and 1.2225) and shifted. Maximal color -  max(texture1, texture2) works good for the clouds.

Here we explain method for making any 3D texture (voxel object) seamless repeated. Note, the resulted object will be smaller due this procedure.

It's based on idea of mixing border voxels with changed mixing value. Normally such technique is used for creating smooth music loops and repeated 2D-textures.

For simplicity, we will consider a case where 3D texture is a cube, 4-channel (RGBA).

Code

---------------------------------------
    unsigned int w0=256, h0=256, d0=256;   //destination cube size
    unsigned int channels = 4;  //number of color channels

    unsigned int Q = int(w0*0.15);  //size of the border for mixing
    //We use one value Q for all dimensions, because we proposed it's a cube.
    //For none-cube, just use Qw,Qh,Qd variables

    //Create a cube with bigger size for seamless repeating procedure
    unsigned int w=w0+Q;
    unsigned int h=h0+Q;
    unsigned int d=d0+Q;

    vector<unsigned char> Volume(w*h*d*channels); //voxel cube

    //-----------------------------------------------------------------
    //... here is some filling of the Volume by values...
    //Please, fill it by yourself by code or by loading from the some volume file
    //remeber - size is w x h x d
    //-----------------------------------------------------------------

    //Now make seamless repeated borders of Volume in size w0 x h0 x d0
    //We do it in three psses, axis by axis: X,Y,Z.
    //So result will be depending on the order of axis.
    //For most cases it's not noticeable, but, if your project sensitive on this,
    //please use more careful formulas, which it make such mixing in one pass.

    typedef unsigned int uint_;  //for shortening code, denote "unsigned int" as "unit_"

    //X
    for (uint_ x = 0; x < Q; x++) {
        float t = (1 - float(x) / Q); //1..0
        for (uint_ z = 0; z < d; z++) {
            for (uint_ y = 0; y < h; y++) {
                uint_ j = channels * (x + w * (y + h * z));
                uint_ i = channels * (x+w0 + w * (y + h * z));
                //mix
                for (int k = 0; k < channels; k++) {
                    Volume[j + k] = Volume[j + k] * (1 - t) + Volume[i + k] * t;
                }
            }
        }
    }

    //Y
    for (uint_ y = 0; y < Q; y++) {
        float t = (1 - float(y) / Q); //1..0
        for (uint_ x = 0; x < w; x++) {
            for (uint_ z = 0; z < d; z++) {
                uint_ j = channels * (x + w * (y + h * z));
                uint_ i = channels * (x + w * (y + h0 + h * z));
                //mix
                for (int k = 0; k < channels; k++) {
                    Volume[j + k] = Volume[j + k] * (1 - t) + Volume[i + k] * t;
                }
            }
        }
    }

    //Z
    for (uint_ z = 0; z < Q; z++) {
        float t = (1 - float(z) / Q); //1..0
        for (uint_ y = 0; y < h; y++) {
            for (uint_ x = 0; x < w; x++) {
                uint_ j = channels * (x + w * (y + h * z));
                uint_ i = channels * (x + w * (y + h * (z+d0)));
                //mix
                for (int k = 0; k < channels; k++) {
                    Volume[j + k] = Volume[j + k] * (1 - t) + Volume[i + k] * t;
                }
            }
        }
    }


    //Now Volume is seamless repeated inside size w0 x h0 x d0.
    //All we need copy this part of the object to the resulted ResVolume.

    //Define the resulted voxel volume
    vector<unsigned char>  ResVolume(w0*h0*d0*channels);

    //Copy
    for (uint_ z = 0; z < d0; z++) {
        for (uint_ y = 0; y < h0; y++) {
            for (uint_ x = 0; x < w0; x++) {
                uint_ i0 = channels * (x + w0 * (y + h0 * z));
                uint_ i = channels * (x + w * (y + h * z));
                for (int k = 0; k < channels; k++) {
                    ResVolume[i0 + k] = Volume[i + k];
                }
            }
        }
    }
---------------------------------------

So, in the given code Volume is a original voxel object with size w x h x d, and ResVolume is a seamless repeated voxel object with size w0 x h0 x d0.

Comments

Popular posts from this blog

Computing ray origin and direction from Model View Projection matrices for raymarching

Forward and backward alpha blending for raymarching

Forward, Deferred and Raytracing rendering in openFrameworks and web