Skip to content

Instantly share code, notes, and snippets.

@sirisian
Created June 6, 2026 18:27
Show Gist options
  • Select an option

  • Save sirisian/932b38b3b16f3a42401a263faf79ab96 to your computer and use it in GitHub Desktop.

Select an option

Save sirisian/932b38b3b16f3a42401a263faf79ab96 to your computer and use it in GitHub Desktop.
Value Noise Example https://i.imgur.com/L4DcNAZ.png
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Value Noise</title>
<script type="text/javascript">
// Canvas
var canvas = null;
var context2D = null;
var tiledcanvas = null;
var tiledcontext2D = null;
var seed = Math.floor(Math.random() * 100);
function ValueNoise(width, height, startOctave, endOctave, persistence, smoothAmount, postprocess)
{
var valueNoiseMap = new Float32Array(width * height);
// We're storing the random data samples in a quadtree
// octave 0 is the whole area
// octave 1 is the area divided by 4
// octave n is the previous octave with each area divided by 4
var startOctave = 3;
// Go to the pixel level. This algorithm assumes base 2 area
var endOctave = 7; //Math.log(width) / Math.log(2) - 2;
// We need 4 points to do bilinear interpolation from for the noise generation for each octave.
// This is the summation of Math.pow(2, i + 1) - Math.pow(2, i) + 1 which represents the
// number of corners per depth of a quadtree. So depth zero has 4 and depth one has 9.
var nodeCount = 1 / 3 * (3 * (endOctave + 1) + 3 * Math.pow(2, (endOctave + 1) + 2) + Math.pow(2, 2 * (endOctave + 1) + 2) - 4) -
1 / 3 * (3 * startOctave + 3 * Math.pow(2, startOctave + 2) + Math.pow(2, 2 * startOctave + 2) - 4);
var randomTree = new Float32Array(nodeCount);
for (var i = 0; i < randomTree.length; ++i)
{
randomTree[i] = Math.random();
}
// Make it tileable
for (var i = startOctave; i <= endOctave; ++i)
{
var octaveSize = Math.pow(2, i + 1) - Math.pow(2, i) + 1;
var indexOffset = 1 / 3 * (3 * i + 3 * Math.pow(2, i + 2) + Math.pow(2, 2 * i + 2) - 4) -
1 / 3 * (3 * startOctave + 3 * Math.pow(2, startOctave + 2) + Math.pow(2, 2 * startOctave + 2) - 4);
for(var y = 0; y < octaveSize; ++y)
{
randomTree[indexOffset + y * octaveSize] = randomTree[indexOffset + y * octaveSize + octaveSize - 1];
}
for(var x = 0; x < octaveSize; ++x)
{
randomTree[indexOffset + x] = randomTree[indexOffset + (octaveSize - 1) * octaveSize + x];
}
}
for(var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
valueNoiseMap[y * width + x] = 0;
for (var i = startOctave; i <= endOctave; ++i)
{
var cellSize = width / Math.pow(2, i);
var integerX = Math.floor(x / cellSize);
var integerY = Math.floor(y / cellSize);
var indexOffset = 1 / 3 * (3 * i + 3 * Math.pow(2, i + 2) + Math.pow(2, 2 * i + 2) - 4) -
1 / 3 * (3 * startOctave + 3 * Math.pow(2, startOctave + 2) + Math.pow(2, 2 * startOctave + 2) - 4);
var fractionalX = (x - integerX * cellSize) / cellSize;
var fractionalY = (y - integerY * cellSize) / cellSize;
//Log(cellSize + " " + fractionalX + " " + fractionalY);
var octaveSize = Math.pow(2, i + 1) - Math.pow(2, i) + 1;
var i1 = Interpolate(randomTree[indexOffset + integerY * octaveSize + integerX],
randomTree[indexOffset + integerY * octaveSize + integerX + 1],
fractionalX);
var i2 = Interpolate(randomTree[indexOffset + (integerY + 1) * octaveSize + integerX],
randomTree[indexOffset + (integerY + 1) * octaveSize + integerX + 1],
fractionalX);
valueNoiseMap[y * width + x] += Interpolate(i1 , i2 , fractionalY) * Math.pow(persistence, i - startOctave);
// Smooth and then normalize at the very end
}
}
}
Smooth(width, height, valueNoiseMap, smoothAmount);
Normalize(width, height, valueNoiseMap, 0, 1);
if (postprocess)
{
postprocess(valueNoiseMap);
}
return valueNoiseMap;
}
function Smooth(width, height, noise, amount)
{
// Smooth
for (var i = 0; i < amount; ++i)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
var xMinus1 = x == 0 ? width - 1 : x - 1;
var yMinus1 = y == 0 ? height - 1 : y - 1;
var xPlus1 = (x + 1) % width;
var yPlus1 = (y + 1) % height;
var corners = (noise[yMinus1 * width + xMinus1] +
noise[yMinus1 * width + xPlus1] +
noise[yPlus1 * width + xPlus1] +
noise[yPlus1 * width + xMinus1]) / 16.0;
var sides = (noise[y * width + xMinus1] +
noise[y * width + xPlus1] +
noise[yMinus1 * width + x] +
noise[yPlus1 * width + x]) / 8.0;
var center = noise[y * width + x] / 4.0;
noise[y * width + x] = corners + sides + center;
}
}
}
}
function Normalize(width, height, noise, minimum, maximum)
{
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
// Calculate min and max range used to normalize with
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
min = Math.min(min, noise[y * width + x]);
max = Math.max(max, noise[y * width + x]);
}
}
// Normalize the range to 0 to 1
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = (noise[y * width + x] - min) / (max - min) * (maximum - minimum) + minimum;
}
}
}
function Interpolate(a, b, x)
{
var ft = x * 3.1415927;
var f = (1 - Math.cos(ft)) * 0.5;
return a * (1 - f) + b * f;
}
function Initialize()
{
canvas = document.getElementById('canvas');
context2D = canvas.getContext('2d');
var width = 256;
var height = 256;
var tests =
[
// Terrain
[width, height, 3, 7, 0.6, 10],
// Rivers
[width, height, 3, 7, 0.6, 20, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.45 && noise[y * width + x] < 0.55 ? 0 : 1;
}
}
}],
// Lakes
[width, height, 1, 1, 0.6, 50, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.7 ? 0 : 1;
}
}
}],
// Mixed
[width, height, 3, 7, 0.6, 10, function(noise)
{
var noise2 = ValueNoise(width, height, 3, 7, 0.6, 20, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.45 && noise[y * width + x] < 0.55 ? 0 : 1;
}
}
});
var noise3 = ValueNoise(width, height, 1, 1, 0.6, 50, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.7 ? 0 : 1;
}
}
});
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
if (noise2[y * width + x] != 1)
{
noise[y * width + x] = noise2[y * width + x];
}
else if (noise3[y * width + x] != 1)
{
noise[y * width + x] = noise3[y * width + x];
}
else
{
noise[y * width + x] = noise[y * width + x];
}
}
}
}],
// Smoothed and normalized
[width, height, 3, 7, 0.6, 10, function(noise)
{
var noise2 = ValueNoise(width, height, 3, 7, 0.6, 20, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.45 && noise[y * width + x] < 0.55 ? 0 : 1;
}
}
});
var noise3 = ValueNoise(width, height, 1, 1, 0.6, 50, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.7 ? 0 : 1;
}
}
});
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
if (noise2[y * width + x] != 1)
{
noise[y * width + x] = noise2[y * width + x];
}
else if (noise3[y * width + x] != 1)
{
noise[y * width + x] = noise3[y * width + x];
}
else
{
noise[y * width + x] = noise[y * width + x];
}
}
}
Smooth(width, height, noise, 10);
Normalize(width, height, noise, 0, 1);
}],
[width, height, 3, 7, 0.6, 10, function(noise)
{
var noise2 = ValueNoise(width, height, 3, 7, 0.6, 20, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.45 && noise[y * width + x] < 0.55 ? 0 : 1;
}
}
});
var noise3 = ValueNoise(width, height, 1, 1, 0.6, 50, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] > 0.7 ? 0 : 1;
}
}
});
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
if (noise2[y * width + x] != 1)
{
noise[y * width + x] = noise2[y * width + x];
}
else if (noise3[y * width + x] != 1)
{
noise[y * width + x] = noise3[y * width + x];
}
else
{
noise[y * width + x] = noise[y * width + x];
}
}
}
Smooth(width, height, noise, 10);
Normalize(width, height, noise, 0, 1);
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = Math.floor(noise[y * width + x] * 10) / 10;
}
}
}],
[width, height, 7, 7, 1, 1],
[width, height, 6, 7, 1, 3, function(noise)
{
for (var y = 0; y < height; ++y)
{
for(var x = 0; x < width; ++x)
{
noise[y * width + x] = noise[y * width + x] < 0.3 || noise[y * width + x] > 0.7 ? 0.5 : 0;
}
}
}]
];
canvas.height = tests.length * height;
for (var i = 0; i < tests.length; ++i)
{
var valueNoiseMap = ValueNoise(tests[i][0], tests[i][1], tests[i][2], tests[i][3], tests[i][4], tests[i][5], tests[i][6]);
// Create value noise texture and render it to the canvas
var imageData = context2D.createImageData(width, height);
var pixels = imageData.data;
for (var j = 0, n = pixels.length; j < n; j += 4)
{
var bwIntensity = Math.floor(valueNoiseMap[j / 4] * 255);
pixels[j] = bwIntensity;
pixels[j + 1] = bwIntensity;
pixels[j + 2] = bwIntensity;
pixels[j + 3] = 255;
}
context2D.putImageData(imageData, 0, i * height);
}
}
</script>
<body onload="Initialize()">
<canvas id="canvas" width="256" height="256" style="border:2px solid gray;float:left;"/>
<h1>Canvas is not supported in this browser.</h1>
</canvas>
<div>
<p>1) Terrain<p>
<p>2) Rivers<p>
<p>3) Lakes<p>
<p>4) Terrain + Rivers + Lakes<p>
<p>5) Smooth and normalize<p>
<p>6) Stepped<p>
<p>... other stuff</p>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment