Value noise 2D/3D (Java & GLSL)

As mentioned on the wiki page Noise (bandpassed white), value noise is very defective. However defective is not the same as useless. Value noise has an advantage of being very fast in low dimensions. It simple usages its defects can be an advantage of giving a certain “look” and in more complex usages the defeats can be hidden by how the samples are mixed.

The java version is a mixed implementation. It restricts itself to a single cell per evaluation for speed purposes, otherwise the complexity becomes too high for it to be of any potential interest. On the other hand it uses an excellent hashing function and uses a first order continuous weighting function, both of which add in reducing defects. Note that the hashing functions chosen have short dependency chains and probably performs better than one might expect.


public final class ValueNoise
{
  private static final int pseudoFloor(float x)
  {
    return x >= 0 ? (int)x : (int)x-1;
  }

  private static final float weight(float t)
  {
    return (t*t*(3.f-(t+t)));
  }
  
  private static final float normalizeI(int h)
  {
    return (h>>>7)*0x1.0p-24f - 1;
  }

  private static final int M = 0x5bd1e995;

  private static final int postHashM2(int h)
  {
    h ^= h >>> 13; h *= M; h ^= h >>> 15;

    return h;
  }

  private static final int preHashM2(int x)
  {
    int h = 0x9747b28c; 

    x *= M; x ^= x >>> 24; x *= M;

    h ^= x; h *= M;

    return h;
  }

  private static final int hashM2(int x)
  {
    return postHashM2(preHashM2(x));
  }

  private static final int preHashM2(int x, int y)
  {
    int h = 0x9747b28c; 

    x *= M; x ^= x >>> 24; x *= M;
    y *= M; y ^= y >>> 24; y *= M;

    h ^= x; h *= M; h ^= y;

    return h;
  }

  private static final int hashM2(int x, int y)
  {
    return postHashM2(preHashM2(x,y));
  }

  private static final int preHashM2(int x, int y, int z)
  {
    int h = 0x9747b28c; 

    x *= M; x ^= x >>> 24; x *= M;
    y *= M; y ^= y >>> 24; y *= M;
    z *= M; z ^= z >>> 24; z *= M;

    h ^= x; h *= M; 
    h ^= y; h *= M; 
    h ^= z;

    return h;
  }

  private static final int hashM2(int x, int y, int z)
  {
    return postHashM2(preHashM2(x,y,z));
  }

  private static final float mix(int x)
  {
    int h = hashM2(x); 

    return normalizeI(h);
  }

  /** Sample 1D function */
  public static final float calc(float x)
  {
    int   ix = pseudoFloor(x);
    float dx = ease(x-ix);
    float r0 = mix(ix);
    float r1 = mix(ix+1);

    return lerp(dx, r0, r1);
  }

  private static final float mix(int x, int y)
  {
    return normalizeI(hashM2(x,y));
  }

  /** Sample 2D function */
  public static final float calc(float x, float y) 
  {
    // get the coordinate of the cell
    int ix = pseudoFloor(x);
    int iy = pseudoFloor(y);

    // compute the offset into the cell, then weight it
    x = weight(x-ix);
    y = weight(y-iy);

    // compute hash values for the four vertices of the cell and
    // convert into a uniform float
    float r00 = mix(ix,   iy);
    float r10 = mix(ix+1, iy);
    float r01 = mix(ix,   iy+1);
    float r11 = mix(ix+1, iy+1);

    float xb = lerp(x, r00, r10);  // lerp bottom edge
    float xt = lerp(x, r01, r11);  // lerp top edge

    return lerp(y, xb, xt);  // lerp the two edges into the final result
  }

  private static final float mix(int x, int y, int z)
  {
    int h = hashM2(x,y,z); 

    return normalizeI(h);
  }

