1
\$\begingroup\$

I recently made 2d simplex noise based coherent noise in GLSL. [Here] is the shader toy.

I wanted to make an implementation of simplex noise on the gpu in order to get better performance than the CPU implementation. I've yet to compare my cpu implementation, but it runs pretty fast, the only difference is that this is limited to single precision input. Another thing I wanted to see is if I could use my custom hash function on the GPU version. My hash function has two parts, first randomizing the inputs, then combining the randomized inputs, singlehash, and combinedHash. It's based on the Murmurhash3 avalanche mixer.

There are three main functions, simplexNoiseV, simplexNoiseDV, and simplexNoiseD, along with their respective octave accumulator functions (accumulateSimplexNoise). They mostly have the same code, except the D functions also need to calculate the derivatives, and V functions need to calculate noise value (DV calculates both).

I'm looking for performance suggestions, code quality suggestions, and/or algorithm quality suggestions. I have a tough time trying to get stuff readable in GLSL since there aren't many IDEs for it, and the language has some pretty annoying syntax.

Note: there is a define above main, DISPLAY_TYPE, changing the display type will display the noise, the derivative of the noise, the noise and the derivative on top of each-other, and a sliding window showing both side by side.

additionally there is another set of functions over in Common, those have nothing to do with the noise, and you can ignore them, they were copied from another project for my convenience and normalize the UV values to be the proper scale no matter the window size.

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


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


const float SQRT3 = 1.7320508075688772935274463415059;
const float SQRT2 = 1.4142135623730950488016887242096;

//contains a value and a vector derivative. 
struct vecd2{
    float v;
    vec2 d;
};

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

uint combineHash(uint seed, uvec2 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);
    return un;
}

float calcGradv(uint uval){
    //straight uint to float conversion will leave 
    //large swaths of zero values on sin(gradv) or cos(gradv)
    //taking only the bottom 16bits takes care of this
    //hash already combines top hash ^= hash >> 16;
    //so no "random-ness" is lost.
    float gradv = float(int(uval & 65535u));
    return gradv;

}

vec2 getGradient(uint uval){
    float gradv = calcGradv(uval);
    //look up tables on gpu are so slow, 
    //gradient calculation will run > 2x as slow with out this
    vec2 grad = vec2(cos(gradv), sin(gradv));
    return grad;
}


//source of some constants
//https://github.com/Auburns/FastNoise/blob/master/FastNoise.cpp
const float SKEW2D = 0.5 * (SQRT3 - 1.0);
const float UNSKEW2D = (3.0 - SQRT3) / 6.0;
const float FAR_CORNER_UNSKEW2D = -1.0 + 2.0*UNSKEW2D;
//wasn't getting consistent high values close to 1.0 or -1.0, so increased scale slightly, sqrt needed to un-normalize sin cos values to be sqrt(2) -> -sqrt(2) range. 
const float NORMALIZE_SCALE2D = 72.0 * SQRT2;
const float DISTCONST_2D = 0.5;
const uint SEED = 5u;

float simplexNoiseV(in vec2 pos){
    float skew_factor = (pos.x + pos.y)*SKEW2D;
    vec2 fsimplex_corner0 = floor(pos + skew_factor);
    ivec2 simplex_corner0 = ivec2(fsimplex_corner0);

    float unskew_factor = (fsimplex_corner0.x + fsimplex_corner0.y) * UNSKEW2D;
    vec2 pos0 = fsimplex_corner0 - unskew_factor;

    //subpos's are positions with in grid cell. 
    vec2 subpos0 = pos - pos0;
    //precomputed values used in determining hash, reduces redundant hash computation
    //shows 10% -> 20% speed boost. 
    uvec2 hashes_offset0 = singleHash(uvec2(simplex_corner0));
    uvec2 hashes_offset1 = singleHash(uvec2(simplex_corner0+1));
    //near corner hash value
    uint hashval0 = combineHash(SEED, hashes_offset0);
    //mid corner hash value
    uint hashval1;
    //far corner hash value
    uint hashval2 = combineHash(SEED, hashes_offset1);

    ivec2 simplex_corner1;
    if(subpos0.x > subpos0.y){
        hashval1 = combineHash(SEED, uvec2(hashes_offset1.x, hashes_offset0.y));
        simplex_corner1 =  ivec2(1, 0);
    }else{
        hashval1 = combineHash(SEED, uvec2(hashes_offset0.x, hashes_offset1.y));
        simplex_corner1 =  ivec2(0, 1);
    }

    vec2 subpos1 = subpos0 - vec2(simplex_corner1) + UNSKEW2D;
    vec2 subpos2 = subpos0 + FAR_CORNER_UNSKEW2D;
    float n0, n1, n2;

    //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_2D - subpos0.x*subpos0.x - subpos0.y*subpos0.y;
    //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;
        vec2 grad = getGradient(hashval0);
        float product = dot(subpos0, grad);
        n0 = t0_pow4 * product;
    }
    float t1 = 0.5 - subpos1.x*subpos1.x - subpos1.y*subpos1.y;
    if(t1 < 0.0){
        n1 = 0.0;
    }else{
        float t1_pow2 = t1 * t1;
        float t1_pow4 = t1_pow2*t1_pow2;
        vec2 grad = getGradient(hashval1);
        float product = dot(subpos1, grad);
        n1 = t1_pow4 * product;
    }
    float t2 = 0.5 - subpos2.x*subpos2.x - subpos2.y*subpos2.y;
    if(t2 < 0.0){
        n2 = 0.0;
    }else{
        float t2_pow2 = t2 * t2;
        float t2_pow4 = t2_pow2*t2_pow2;
        vec2 grad = getGradient(hashval2);
        float product = dot(subpos2, grad);
        n2 = t2_pow4 * product;
    }
    return (n0 + n1 + n2);
}

