vendredi 31 août 2018

Why is wrapping coordinates not making my simplex noise tile seamlessly?

I've been trying to create a fake 3D texture that repeats in shadertoy (see here, use wasd to move, arrow keys to rotate) But as you can see, it doesn't tile.

I generate the noise myself, and I've isolated the noise generation in this minimal example, however it does not generate seamlessly tileable noise seemingly no matter what I do.

Here is the code:

//Common, you probably won't have to look here. 
vec2 modv(vec2 value, float modvalue){
    return vec2(mod(value.x, modvalue), 
                mod(value.y, modvalue));
}
vec3 modv(vec3 value, float modvalue){
    return vec3(mod(value.x, modvalue), 
                mod(value.y, modvalue),
                mod(value.z, modvalue));
}
vec4 modv(vec4 value, float modvalue){
    return vec4(mod(value.x, modvalue), 
                mod(value.y, modvalue),
                mod(value.z, modvalue),
                mod(value.w, modvalue));
}

//MATH CONSTANTS
const float pi  = 3.1415926535897932384626433832795;
const float tau = 6.2831853071795864769252867665590;
const float eta = 1.5707963267948966192313216916397;
const float SQRT3 = 1.7320508075688772935274463415059;
const float SQRT2 = 1.4142135623730950488016887242096;
const float LTE1 =  0.9999999999999999999999999999999;
const float inf = uintBitsToFloat(0x7F800000u);

#define saturate(x) clamp(x,0.0,1.0)
#define norm01(x) ((x + 1.0) / 2.0)

vec2 pos3DTo2D(in vec3 pos, 
               const in int size_dim, 
               const in ivec2 z_size){
    float size_dimf = float(size_dim);
    pos = vec3(mod(pos.x, size_dimf), mod(pos.y, size_dimf),  mod(pos.z, size_dimf));
    int z_dim_x = int(pos.z) % z_size.x;
    int z_dim_y = int(pos.z) / z_size.x;
    float x = pos.x + float(z_dim_x * size_dim);
    float y = pos.y + float(z_dim_y * size_dim);
    return vec2(x,y);
}

vec4 textureAs3D(const in sampler2D iChannel, 
                 in vec3 pos, 
                 const in int size_dim, 
                 const in ivec2 z_size,
                 const in vec3 iResolution){
    //only need whole, will do another texture read to make sure interpolated?

    vec2 tex_pos = pos3DTo2D(pos, size_dim, z_size)/iResolution.xy;
    vec4 base_vec4 = texture(iChannel, tex_pos);

    vec2 tex_pos_z1 = pos3DTo2D(pos+vec3(0.0,0.0,1.0), size_dim, z_size.xy)/iResolution.xy;
    vec4 base_vec4_z1 = texture(iChannel, tex_pos_z1);
    //return base_vec4;
    return mix(base_vec4, base_vec4_z1, fract(pos.z));
}

vec4 textureZ3D(const in sampler2D iChannel, 
                 in int y,
                 in int z,
                 in int offsetX,
                 const in int size_dim, 
                 const in ivec2 z_size,
                const in vec3 iResolution){
    int tx = (z%z_size.x);
    int ty = z/z_size.x;
    int sx = offsetX + size_dim * tx;
    int sy = y  + (ty *size_dim);
    if(ty < z_size.y){
        return texelFetch(iChannel, ivec2(sx, sy),0);
    }else{
        return vec4(0.0);
    }
    //return texelFetch(iChannel, ivec2(x, y - (ty *32)),0);
}

//Buffer B this is what you are going to have to look at. 
//noise

//NOISE CONSTANTS
// captured from https://en.wikipedia.org/wiki/SHA-2#Pseudocode
const uint CONST_A = 0xcc9e2d51u;
const uint CONST_B = 0x1b873593u;
const uint CONST_C = 0x85ebca6bu;
const uint CONST_D = 0xc2b2ae35u;
const uint CONST_E = 0xe6546b64u;
const uint CONST_F = 0x510e527fu;
const uint CONST_G = 0x923f82a4u;
const uint CONST_H = 0x14292967u;

const uint CONST_0 = 4294967291u;
const uint CONST_1 = 604807628u;
const uint CONST_2 = 2146583651u;
const uint CONST_3 = 1072842857u;
const uint CONST_4 = 1396182291u;
const uint CONST_5 = 2227730452u;
const uint CONST_6 = 3329325298u;
const uint CONST_7 = 3624381080u;




