Noise
Improved Perlin Noise


return to main index



Introduction

Ken Perlin introduced a controlled form of randomness called noise in the 1980's. In 1997 he received a Technical Achievement Award from the Academy of Motion Picture Arts and Sciences for his work. Pixar's shading language, Houdini's Vex shading language and several other graphics environments have incorporated so-called Perlin noise into their API's.



Figure 1
Pnoise as input to a RenderMan height field


In 2002 Perlin introduced an improved implementation of noise. The source code given at his web site is written in the Java programming language. The code shown below are Python, Tcl and the 'C' language and are equivalent to his Java code. Apart from minor reformatting the only change I have made is in naming the principle proc/function pnoise.


Python

Listing 1 (pnoise.py)


import math
p = (
151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,
30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,
62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,
125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,
83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,
143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,
196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,
250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,
58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,
221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,
224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,
12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,
199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,
205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,
151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,
30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,
62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,
125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,
83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,
143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,
196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,
250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,
58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,
221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,
224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,
12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,
199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,
205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180)
  
def lerp(t, a, b):
    return a + t * (b - a)
  
def fade(t):
    return t * t * t * (t * (t * 6 - 15) + 10)
  
def grad(hash, x, y, z):
    h = hash & 15
    if h < 8:
        u = x
    else:
        u = y
    if h < 4:
        v = y
    elif h == 12 or h == 14:
        v = x
    else:
        v = z
    if h & 1 != 0:
        u = -u
    if h & 2 != 0:
        v = -v
    return u + v
  
def pnoise(x, y, z):
    global p
    X = int(math.floor(x)) & 255
    Y = int(math.floor(y)) & 255
    Z = int(math.floor(z)) & 255
    x -= math.floor(x)
    y -= math.floor(y)
    z -= math.floor(z)
    
    u = fade(x)
    v = fade(y)
    w = fade(z)
    
    A =  p[X] + Y
    AA = p[A] + Z
    AB = p[A + 1] + Z
    B =  p[X + 1] + Y
    BA = p[B] + Z
    BB = p[B + 1] + Z
    
    pAA = p[AA]
    pAB = p[AB]
    pBA = p[BA]
    pBB = p[BB]
    pAA1 = p[AA + 1]
    pBA1 = p[BA + 1]
    pAB1 = p[AB + 1]
    pBB1 = p[BB + 1]
    
    gradAA =  grad(pAA, x,   y,   z)
    gradBA =  grad(pBA, x-1, y,   z)
    gradAB =  grad(pAB, x,   y-1, z)
    gradBB =  grad(pBB, x-1, y-1, z)
    gradAA1 = grad(pAA1,x,   y,   z-1)
    gradBA1 = grad(pBA1,x-1, y,   z-1)
    gradAB1 = grad(pAB1,x,   y-1, z-1)
    gradBB1 = grad(pBB1,x-1, y-1, z-1)
    return lerp(w, 
    lerp(v, lerp(u, gradAA, gradBA), lerp(u, gradAB, gradBB)),
    lerp(v, lerp(u, gradAA1,gradBA1),lerp(u, gradAB1,gradBB1)))    

Listing 2 demonstrates how the pnoise.py can be used to generate a RenderMan height field.


Listing 2 (test_pnoise.py)


import math
from pnoise import pnoise
  
# The rib file will be written to the cutter directory
fileID = open("./heightfield.rib", 'w')
fileID.write('Basis "b-spline" 1 "b-spline" 1\n')
fileID.write('PatchMesh "bicubic" 30 "nonperiodic" 30 "nonperiodic" "Pz" [')
for i in range(900):
    x = i + math.sin(i)
    y = i + math.cos(i)
    fileID.write("%1.3f " % (pnoise(x,y,0)))
    if i % 12 == 0:
        fileID.write("\n")
fileID.write("]\n")
fileID.close()


Tcl

Listing 3 (pnoise.tcl)