vecd2 simplexNoiseDV(in vec2 pos){
    float skew_factor = (pos.x + pos.y)*SKEW2D;
    vec2 fsimplex_corner0 = floor(pos + skew_factor);
    ivec2 simplex_corner0 = ivec2(fsimplex_corner0);

    float unskew_factor = (fsimplex_corner0.x + fsimplex_corner0.y) * UNSKEW2D;
    vec2 pos0 = fsimplex_corner0 - unskew_factor;

    //subpos's are positions with in grid cell. 
    vec2 subpos0 = pos - pos0;
    //precomputed values used in determining hash, reduces redundant hash computation
    //shows 10% -> 20% speed boost. 
    uvec2 hashes_offset0 = singleHash(uvec2(simplex_corner0));
    uvec2 hashes_offset1 = singleHash(uvec2(simplex_corner0+1));
    //near corner hash value
    uint hashval0 = combineHash(SEED, hashes_offset0);
    //mid corner hash value
    uint hashval1;
    //far corner hash value
    uint hashval2 = combineHash(SEED, hashes_offset1);

    ivec2 simplex_corner1;
    if(subpos0.x > subpos0.y){
        hashval1 = combineHash(SEED, uvec2(hashes_offset1.x, hashes_offset0.y));
        simplex_corner1 =  ivec2(1, 0);
    }else{
        hashval1 = combineHash(SEED, uvec2(hashes_offset0.x, hashes_offset1.y));
        simplex_corner1 =  ivec2(0, 1);
    }

    vec2 subpos1 = subpos0 - vec2(simplex_corner1) + UNSKEW2D;
    vec2 subpos2 = subpos0 + FAR_CORNER_UNSKEW2D;
    float n0, n1, n2;
    vec2 dn0, dn1, dn2;

    //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_2D - subpos0.x*subpos0.x - subpos0.y*subpos0.y;
    //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;
        dn0 = vec2(0.0);
    }else{
        float t0_pow2 = t0 * t0;
        float t0_pow3 = t0_pow2 * t0;
        float t0_pow4 = t0_pow2 * t0_pow2;
        vec2 grad = getGradient(hashval0);
        float product = dot(subpos0, grad);
        n0 = t0_pow4 * product;
        float coefA = t0_pow3 * product * -8.0;
        dn0 = (coefA * subpos0) + (t0_pow4 * grad);
    }
    float t1 = 0.5 - subpos1.x*subpos1.x - subpos1.y*subpos1.y;
    if(t1 < 0.0){
        n1 = 0.0;
        dn1= vec2(0.0);
    }else{
        float t1_pow2 = t1 * t1;
        float t1_pow3 = t1_pow2 * t1;
        float t1_pow4 = t1_pow2*t1_pow2;
        vec2 grad = getGradient(hashval1);
        float product = dot(subpos1, grad);
        n1 = t1_pow4 * product;
        float coefA = t1_pow3 * product * -8.0;
        dn1 = (coefA * subpos1) + (t1_pow4 * grad);
    }
    float t2 = 0.5 - subpos2.x*subpos2.x - subpos2.y*subpos2.y;
    if(t2 < 0.0){
        n2 = 0.0;
        dn2 = vec2(0.0);
    }else{
        float t2_pow2 = t2 * t2;
        float t2_pow3 = t2_pow2 * t2;
        float t2_pow4 = t2_pow2*t2_pow2;
        vec2 grad = getGradient(hashval2);
        float product = dot(subpos2, grad);
        n2 = t2_pow4 * product;
        float coefA = t2_pow3 * product * -8.0;
        dn2 = (coefA * subpos2) + (t2_pow4 * grad);
    }
    float v = (n0 + n1 + n2);
    return vecd2(v, vec2(dn0 + dn1 + dn2));
}

vec2 simplexNoiseD(in vec2 pos){
    float skew_factor = (pos.x + pos.y)*SKEW2D;
    vec2 fsimplex_corner0 = floor(pos + skew_factor);
    ivec2 simplex_corner0 = ivec2(fsimplex_corner0);

    float unskew_factor = (fsimplex_corner0.x + fsimplex_corner0.y) * UNSKEW2D;
    vec2 pos0 = fsimplex_corner0 - unskew_factor;

    //subpos's are positions with in grid cell. 
    vec2 subpos0 = pos - pos0;
    //precomputed values used in determining hash, reduces redundant hash computation
    //shows 10% -> 20% speed boost. 
    uvec2 hashes_offset0 = singleHash(uvec2(simplex_corner0));
    uvec2 hashes_offset1 = singleHash(uvec2(simplex_corner0+1));
    //near corner hash value
    uint hashval0 = combineHash(SEED, hashes_offset0);
    //mid corner hash value
    uint hashval1;
    //far corner hash value
    uint hashval2 = combineHash(SEED, hashes_offset1);

    ivec2 simplex_corner1;
    if(subpos0.x > subpos0.y){
        hashval1 = combineHash(SEED, uvec2(hashes_offset1.x, hashes_offset0.y));
        simplex_corner1 =  ivec2(1, 0);
    }else{
        hashval1 = combineHash(SEED, uvec2(hashes_offset0.x, hashes_offset1.y));
        simplex_corner1 =  ivec2(0, 1);
    }

    vec2 subpos1 = subpos0 - vec2(simplex_corner1) + UNSKEW2D;
    vec2 subpos2 = subpos0 + FAR_CORNER_UNSKEW2D;

    vec2 dn0, dn1, dn2;

    //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_2D - subpos0.x*subpos0.x - subpos0.y*subpos0.y;
    //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){
        dn0 = vec2(0.0);
    }else{
        float t0_pow2 = t0 * t0;
        float t0_pow3 = t0_pow2 * t0;
        float t0_pow4 = t0_pow2 * t0_pow2;
        vec2 grad = getGradient(hashval0);
        float product = dot(subpos0, grad);
        float coefA = t0_pow3 * product * -8.0;
        dn0 = (coefA * subpos0) + (t0_pow4 * grad);
    }
    float t1 = 0.5 - subpos1.x*subpos1.x - subpos1.y*subpos1.y;
    if(t1 < 0.0){
        dn1 = vec2(0.0);
    }else{
        float t1_pow2 = t1 * t1;
        float t1_pow3 = t1_pow2 * t1;
        float t1_pow4 = t1_pow2*t1_pow2;
        vec2 grad = getGradient(hashval1);
        float product = dot(subpos1, grad);
        float coefA = t1_pow3 * product * -8.0;
        dn1 = (coefA * subpos1) + (t1_pow4 * grad);
    }
    float t2 = 0.5 - subpos2.x*subpos2.x - subpos2.y*subpos2.y;
    if(t2 < 0.0){
        dn2 = vec2(0.0);
    }else{
        float t2_pow2 = t2 * t2;
        float t2_pow3 = t2_pow2 * t2;
        float t2_pow4 = t2_pow2*t2_pow2;
        vec2 grad = getGradient(hashval2);
        float product = dot(subpos2, grad);
        float coefA = t2_pow3 * product * -8.0;
        dn2 = (coefA * subpos2) + (t2_pow4 * grad);
    }
    return vec2(dn0 + dn1 + dn2);
}

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

vecd2 accumulateSimplexNoiseDV(in BrownianFractalSettings settings, vec2 pos){
    float accumulated_noise = 0.0;
    vec2 accumulated_dnoise = vec2(0.0);
    vec2 octave_pos = pos * settings.frequency;

    for (int octave = 0; octave < settings.octave_count; octave++) {
        vecd2 vdnoise = simplexNoiseDV(octave_pos);
        vdnoise.v *= pow(settings.persistence, float(octave));
        vdnoise.d *= pow(settings.persistence, float(octave));
        accumulated_noise += vdnoise.v;
        accumulated_dnoise += vdnoise.d;
        octave_pos *= settings.lacunarity;
    }
    float scale = 2.0 - pow(settings.persistence, float(settings.octave_count - 1));
    float noise = (accumulated_noise/scale) * NORMALIZE_SCALE2D * settings.amplitude;
    vec2 dnoise = (accumulated_dnoise/scale) * NORMALIZE_SCALE2D * settings.amplitude;
    return vecd2(noise, dnoise);
}