uvec3 singleHash(uvec3 uval){
    uval ^= uval >> 16;
    uval.x *= CONST_A;
    uval.y *= CONST_B;
    uval.z *= CONST_C;
    return uval;
}

uint combineHash(uint seed, uvec3 uval){
    // can move this out to compile time if need be. 
    // with out multiplying by one of the randomizing constants
    // will result in not very different results from seed to seed. 
    uint un = seed * CONST_5;
    un ^= (uval.x^uval.y)* CONST_0;
    un ^= (un >> 16);
    un = (un^uval.z)*CONST_1;
    un ^= (un >> 16);
    return un;
}

/* 
//what the above hashes are based upon, seperate 
//out this mumurhash based coherent noise hash
uint fullHash(uint seed, uvec3 uval){
    uval ^= uval >> 16;
    uval.x *= CONST_A;
    uval.y *= CONST_B;
    uval.z *= CONST_D;
    uint un = seed * CONST_6;
    un ^= (uval.x ^ uval.y) * CONST_0;
    un ^= un >> 16;
    un = (un^uval.z) * CONST_2;
    un ^= un >> 16;
    return un;
}
*/

const vec3 gradArray3d[8] = vec3[8](
    vec3(1, 1, 1), vec3(1,-1, 1), vec3(-1, 1, 1), vec3(-1,-1, 1),
    vec3(1, 1,-1), vec3(1,-1,-1), vec3(-1, 1,-1), vec3(-1,-1,-1)
);


vec3 getGradient3Old(uint uval){
    vec3 grad = gradArray3d[uval & 7u];
    return grad;
}

//source of some constants
//https://github.com/Auburns/FastNoise/blob/master/FastNoise.cpp
const float SKEW3D = 1.0 / 3.0;
const float UNSKEW3D = 1.0 / 6.0;
const float FAR_CORNER_UNSKEW3D = -1.0 + 3.0*UNSKEW3D;
const float NORMALIZE_SCALE3D = 30.0;// * SQRT3;
const float DISTCONST_3D = 0.6;

