Perlin Noise函数异常:2D模式重复问题求助
Perlin Noise网格重复问题修复与优化
问题根源
你的Perlin噪声出现网格重复,核心原因是置换数组(permutation)未被正确随机化。原代码中生成置换数组的逻辑是:
for (var i = 0; i < 256; i++) { permutation.push(seed ? (seed + i) % 256 : i); }
这种方式生成的是连续递增后取模的序列(比如seed=384时,序列是128,129,...,255,0,1,...,127),完全没有打乱顺序,导致噪声的梯度点排列规律,最终出现网格状重复。而1D噪声能正常工作是因为一维下这种序列的规律性表现不明显。
修复步骤
替换原置换数组生成逻辑,使用Fisher-Yates洗牌算法基于seed生成真正随机的置换序列,确保噪声的伪随机性:
function perlinNoise(x, y, z, seed) { function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); } function lerp(t, a, b) { return a + t * (b - a); } function grad(hash, x, y, z) { var h = hash & 15; var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); } function scale(n) { return ((1 + n) / 2); } // 生成随机置换数组(修复核心) var p = new Array(512); var permutation = Array.from({length:256}, (_,i) => i); if(seed){ // 基于seed的可复现随机数生成器 let rng = (function(seed){ let a = 16807; let m = 2147483647; let s = seed % m; return function(){ s = (a * s) % m; return s / m; } })(seed); // Fisher-Yates洗牌 for(let i=255; i>0; i--){ let j = Math.floor(rng()*(i+1)); [permutation[i], permutation[j]] = [permutation[j], permutation[i]]; } } for (var i = 0; i < 256; i++) { p[256 + i] = p[i] = permutation[i]; } var X = Math.floor(x) & 255, Y = Math.floor(y) & 255, Z = Math.floor(z) & 255; x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z); var u = fade(x), v = fade(y), w = fade(z); var 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; return scale(lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)), lerp(u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z))), lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1))))); }
绘制效率优化
原代码通过嵌套循环逐个绘制1x1矩形,性能极差。改用ImageData直接操作像素数据,一次性绘制,大幅提升速度:
// Helper function for 2d perlin noise function perlin2D(x, y, seed) { return perlinNoise(x, y, 0, seed); } var scene = document.getElementById("scene"); var ctx = scene.getContext("2d"); scene.width = window.innerWidth; scene.height = window.innerHeight; const drawWidth = Math.floor(scene.height / 3); const drawHeight = Math.floor(scene.height / 3); // 创建ImageData对象 const imageData = ctx.createImageData(drawWidth, drawHeight); const pixelData = imageData.data; for (var x = 0; x < drawWidth; x++) { for (var y = 0; y < drawHeight; y++) { var perlinValue = perlin2D(x / 10, y / 10, 384); var gray = Math.floor(perlinValue * 255); // 计算当前像素在数组中的索引 const index = (y * drawWidth + x) * 4; pixelData[index] = gray; // R通道 pixelData[index + 1] = gray; // G通道 pixelData[index + 2] = gray; // B通道 pixelData[index + 3] = 255; // Alpha通道(不透明) } } // 一次性绘制所有像素 ctx.putImageData(imageData, 0, 0);
完整代码
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Perlin Noise Fix</title> <style> *, *:before, *:after { font-family: roboto, Arial, Helvetica, sans-serif, system-ui; padding: 0; margin: 0; box-sizing: border-box; } canvas { display: block; } </style> </head> <body> <canvas id="scene"></canvas> <script> // 修复后的perlinNoise函数 function perlinNoise(x, y, z, seed) { function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); } function lerp(t, a, b) { return a + t * (b - a); } function grad(hash, x, y, z) { var h = hash & 15; var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); } function scale(n) { return ((1 + n) / 2); } // 生成随机置换数组(修复核心) var p = new Array(512); var permutation = Array.from({length:256}, (_,i) => i); if(seed){ // 基于seed的可复现随机数生成器 let rng = (function(seed){ let a = 16807; let m = 2147483647; let s = seed % m; return function(){ s = (a * s) % m; return s / m; } })(seed); // Fisher-Yates洗牌 for(let i=255; i>0; i--){ let j = Math.floor(rng()*(i+1)); [permutation[i], permutation[j]] = [permutation[j], permutation[i]]; } } for (var i = 0; i < 256; i++) { p[256 + i] = p[i] = permutation[i]; } var X = Math.floor(x) & 255, Y = Math.floor(y) & 255, Z = Math.floor(z) & 255; x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z); var u = fade(x), v = fade(y), w = fade(z); var 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; return scale(lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)), lerp(u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z))), lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1))))); } function perlin2D(x, y, seed) { return perlinNoise(x, y, 0, seed); } var scene = document.getElementById("scene"); var ctx = scene.getContext("2d"); scene.width = window.innerWidth; scene.height = window.innerHeight; const drawWidth = Math.floor(scene.height / 3); const drawHeight = Math.floor(scene.height / 3); const imageData = ctx.createImageData(drawWidth, drawHeight); const pixelData = imageData.data; for (var x = 0; x < drawWidth; x++) { for (var y = 0; y < drawHeight; y++) { var perlinValue = perlin2D(x / 10, y / 10, 384); var gray = Math.floor(perlinValue * 255); const index = (y * drawWidth + x) * 4; pixelData[index] = gray; pixelData[index + 1] = gray; pixelData[index + 2] = gray; pixelData[index + 3] = 255; } } ctx.putImageData(imageData, 0, 0); </script> </body> </html>
内容的提问来源于stack exchange,提问作者Pete21
相关产品推荐
相关产品推荐

