Skip to content

Instantly share code, notes, and snippets.

@toja
Last active March 8, 2023 00:26
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save toja/0e798c8dd54eb53ea0f0aeef414d16c9 to your computer and use it in GitHub Desktop.
Save toja/0e798c8dd54eb53ea0f0aeef414d16c9 to your computer and use it in GitHub Desktop.
Perlin noise - 3D Demo
license: gpl-3.0
height: 480
border: no

Perlin noise with THREE.js: In this example a 3D perlin noise is used to generate a spherical texture and a bump map. The texture is than applied to a sphere and displayed with THREE.js.

<!DOCTYPE html>
<meta charset=utf-8>
<style>
#container {
width: 960px;
height: 480px;
}
canvas {
width: 100%;
height: 100%
}
</style>
<body>
<div id="container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="./noise.js"></script>
<script>
// Utils
//
// degrees to radians conversion
function deg2rad (deg) {
return Math.PI / 180 * deg;
}
// get sperical coordinates for pixel x, y
function getCoords(x, y) {
theta = deg2rad(x / 512 * 360 - 180);
phi = deg2rad(y / 256 * 180 - 90);
rho = 0.2;
_x = rho * Math.cos(phi) * Math.cos(theta);
_y = rho * Math.cos(phi) * Math.sin(theta);
_z = rho * Math.sin(phi); // z is 'up'
return [_x, _y, _z];
}
// Noise generation with Fractional Brownian Motion
function fbm(octaves) {
var noise = Noise.perlin3D;
return function (x, y, z, t) {
frequency = 1;
amplitude = 1;
persistence = 0.7;
lacunarity = 2;
var total = 0;
for(var i = 0; i < octaves; i++) {
total += noise (x * frequency, y * frequency, z * frequency, t * frequency) * amplitude;
amplitude *= persistence;
frequency *= lacunarity;
}
return total;
}
};
var data = new Uint8Array(512 * 256 * 4);
var bump = new Uint8Array(512 * 256 * 4);
var noise = fbm(8);
var time = 0;
var scale = d3.interpolateRgb("#381b0a", '#c9ba8a');
// generate texture and bump map
for (var y = 0; y < 256; y++) {
for (var x = 0; x < 512; x++) {
index = (x + y * 512) * 4;
xyz = getCoords(x, y);
n = noise(xyz[0], xyz[1], xyz[2], time);
n = Math.abs(Math.cos(y/64 + noise(xyz[0], xyz[1], xyz[2], time)));
col = d3.color(d3.interpolateRainbow(n));
data[index ] = col.r; // n * 220;
data[index + 1] = col.g; // n * 180;
data[index + 2] = col.b; // * 160;
data[index + 3] = 255;
bump[index ] = n * 255; // n * 220;
bump[index + 1] = n * 255; // n * 180;
bump[index + 2] = n * 255; // * 160;
bump[index + 3] = 255;
}
}
// set texture and bump map
var texture = new THREE.DataTexture(data, 512, 256, THREE.RGBAFormat, THREE.UnsignedByteType, THREE.EquirectangularReflectionMapping);
texture.unpackAlignment = 1;
texture.needsUpdate = true;
var bumpMap = new THREE.DataTexture(bump, 512, 256, THREE.RGBAFormat, THREE.UnsignedByteType, THREE.EquirectangularReflectionMapping);
bumpMap.unpackAlignment = 1;
bumpMap.needsUpdate = true;
// Set the scene size.
const WIDTH = 960;
const HEIGHT = 480;
// Set some camera attributes.
const VIEW_ANGLE = 45;
const ASPECT = WIDTH / HEIGHT;
const NEAR = 0.1;
const FAR = 10000;
// Get the DOM element to attach to
const container = document.querySelector('#container');
// Create a WebGL renderer, camera and a scene
const renderer = new THREE.WebGLRenderer();
const camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf6f4f2);
// Lights
const light = new THREE.AmbientLight( 0x606060 ); // soft white light
scene.add( light );
var dirLight = new THREE.DirectionalLight(0xe0e0e0, 1);
dirLight.position.set(90, 90, 50);
scene.add(dirLight);
// Add the camera to the scene.
scene.add(camera);
// Start the renderer.
renderer.setSize(WIDTH, HEIGHT);
// Attach the renderer-supplied
// DOM element.
container.appendChild(renderer.domElement);
// Create a new mesh with sphere geometry
// Set up the sphere vars
const RADIUS = 50;
const SEGMENTS = 32;
const RINGS = 32;
const sphereMaterial1 = new THREE.MeshPhongMaterial( { emissive: new THREE.Color("rgb(7,3,5)"),
specular: new THREE.Color("rgb(64,64,64)"), shininess: 10, bumpMap: bumpMap, bumpScale: 0.6, map: texture, transparent: false});
const sphere = new THREE.Mesh( new THREE.SphereGeometry( RADIUS, SEGMENTS, RINGS), sphereMaterial1);
// Move the Sphere back in Z so we
// can see it.
sphere.position.z = -200;
// Finally, add the sphere to the scene.
scene.add(sphere);
function update () {
renderer.render(scene, camera);
sphere.rotation.x += 0.002;
sphere.rotation.y += 0.001;
// Schedule the next frame.
requestAnimationFrame(update);
}
// Schedule the first frame.
requestAnimationFrame(update);
</script>
/**
* Perlin Noise functions for 1D, 2D, 3D and 4D.
*
* The 3D-Noise is a port of Ken Perlin's Java code. The
* original Java code is at http://cs.nyu.edu/%7Eperlin/noise/.
*
* 1D, 2D and 4D versions are simple variations of Perlins concept
**/
(function(){
var 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
];
// build the perm array to avoid overflow
var p = new Array(512);
for (var i = 0; i < 256 ; i++) {
p[256+i] = p[i] = permutation[i];
}
// fade: 6t^5-15t^4+10t^3
function fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
// linear interpolation between a and b by amount t (0, 1)
function lerp(t, a, b) {
return a + t * (b - a);
}
function grad1D(hash, x) {
// only two cases in one dimension
return (hash & 1) == 0 ? x : -x;
}
function grad2D(hash, x, y) {
/**
* return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
**/
switch(hash & 3) {
case 0: return x + y;
case 1: return -x + y;
case 2: return x - y;
case 3: return -x - y;
default: return 0; // never happens
}
}
function grad3D(hash, x, y, z) {
/**
* Ken Perlins original implementation:
* var h = hash & 15, // convert lo 4 bits of hash code
* 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);
*
* The switch-statement seems to run faster in JS
**/
switch(hash & 0xF) {
case 0x0: return x + y;
case 0x1: return -x + y;
case 0x2: return x - y;
case 0x3: return -x - y;
case 0x4: return x + z;
case 0x5: return -x + z;
case 0x6: return x - z;
case 0x7: return -x - z;
case 0x8: return y + z;
case 0x9: return -y + z;
case 0xA: return y - z;
case 0xB: return -y - z;
case 0xC: return y + x;
case 0xD: return -y + z;
case 0xE: return y - x;
case 0xF: return -y - z;
default: return 0; // never happens
}
}
function grad4D(hash, x, y, z, t) {
switch(hash & 31) {
case 0: return x + y;
case 1: return -x + y;
case 2: return x - y;
case 3: return -x - y;
case 4: return x + z;
case 5: return -x + z;
case 6: return x - z;
case 7: return -x - z;
case 8: return x + t;
case 9: return -x + t;
case 10: return x - t;
case 11: return -x - t;
case 12: return y + z;
case 13: return -y + z;
case 14: return y - z;
case 15: return -y - z;
case 16: return y + t;
case 17: return -y + t;
case 18: return y - t;
case 19: return -y - t;
// double cases
case 20: return y + x;
case 21: return y - x;
case 22: return y + z;
case 23: return y - z;
case 24: return y + t;
case 25: return y - t;
case 26: return -y + x;
case 27: return -y - x;
case 28: return -y + z;
case 29: return -y - z;
case 30: return -y + t;
case 31: return -y - t;
// never happens
default: return 0;
}
}
var perlin1D = function(x) {
// find interval that contains this point
var X = Math.floor(x) & 255;
// find relative x of point in interval
x -= Math.floor(x);
// compute fade curves for x
var u = fade(x);
// hash coordinates of the interval
var a = p[ X ],
b = p[ X + 1 ];
// return blended result
return lerp(u, grad1D( a, x ),
grad1D( b, x - 1 ));
}
var perlin2D = function(x, y) {
// find square that contains this point
var X = Math.floor(x) & 255,
Y = Math.floor(y) & 255;
// find relative x, y, z of point in square
x -= Math.floor(x);
y -= Math.floor(y);
// compute fade curves for x, y
var u = fade(x),
v = fade(y);
// hash coordinates of the 4 square corners
var aa = p[p[ X ]+ Y ],
ab = p[p[ X ]+ Y + 1 ],
ba = p[p[ X + 1 ]+ Y ],
bb = p[p[ X + 1 ]+ Y + 1 ];
// add blended results from 4 corners of square
return lerp ( v, lerp( u, grad2D( aa, x , y ),
grad2D( ba, x - 1, y )),
lerp( u, grad2D( ab, x , y - 1 ),
grad2D( bb, x - 1, y - 1 )));
}
var perlin3D = function(x, y, z) {
// find unit cube that contains this point
var X = Math.floor(x) & 255,
Y = Math.floor(y) & 255,
Z = Math.floor(z) & 255;
// find relative x, y, z of point in cube
x -= Math.floor(x);
y -= Math.floor(y);
z -= Math.floor(z);
// compute fade curves for each of x, y, z
var u = fade(x),
v = fade(y),
w = fade(z);
// hash coordinates of the 8 cube corners
var aaa = p[p[p[ X ]+ Y ]+ Z ],
aab = p[p[p[ X ]+ Y ]+ Z + 1 ],
aba = p[p[p[ X ]+ Y + 1 ]+ Z ],
abb = p[p[p[ X ]+ Y + 1 ]+ Z + 1 ],
baa = p[p[p[ X + 1 ]+ Y ]+ Z ],
bab = p[p[p[ X + 1 ]+ Y ]+ Z + 1 ],
bba = p[p[p[ X + 1 ]+ Y + 1 ]+ Z ],
bbb = p[p[p[ X + 1 ]+ Y + 1 ]+ Z + 1 ];
// add blended results from 8 corners of cube
return lerp ( w, lerp ( v, lerp( u, grad3D( aaa, x , y , z ),
grad3D( baa, x - 1, y , z )),
lerp( u, grad3D( aba, x , y - 1, z ),
grad3D( bba, x - 1, y - 1, z ))),
lerp ( v, lerp( u, grad3D( aab, x , y , z - 1 ),
grad3D( bab, x - 1, y , z - 1 )),
lerp( u, grad3D( abb, x , y - 1, z - 1 ),
grad3D( bbb, x - 1, y - 1, z - 1 ))));
};
var perlin4D = function(x, y, z, t) {
// find unit cube that contains this point
var X = Math.floor(x) & 255,
Y = Math.floor(y) & 255,
Z = Math.floor(z) & 255,
T = Math.floor(t) & 255;
// find relative x, y, z of point in cube
x -= Math.floor(x);
y -= Math.floor(y);
z -= Math.floor(z);
t -= Math.floor(t);
// compute fade curves for each of x, y, z
var u = fade(x),
v = fade(y),
w = fade(z);
_t = fade(t);
// hash coordinates of the 16 cube corners
var aaaa = p[p[p[p[ X ]+ Y ]+ Z ]+ T ],
aaab = p[p[p[p[ X ]+ Y ]+ Z ]+ T + 1 ],
aaba = p[p[p[p[ X ]+ Y ]+ Z + 1 ]+ T ],
aabb = p[p[p[p[ X ]+ Y ]+ Z + 1 ]+ T + 1 ],
abaa = p[p[p[p[ X ]+ Y + 1 ]+ Z ]+ T ],
abab = p[p[p[p[ X ]+ Y + 1 ]+ Z ]+ T + 1 ],
abba = p[p[p[p[ X ]+ Y + 1 ]+ Z + 1 ]+ T ],
abbb = p[p[p[p[ X ]+ Y + 1 ]+ Z + 1 ]+ T + 1 ],
baaa = p[p[p[p[ X + 1 ]+ Y ]+ Z ]+ T ],
baab = p[p[p[p[ X + 1 ]+ Y ]+ Z ]+ T + 1 ],
baba = p[p[p[p[ X + 1 ]+ Y ]+ Z + 1 ]+ T ],
babb = p[p[p[p[ X + 1 ]+ Y ]+ Z + 1 ]+ T + 1 ],
bbaa = p[p[p[p[ X + 1 ]+ Y + 1 ]+ Z ]+ T ],
bbab = p[p[p[p[ X + 1 ]+ Y + 1 ]+ Z ]+ T + 1 ],
bbba = p[p[p[p[ X + 1 ]+ Y + 1 ]+ Z + 1 ]+ T ],
bbbb = p[p[p[p[ X + 1 ]+ Y + 1 ]+ Z + 1 ]+ T + 1 ];
// add blended results from 16 corners of cube
return lerp ( _t, lerp ( w, lerp ( v, lerp( u, grad4D( aaaa, x , y , z , t ),
grad4D( baaa, x - 1, y , z , t )),
lerp( u, grad4D( abaa, x , y - 1, z , t ),
grad4D( bbaa, x - 1, y - 1, z , t ))),
lerp ( v, lerp( u, grad4D( aaba, x , y , z - 1 , t ),
grad4D( baba, x - 1, y , z - 1 , t )),
lerp( u, grad4D( abba, x , y - 1, z - 1 , t ),
grad4D( bbba, x - 1, y - 1, z - 1 , t )))),
lerp ( w, lerp ( v, lerp( u, grad4D( aaab, x , y , z , t - 1 ),
grad4D( baab, x - 1, y , z , t - 1 )),
lerp( u, grad4D( abab, x , y - 1, z , t - 1 ),
grad4D( bbab, x - 1, y - 1, z , t - 1 ))),
lerp ( v, lerp( u, grad4D( aabb, x , y , z - 1 , t - 1 ),
grad4D( babb, x - 1, y , z - 1 , t - 1 )),
lerp( u, grad4D( abbb, x , y - 1, z - 1 , t - 1 ),
grad4D( bbbb, x - 1, y - 1, z - 1 , t - 1 )))));
};
window.Noise = {
perlin1D: perlin1D,
perlin2D: perlin2D,
perlin3D: perlin3D,
perlin4D: perlin4D
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment