Created
June 6, 2026 18:27
-
-
Save sirisian/932b38b3b16f3a42401a263faf79ab96 to your computer and use it in GitHub Desktop.
Value Noise Example https://i.imgur.com/L4DcNAZ.png
This file contains hidden or 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> | |
| <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