float simplexNoiseV(uint seed, in vec3 pos, in uint wrap){
    pos = modv(pos, float(wrap));
    float skew_factor = (pos.x + pos.y + pos.z)*SKEW3D;
    vec3 fsimplex_corner0 = floor(pos + skew_factor);
    ivec3 simplex_corner0 = ivec3(fsimplex_corner0);

    float unskew_factor = (fsimplex_corner0.x + fsimplex_corner0.y + fsimplex_corner0.z) * UNSKEW3D;
    vec3 pos0 = fsimplex_corner0 - unskew_factor;

    //subpos's are positions with in grid cell. 
    vec3 subpos0 = pos - pos0;
    //precomputed values used in determining hash, reduces redundant hash computation
    //shows 10% -> 20% speed boost. 
    uvec3 wrapped_corner0 = uvec3(simplex_corner0);
    uvec3 wrapped_corner1 = uvec3(simplex_corner0+1);
    wrapped_corner0 = wrapped_corner0 % wrap;
    wrapped_corner1 = wrapped_corner1 % wrap;

    //uvec3 hashes_offset0 = singleHash(uvec3(simplex_corner0));
    //uvec3 hashes_offset1 = singleHash(uvec3(simplex_corner0+1));
    uvec3 hashes_offset0 = singleHash(wrapped_corner0);
    uvec3 hashes_offset1 = singleHash(wrapped_corner1);
    //near corner hash value
    uint hashval0 = combineHash(seed, hashes_offset0);
    //mid corner hash value
    uint hashval1;

    uint hashval2;
    //far corner hash value
    uint hashval3 = combineHash(seed, hashes_offset1);

    ivec3 simplex_corner1;
    ivec3 simplex_corner2;
    if (subpos0.x >= subpos0.y)
    {
        if (subpos0.y >= subpos0.z)
        {
            hashval1 = combineHash(seed, uvec3(hashes_offset1.x, hashes_offset0.yz));
            hashval2 = combineHash(seed, uvec3(hashes_offset1.xy, hashes_offset0.z));
            simplex_corner1 = ivec3(1,0,0);
            simplex_corner2 = ivec3(1,1,0);
        }
        else if (subpos0.x >= subpos0.z)
        {
            hashval1 = combineHash(seed, uvec3(hashes_offset1.x, hashes_offset0.yz));
            hashval2 = combineHash(seed, uvec3(hashes_offset1.x, hashes_offset0.y, hashes_offset1.z));
            simplex_corner1 = ivec3(1,0,0);
            simplex_corner2 = ivec3(1,0,1);
        }
        else // subpos0.x < subpos0.z
        {
            hashval1 = combineHash(seed, uvec3(hashes_offset0.xy, hashes_offset1.z));
            hashval2 = combineHash(seed, uvec3(hashes_offset1.x, hashes_offset0.y, hashes_offset1.z));
            simplex_corner1 = ivec3(0,0,1);
            simplex_corner2 = ivec3(1,0,1);
        }
    }
    else // subpos0.x < subpos0.y
    {
        if (subpos0.y < subpos0.z)
        {
            hashval1 = combineHash(seed, uvec3(hashes_offset0.xy, hashes_offset1.z));
            hashval2 = combineHash(seed, uvec3(hashes_offset0.x, hashes_offset1.yz));
            simplex_corner1 = ivec3(0,0,1);
            simplex_corner2 = ivec3(0,1,1);
        }
        else if (subpos0.x < subpos0.z)
        {
            hashval1 = combineHash(seed, uvec3(hashes_offset0.x, hashes_offset1.y, hashes_offset0.z));
            hashval2 = combineHash(seed, uvec3(hashes_offset0.x, hashes_offset1.yz));
            simplex_corner1 = ivec3(0,1,0);
            simplex_corner2 = ivec3(0,1,1);
        }
        else // subpos0.x >= subpos0.z
        {
            hashval1 = combineHash(seed, uvec3(hashes_offset0.x, hashes_offset1.y, hashes_offset0.z));
            hashval2 = combineHash(seed, uvec3(hashes_offset1.xy, hashes_offset0.z));
            simplex_corner1 = ivec3(0,1,0);
            simplex_corner2 = ivec3(1,1,0);
        }
    }

    //we would do this if we didn't want to seperate the hash values. 
    //hashval0 = fullHash(seed, uvec3(simplex_corner0));
    //hashval1 = fullHash(seed, uvec3(simplex_corner0+simplex_corner1));
    //hashval2 = fullHash(seed, uvec3(simplex_corner0+simplex_corner2));
    //hashval3 = fullHash(seed, uvec3(simplex_corner0+1));

    vec3 subpos1 = subpos0 - vec3(simplex_corner1) + UNSKEW3D;
    vec3 subpos2 = subpos0 - vec3(simplex_corner2) + 2.0*UNSKEW3D;
    vec3 subpos3 = subpos0 + FAR_CORNER_UNSKEW3D;
    float n0, n1, n2, n3;

    //http://catlikecoding.com/unity/tutorials/simplex-noise/
    //circle distance factor to make sure second derivative is continuous
    // t variables represent (1 - x^2 + y^2 + ...)^3, a distance function with 
    // continous first and second derivatives that are zero when x is one. 
    float t0 = DISTCONST_3D - subpos0.x*subpos0.x - subpos0.y*subpos0.y - subpos0.z*subpos0.z;
    //if t < 0, we get odd dips in continuity at the ends, so we just force it to zero
    // to prevent it
    if(t0 < 0.0){
        n0 = 0.0;
    }else{
        float t0_pow2 = t0 * t0;
        float t0_pow4 = t0_pow2 * t0_pow2;
        vec3 grad = getGradient3Old(hashval0);
        float product = dot(subpos0, grad);
        n0 = t0_pow4 * product;
    }
    float t1 = DISTCONST_3D - subpos1.x*subpos1.x - subpos1.y*subpos1.y - subpos1.z*subpos1.z;
    if(t1 < 0.0){
        n1 = 0.0;
    }else{
        float t1_pow2 = t1 * t1;
        float t1_pow4 = t1_pow2 * t1_pow2;
        vec3 grad = getGradient3Old(hashval1);
        float product = dot(subpos1, grad);
        n1 = t1_pow4 * product;
    }
    float t2 = DISTCONST_3D - subpos2.x*subpos2.x - subpos2.y*subpos2.y - subpos2.z*subpos2.z;
    if(t2 < 0.0){
        n2 = 0.0;
    }else{
        float t2_pow2 = t2 * t2;
        float t2_pow4 = t2_pow2*t2_pow2;
        vec3 grad = getGradient3Old(hashval2);
        float product = dot(subpos2, grad);
        n2 = t2_pow4 * product;
    }

    float t3 = DISTCONST_3D - subpos3.x*subpos3.x - subpos3.y*subpos3.y - subpos3.z*subpos3.z;
    if(t3 < 0.0){
        n3 = 0.0;
    }else{
        float t3_pow2 = t3 * t3;
        float t3_pow4 = t3_pow2*t3_pow2;
        vec3 grad = getGradient3Old(hashval3);
        float product = dot(subpos3, grad);
        n3 = t3_pow4 * product;
    }
    return (n0 + n1 + n2 + n3);
}