  /** Sample 3D function */
  public static final float calc(float x, float y, float z) 
  {
    float t0,t1,t2,t3,t4;

    // lower left hand coordinate of cell
    int ix = pseudoFloor(x);
    int iy = pseudoFloor(y);
    int iz = pseudoFloor(z);

    // offset into cell, then convert to weighting value
    x = weight(x-ix);
    y = weight(y-iy);
    z = weight(z-iz);

    // bottom edge of forward face
    t0 = mix(ix,   iy, iz);
    t1 = mix(ix+1, iy, iz);
    t2 = lerp(x, t0, t1);

    // top edge of forward face
    t0 = mix(ix,   iy+1, iz);
    t1 = mix(ix+1, iy+1, iz);
    t3 = lerp(x, t0, t1);
    t3 = lerp(y, t2, t3);                // final result in forward face

    // bottom edge of back face
    t0 = mix(ix,   iy, iz+1);
    t1 = mix(ix+1, iy, iz+1);
    t2 = lerp(x, t0, t1);

    // top edge of back face
    t0 = mix(ix,   iy+1, iz+1);
    t1 = mix(ix+1, iy+1, iz+1);
    t4 = lerp(x, t0, t1);
    t4 = lerp(y, t2, t4);

    return lerp(z, t3, t4);
  }
}

GLSL version using permutation polynomials for hashing/random number:


#version 150 core

// Permutation polynomial RNG/Hashing from Ashima Arts. 
float mod289(float x) { return x - floor(x * (1.0/289.0)) * 289.0; }
vec2  mod289(vec2 x)  { return x - floor(x * (1.0/289.0)) * 289.0; }
vec3  mod289(vec3 x)  { return x - floor(x * (1.0/289.0)) * 289.0; }
vec4  mod289(vec4 x)  { return x - floor(x * (1.0/289.0)) * 289.0; }
vec2  permute(vec2 x) { return mod289(((x*34.0)+1.0)*x); }
vec3  permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
vec4  permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }

float rng(float x)  { return fract(x * (1.f/41.f)); }
vec2  rng(vec2 x)   { return fract(x * (1.f/41.f)); }
vec3  rng(vec3 x)   { return fract(x * (1.f/41.f)); }
vec4  rng(vec4 x)   { return fract(x * (1.f/41.f)); }
float ease(float t) { return (t*t*(3.f-(t+t))); }
vec2  ease(vec2 t)  { return (t*t*(3.f-(t+t))); }
vec3  ease(vec3 t)  { return (t*t*(3.f-(t+t))); }
vec4  ease(vec4 t)  { return (t*t*(3.f-(t+t))); }

// sample 2D noise function
float vnoise(vec2 v)
{
  vec4  c, h;
  vec2  b, ds;
  float r; 

  c  = floor(v.xxyy) + vec4(0,1,0,1);  // cell coords: (x0,x1,y0,y1)
  ds = ease(fract(v));                 // offset into cell -> weighting function
  c  = mod289(c);

  // x=hash(x0,y0) : y=hash(x1,y0) : z=hash(x0,y1) : w=hash(x1,y1)
  h  = permute(permute(c.xyxy) + c.zzww);
  h  = rng(h);
  b  = mix(h.xz, h.yw, ds.x);          // lerp top and bottom edges
  r  = mix(b.x, b.y, ds.y);            // lerp in 'y'

  return r+r-1;
}

// sample 3D noise function
float vnoise(vec3 v)
{
  vec4 c  = floor(v.xxyy) + vec4(0,1,0,1);  // cell coords: (x0,x1,y0,y1)
  vec2 z  = floor(v.zz)   + vec2(0,1);      // cell coords: (z0,z1)
  vec3 ds = ease(fract(v));                 // offset into cell -> weighting function
  
  c  = mod289(c);

  // x=hash(x0,y0) : y=hash(x1,y0) : z=hash(x0,y1) : w=hash(x1,y1)
  vec4 h  = permute(permute(c.xyxy) + c.zzww);
  vec4 ff = rng(permute(h + z.xxxx));
  vec4 bf = rng(permute(h + z.yyyy));
  vec2  t = mix(ff.xz, ff.yw, ds.x);  // lerp top and bottom edges (front face)
  vec2  s = mix(bf.xz, bf.yw, ds.x);  // lerp top and bottom edges (back face)
  float a = mix(t.x, t.y, ds.y);
  float b = mix(s.x, s.y, ds.y);
  float r = mix(a, b, ds.z);

  return r+r-1;
}

// EXAMPLE 

// Turbulence formulated to insure that cell structures
// are aligned, assuming input coordinates are on 0,1 or
// -1,1, and therefore making defects compound (most
// obvious).
float turb(vec2 v, float s)
{
  float n, r;
  n  = vnoise(s*v.xy); r  = abs((1/ 2.f)*(n)); s += s;
  n  = vnoise(s*v.xy); r += abs((1/ 4.f)*(n)); s += s;
  n  = vnoise(s*v.xy); r += abs((1/ 8.f)*(n)); s += s;
  n  = vnoise(s*v.xy); r += abs((1/16.f)*(n));

  return r;
}

in  vec4 coord;
out vec4 out_Color;

void main(void)
{
  vec4  c;
  float f;

//f = vnoise(10*coord.xy); f = (f+1)*0.5;
  f = turb(coord.xy, 5.f);
  c = vec4(f,f,f,1);

  out_Color.rgb = c.rgb;
}

That GLSL code isn’t valid. At least my drivers won’t allow the function name “uniform”.

[quote]Shader compile log:
0(9) : error C0000: syntax error, unexpected ‘}’, expecting ‘,’ or ‘)’ at token “}”
0(21) : error C0000: syntax error, unexpected ‘=’, expecting “::” at token “=”
0(39) : error C1038: declaration of “ds” conflicts with previous declaration at 0(18)
0(41) : error C0000: syntax error, unexpected ‘=’, expecting “::” at token “=”
0(45) : error C1101: ambiguous overloaded function reference “permute(vec4)”
0(7) : vec2 permute(vec2)
0(6) : vec3 permute(vec3)
0(46) : error C1101: ambiguous overloaded function reference “permute(vec4)”
0(7) : vec2 permute(vec2)
0(6) : vec3 permute(vec3)
0(50) : error C1038: declaration of “b” conflicts with previous declaration at 0(18)
0(50) : error C7011: implicit cast from “float” to “vec2”
0(51) : error C1031: swizzle mask element not present in operand “z”
0(51) : warning C1068: array index out of bounds
0(51) : error C7011: implicit cast from “float” to “vec2”
0(51) : error C1038: declaration of “r” conflicts with previous declaration at 0(19)
0(53) : error C0000: syntax error, unexpected reserved word “return” at token “return”
[/quote]
You’re passing lots of vec4s into your functions… BTW, this was after renaming uniform() to something else so that’s not the problem.

Over 200 lines of codes in a forum post? Not the pastebin? :expressionless:

This is the Shared Code board. What else did you expect?

Thanks for sharing!

Gosh, I guess my cut-and-paste from large file and doing a little clean-up pass in forum post exercise failed. Indeed uniform is a keyword…opps! and functionName(paramList} isn’t proper syntax. All of that is corrected…now with a trivial example which shows defects at their worst (more or less).

Looks better. I’ll try it when I get back home. =D

Just for fun I quickly hacked a out version using the rng you have on the tricks page:


float vnoise(vec2 v)
{
  vec2  c   = floor(v);
  vec2  ds  = ease(fract(v));

  float h00 = rand(c);
  float h10 = rand(c + vec2(1,0));
  float h01 = rand(c + vec2(0,1));
  float h11 = rand(c + vec2(1,1));

  float b   = mix(h00,h10, ds.x);
  float t   = mix(h01,h11, ds.x);
  float r   = mix(b,t,     ds.y);

  return r+r-1;
}

Strangely the half Rey generator (from theagentd’s trick post) version is much faster on my hardware…humm.