vec2 accumulateSimplexNoiseD(in BrownianFractalSettings settings, vec2 pos){
    vec2 accumulated_dnoise = vec2(0.0);
    vec2 octave_pos = pos * settings.frequency;

    for (int octave = 0; octave < settings.octave_count; octave++) {
        vec2 dnoise = simplexNoiseD(octave_pos);
        dnoise *= pow(settings.persistence, float(octave));
        accumulated_dnoise += dnoise;
        octave_pos *= settings.lacunarity;
    }
    float scale = 2.0 - pow(settings.persistence, float(settings.octave_count - 1));
    return (accumulated_dnoise/scale) * NORMALIZE_SCALE2D * settings.amplitude;
}

#define DISPLAY_NOISE 0
#define DISPLAY_DNOISE 1
#define DISPLAY_BOTH_NOISE 2
#define DISPLAY_MOVING 3

#define DISPLAY_TYPE 0

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from -1 to 1)
    vec2 uv = calculateUV(fragCoord, iResolution);
    BrownianFractalSettings fbm_settings = 
        BrownianFractalSettings(1, 2.0, 2.0, 0.5, 1.0);
    #if DISPLAY_TYPE == DISPLAY_NOISE
    float noise = accumulateSimplexNoiseV(fbm_settings, uv);
    noise = (noise+1.0)/2.0;
    vec3 color = vec3(noise);

    #elif DISPLAY_TYPE == DISPLAY_DNOISE
    vec2 dnoise = accumulateSimplexNoiseD(fbm_settings, uv);
    dnoise = normalize(dnoise);
    dnoise = (dnoise + 1.0)/2.0;
    vec3 color = vec3(dnoise,0.0);

    #elif DISPLAY_TYPE == DISPLAY_BOTH_NOISE
    vecd2 vdnoise = accumulateSimplexNoiseDV(fbm_settings, uv);
    float noise = vdnoise.v;
    noise = (noise+1.0)/2.0;
    vec2 dnoise = vdnoise.d;
    dnoise = normalize(dnoise);
    dnoise = (dnoise + 1.0)/2.0;
    dnoise *= 0.2;
    vec3 color = noise*0.8 + vec3(dnoise,0.0);

    #elif DISPLAY_TYPE == DISPLAY_MOVING
    vec3 color;
    if(uv.x < sin(iTime)*1.8){
        float noise = accumulateSimplexNoiseV(fbm_settings, uv);
        noise = (noise+1.0)/2.0;
        color = vec3(noise);
    }else{
        vec2 dnoise = accumulateSimplexNoiseD(fbm_settings, uv);
        dnoise = normalize(dnoise);
        dnoise = (dnoise + 1.0)/2.0;
        color = vec3(dnoise,0.0);
    }

    #endif

    fragColor = vec4(color,0.0);
}
\$\endgroup\$
  • \$\begingroup\$ Why do you need an IDE to achieve readable code? \$\endgroup\$ – yuri Mar 29 '18 at 19:54
  • \$\begingroup\$ @yuri easier to refactor and provide suggestions, with out an IDE, my names can actually be influenced by how hard it would be to find and replace them, and how hard annoying it is to write them and heavily effects how fast it is to program. With an IDE, these are pretty much non issues. \$\endgroup\$ – opa Mar 29 '18 at 19:58
  • \$\begingroup\$ Not sure if this is a common problem, but when I try to run your shader toy, it gets a syntax error on the first line. Running it in Safari 11.0.3 on macOS 10.13.3. \$\endgroup\$ – user1118321 Mar 30 '18 at 1:46
  • \$\begingroup\$ @user1118321 I'm using webgl 2.0 constructs, its possible safari doesn't support that, what syntax error is it saying? I've tried this on firefox and chrome with no errors. Unfortunately safari is usually behind the curve like edge. I just tried it in edge and found that it gave me an error about texel fetch, so that is probably the issue, that isn't available in webgl1.0 \$\endgroup\$ – opa Mar 30 '18 at 13:51
  • \$\begingroup\$ It's actually on "const" on line 1 for some reason. It's possible I have something misconfigured on my end, too. \$\endgroup\$ – user1118321 Mar 30 '18 at 15:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.