//settings for fractal brownian motion noise
struct BrownianFractalSettings{
    uint seed;
    int octave_count;
    float frequency;
    float lacunarity;
    float persistence;
    float amplitude;
};

float accumulateSimplexNoiseV(in BrownianFractalSettings settings, vec3 pos, float wrap){
    float accumulated_noise = 0.0;
    wrap *= settings.frequency;
    vec3 octave_pos = pos * settings.frequency;
    for (int octave = 0; octave < settings.octave_count; octave++) {
        octave_pos = modv(octave_pos, wrap);
        float noise = simplexNoiseV(settings.seed, octave_pos, uint(wrap));
        noise *= pow(settings.persistence, float(octave));
        accumulated_noise += noise;
        octave_pos *= settings.lacunarity;
        wrap *= settings.lacunarity;
    }
    float scale = 2.0 - pow(settings.persistence, float(settings.octave_count - 1));
    return (accumulated_noise/scale) * NORMALIZE_SCALE3D * settings.amplitude;
}

const float FREQUENCY = 1.0/8.0;
const float WRAP = 32.0;

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    //set to zero in order to stop scrolling, scrolling shows the lack of tilability between
    //wrapping. 
    const float use_sin_debug = 1.0;
    vec3 origin = vec3(norm01(sin(iTime))*64.0*use_sin_debug,0.0,0.0);
    vec3 color = vec3(0.0,0.0,0.0);
    BrownianFractalSettings brn_settings = 
        BrownianFractalSettings(203u, 1, FREQUENCY, 2.0, 0.4, 1.0);
    const int size_dim = 32;
    ivec2 z_size = ivec2(8, 4);
    ivec2 iFragCoord = ivec2(fragCoord.x, fragCoord.y);
    int z_dim_x = iFragCoord.x / size_dim;
    int z_dim_y = iFragCoord.y / size_dim;

    if(z_dim_x < z_size.x && z_dim_y < z_size.y){
        int ix = iFragCoord.x % size_dim;
        int iy = iFragCoord.y % size_dim;
        int iz = (z_dim_x) + ((z_dim_y)*z_size.x);
        vec3 pos = vec3(ix,iy,iz) + origin; 
        float value = accumulateSimplexNoiseV(brn_settings, pos, WRAP);
        color = vec3(norm01(value));
    }else{
        color = vec3(1.0,0.0,0.0);
    }
    fragColor = vec4(color,1.0);
}

//Image, used to finally display
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    const float fcm = 4.0;
    //grabs a single 32x32 tile in order to test tileability, currently generates
    //a whole array of images however. 
    vec2 fragCoordMod = vec2(mod(fragCoord.x, 32.0 * fcm), mod(fragCoord.y, 32.0 * fcm));
    vec3 color = texture(iChannel2, fragCoordMod/(fcm*iResolution.xy)).xyz;
    fragColor = vec4(color, 1.0);
}

What I've tried position % wrap value, modifying wrap value by lacunarity, and after warp % wrap value, which are currently in use (look in simplexNoiseV for the core algorithm, accumulateSimplexNoiseV for the octave summation).

According to these answers it should be that simple (mod position used for hashing), however this clearly just doesn't work. I'm not sure if it's partially because my hashing function is not Ken Perlin's, but it doesn't seem like that should make a difference. It does seem the skewing of coordinates should make this method not work at all, but apparently others have had success with this.

Here's an example of it not tiling: enter image description here

Why is wrapping coordinates not making my simplex noise tile seamlessly?




Aucun commentaire:

Enregistrer un commentaire