set permutations {     
151 160 137 91 90 15 131 13 201 95 96 53 194 233 7 225 140 36 103 
30 69 142 8 99 37 240 21 10 23 190 6 148 247 120 234 75 0 26 197 
62 94 252 219 203 117 35 11 32 57 177 33 88 237 149 56 87 174 20 
125 136 171 168 68 175 74 165 71 134 139 48 27 166 77 146 158 231 
83 111 229 122 60 211 133 230 220 105 92 41 55 46 245 40 244 102 
143 54 65 25 63 161 1 216 80 73 209 76 132 187 208 89 18 169 200 
196 135 130 116 188 159 86 164 100 109 198 173 186 3 64 52 217 226 
250 124 123 5 202 38 147 118 126 255 82 85 212 207 206 59 227 47 16 
58 17 182 189 28 42 223 183 170 213 119 248 152 2 44 154 163 70 
221 153 101 155 167 43 172 9 129 22 39 253 19 98 108 110 79 113 
224 232 178 185 112 104 218 246 97 228 251 34 242 193 238 210 144 
12 191 179 162 241 81 51 145 235 249 14 239 107 49 192 214 31 181 
199 106 157 184 84 204 176 115 121 50 45 127 4 150 254 138 236 
205 93 222 114 67 29 24 72 243 141 128 195 78 66 215 61 156 180 }
  
proc lerp { t a b } {
    return [expr $a + $t * ($b - $a)]
    }
proc fade { t } {
    return [expr $t * $t * $t * ($t * ($t * 6 - 15) + 10)]
    }
proc grad { hash x y z } {
    set h [expr $hash & 15]
    set u [expr $h < 8 ? $x : $y]
    set v [expr $h < 4 ? $y : $h==12 || $h == 14 ? $x:$z]
    return [expr (($h&1) == 0 ? $u:-$u) + (($h&2) == 0 ? $v:-$v)]
    }
proc pnoise { x y z } {
    global permutations
    set p ""
    append p $permutations
    append p " "
    append p $permutations
    
    set X [expr int(floor($x)) & 255]
    set Y [expr int(floor($y)) & 255]
    set Z [expr int(floor($z)) & 255]    
    set x [expr $x - floor($x)]
    set y [expr $y - floor($y)]
    set z [expr $z - floor($z)]
    
    set u [fade $x]
    set v [fade $y]
    set w [fade $z]    
    set A  [expr [lindex $p $X] + $Y]
    set AA [expr [lindex $p $A] + $Z]
    set AB [expr [lindex $p [expr $A + 1] ] + $Z]
    
    set B  [expr [lindex $p [expr $X + 1] ] + $Y]
    set BA [expr [lindex $p $B] + $Z]
    set BB [expr [lindex $p [expr $B + 1] ] + $Z]
    set pAA  [lindex $p $AA]
    set pAB  [lindex $p $AB]
    set pBA  [lindex $p $BA]
    set pBB  [lindex $p $BB]
    set pAA1 [lindex $p [expr $AA + 1]]
    set pBA1 [lindex $p [expr $BA + 1]]
    set pAB1 [lindex $p [expr $AB + 1]]
    set pBB1 [lindex $p [expr $BB + 1]]
    
    set gradAA  [grad $pAA $x $y $z]
    set gradBA  [grad $pBA [expr $x - 1] $y $z]
    set gradAB  [grad $pAB $x [expr $y - 1] $z]
    set gradBB  [grad $pBB [expr $x - 1] [expr $y - 1] $z]
    set gradAA1 [grad $pAA1 $x $y [expr $z - 1]]
    set gradBA1 [grad $pBA1 [expr $x - 1] $y [expr $z - 1]]
    set gradAB1 [grad $pAB1 $x [expr $y - 1] [expr $z - 1]]
    set gradBB1 [grad $pBB1 [expr $x - 1] [expr $y - 1] [expr $z - 1]] 
    
    return [lerp $w [lerp $v [lerp $u $gradAA $gradBA] \
           [lerp $u $gradAB $gradBB]]  \
           [lerp $v [lerp $u $gradAA1 $gradBA1] \
           [lerp $u $gradAB1 $gradBB1]]]
    }

Listing 4 demonstrates how the pnoise.tcl can be used to generate a RenderMan height field.


Listing 4 (test_pnoise.tcl)


source "YOUR_PATH/pnoise.tcl"
  
set id [open ./heightfield.rib w]
puts $id "Basis \"b-spline\" 1 \"b-spline\" 1"
puts $id "PatchMesh \"bicubic\" 30 \"nonperiodic\" 30 \"nonperiodic\" \"Pz\" \["
  
for {set i 0} {$i < 900} {incr i} {
    set x [expr $i + sin([expr double($i)])]
    set y [expr $i + cos($i)]
    puts $id [format "%1.3f" [pnoise $x $y $i]]
    }
puts $id "\]\n"
close $id


Rendering a Height Field

Listing 5 (test_heightfiled.rib)


Display "untitled" "it" "rgb"
Format 427 240 1
Projection "perspective" "fov" 20
ShadingRate 1
  
LightSource "distantlight" 1 "intensity" 1.5 "from" [0 0 0] "to" [0 0 1]
  
Translate  0 0 3
Rotate -30 1 0 0
Rotate 30   0 1 0
Scale 1 1 -1
WorldBegin
    # Read the archive rib previously generated by
    # test_pnoise.py or test_pnoise.tcl. 
    Translate -0.5 0 0.5
    Rotate -90 1 0 0
    Scale 1 1 0.25
    ReadArchive "./heightfield.rib"
WorldEnd


'C'

Listing 6 (pnoise.c)


#include <math.h>
#include <stdio.h>
  
static int p[512];
static int permutation[] = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,
21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,
35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,
74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,
230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,
80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,
164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,
118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,
183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,
172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,
218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,
145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,
115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,
141,128,195,78,66,215,61,156,180
};
  
/* Function declarations */
double   fade(double t);
double   lerp(double t, double a, double b);
double   grad(int hash, double x, double y, double z); 
void     init_noise();
double   pnoise(double x, double y, double z);
  
void init_noise()
{
int i;
for(i = 0; i < 256 ; i++) 
    p[256+i] = p[i] = permutation[i]; 
}
  
double pnoise(double x, double y, double z) 
{
int   X = (int)floor(x) & 255,             /* FIND UNIT CUBE THAT */
      Y = (int)floor(y) & 255,             /* CONTAINS POINT.     */
      Z = (int)floor(z) & 255;
x -= floor(x);                             /* FIND RELATIVE X,Y,Z */
y -= floor(y);                             /* OF POINT IN CUBE.   */
z -= floor(z);
double  u = fade(x),                       /* COMPUTE FADE CURVES */
        v = fade(y),                       /* FOR EACH OF X,Y,Z.  */
        w = fade(z);
int  A = p[X]+Y, 
     AA = p[A]+Z, 
     AB = p[A+1]+Z, /* HASH COORDINATES OF */
     B = p[X+1]+Y, 
     BA = p[B]+Z, 
     BB = p[B+1]+Z; /* THE 8 CUBE CORNERS, */
  
return lerp(w,lerp(v,lerp(u, grad(p[AA  ], x, y, z),   /* AND ADD */
                     grad(p[BA  ], x-1, y, z)),        /* BLENDED */
             lerp(u, grad(p[AB  ], x, y-1, z),         /* RESULTS */
                     grad(p[BB  ], x-1, y-1, z))),     /* FROM  8 */
             lerp(v, lerp(u, grad(p[AA+1], x, y, z-1 ),/* CORNERS */
                     grad(p[BA+1], x-1, y, z-1)),      /* OF CUBE */
             lerp(u, grad(p[AB+1], x, y-1, z-1),
                     grad(p[BB+1], x-1, y-1, z-1))));
}
  
double fade(double t){ return t * t * t * (t * (t * 6 - 15) + 10); }
double lerp(double t, double a, double b){ return a + t * (b - a); }
double grad(int hash, double x, double y, double z) 
{
int     h = hash & 15;       /* CONVERT LO 4 BITS OF HASH CODE */
double  u = h < 8 ? x : y,   /* INTO 12 GRADIENT DIRECTIONS.   */
        v = h < 4 ? y : h==12||h==14 ? x : z;
return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
}




© 2002- Malcolm Kesson. All rights reserved.