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.
Last active
March 8, 2023 00:26
-
-
Save toja/0e798c8dd54eb53ea0f0aeef414d16c9 to your computer and use it in GitHub Desktop.
Perlin noise - 3D Demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 | |
height: 480 | |
border: no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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