You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:14:59