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

为何我的Perlin Noise实现效果呈现明显块状?

p5.js 自定义Perlin Noise实现异常问题排查

我尝试用p5.js自行实现Perlin Noise,但生成结果与预期不符。主要参考Perlin Noise的维基百科内容,最初认为问题出在插值函数上,尝试多种插值函数后仍无改善,目前无法定位错误。

以下是原始实现代码:

let grid, spacement;

function setup() {
  createCanvas(500, 500);

  spacement = 20;
  
  //initialize gradient vectors grid
  grid = createGrid(width / spacement, height / spacement);

  background(255);

  noLoop();
}

function draw() {
  for (let i = 0; i < width; i++) {

    for (let j = 0; j < height; j++) {
      let nearbyPoints = getNearestPoints(i, j);

      let distanceVectors = getDistanceVectors( nearbyPoints[0], nearbyPoints[1], nearbyPoints[2], nearbyPoints[3],i, j)

      let correspondingVectors = getGradientVectors(nearbyPoints[0],nearbyPoints[1],nearbyPoints[2],nearbyPoints[3]);

      let dots = [];

      for (let d = 0; d < 4; d++) {
        let dotProduct = getDotProduct(distanceVectors[d],correspondingVectors[d]);

        dots.push(dotProduct);
      }

      let sx = i- nearbyPoints[0]*spacement
      let sy = j-nearbyPoints[2]*spacement
      let ix0 = interpolate(dots[0], dots[1], sx)
      let ix1 = interpolate(dots[3], dots[2], sx)
    
      let finalValue = interpolate(ix0,ix1,sy);
      
      stroke(map(finalValue, -10, 10, 0, 255));

      point(i, j);
    }

  }

  console.info(`All points have found their distance vectors! (${width * height} points)`);

  console.info(`All points have found their matching gradient vectors! (${width * height} points)`);
}

//creates a grid of points to which are assigned random 2D vectors
function createGrid(amountX, amountY) {
  let array = Array(amountX);

  for (let i = 0; i <= amountX; i++) {
    array[i] = Array(amountY);

    for (let j = 0; j <= amountY; j++) {
      
           array[i][j] = p5.Vector.random2D()
           console.log(mag(array[i][j].x,array[i][j].y));
          
    }
  }

  return array;
}

//gets the 4 points of the grid created earlier that are the nearest to a x,y point
function getNearestPoints(x, y) {
  let x1 = Math.floor(x / spacement);

  let x2 = x1 + 1;

  let y1 = Math.floor(y / spacement);

  let y2 = y1 + 1;

  return [x1, x2, y1, y2];
}

//gets the vectors assigned to the nearest points
function getGradientVectors(x1, x2, y1, y2) {
  let topLeft = grid[x1][y1];

  let topRight = grid[x1][y2];

  let bottomLeft = grid[x2][y1];

  let bottomRight = grid[x2][y2];

  return [topLeft, topRight, bottomRight, bottomLeft];
}

//get the distance vector between a x,y point and the four nearest points
function getDistanceVectors(x1, x2, y1, y2, x, y) {
  let topLeft = [x - x1 * spacement, y - y1 * spacement];

  let topRight = [x - x2 * spacement, y - y1 * spacement];

  let bottomLeft = [x - x1 * spacement, y - y2 * spacement];

  let bottomRight = [x - x2 * spacement, y - y2 * spacement];

  return [topLeft, topRight, bottomLeft, bottomRight];
}

//gets the dot product of the distance vector and the gradient vector
function getDotProduct(v1, v2) {
  v1 = createVector(v1[0], v1[1]);

  v2 = createVector(v2.x, v2.y);

  let dot = p5.Vector.dot(v1, v2);

  return dot;
}

//interpolates between a0 and a1, based on the weight w
function interpolate(a0,a1,w){
  
return  (a1-a0)*(3.0-w*2.0)*w*w+a0
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>

错误排查与修正

1. 梯度网格索引越界

createGrid函数中,数组初始化长度为amountX,但循环条件使用i <= amountX,导致索引超出数组范围(数组索引从0开始,最大索引为amountX-1),会出现undefined的梯度向量。

修正:
将数组初始化改为Array(amountX + 1),同时保持循环条件不变,确保网格覆盖整个画布:

function createGrid(amountX, amountY) {
  let array = Array(amountX + 1); // 修正数组长度

  for (let i = 0; i <= amountX; i++) {
    array[i] = Array(amountY + 1); // 修正子数组长度

    for (let j = 0; j <= amountY; j++) {
      array[i][j] = p5.Vector.random2D();
    }
  }

  return array;
}

2. 梯度向量与距离向量对应关系错误

getGradientVectors返回的向量顺序与getDistanceVectors不匹配:

  • distanceVectors顺序:[左上, 右上, 左下, 右下]
  • 原始getGradientVectors返回顺序:[左上, 右上, 右下, 左下]

导致左下、右下的点积对应错误,直接影响插值结果。

修正:
调整getGradientVectors的返回顺序:

function getGradientVectors(x1, x2, y1, y2) {
  let topLeft = grid[x1][y1];
  let topRight = grid[x2][y1]; // 修正:右上是x2,y1,不是x1,y2
  let bottomLeft = grid[x1][y2]; // 修正:左下是x1,y2,不是x2,y1
  let bottomRight = grid[x2][y2];

  return [topLeft, topRight, bottomLeft, bottomRight];
}

3. 插值权重未归一化

原始代码中sx和sy是像素级的偏移值(020),但插值函数的权重参数`w`需要是**01之间的比例值**,否则平滑插值曲线无法生效,还会导致数值范围异常。

修正:
将sx和sy除以spacement,转换为0~1的比例:

let sx = (i - nearbyPoints[0] * spacement) / spacement;
let sy = (j - nearbyPoints[2] * spacement) / spacement;

4. 灰度映射范围适配

原始代码中映射范围-10,10不符合实际点积结果,点积最大值约为spacement(因为距离向量最大长度为spacement,梯度向量长度为1),调整映射范围后灰度过渡更自然。


修正后的完整代码

let grid, spacement;

function setup() {
  createCanvas(500, 500);
  spacement = 20;
  grid = createGrid(width / spacement, height / spacement);
  background(255);
  noLoop();
}

function draw() {
  for (let i = 0; i < width; i++) {
    for (let j = 0; j < height; j++) {
      let nearbyPoints = getNearestPoints(i, j);
      let distanceVectors = getDistanceVectors(nearbyPoints[0], nearbyPoints[1], nearbyPoints[2], nearbyPoints[3], i, j);
      let correspondingVectors = getGradientVectors(nearbyPoints[0], nearbyPoints[1], nearbyPoints[2], nearbyPoints[3]);

      let dots = [];
      for (let d = 0; d < 4; d++) {
        dots.push(getDotProduct(distanceVectors[d], correspondingVectors[d]));
      }

      // 归一化插值权重到0-1区间
      let sx = (i - nearbyPoints[0] * spacement) / spacement;
      let sy = (j - nearbyPoints[2] * spacement) / spacement;
      
      // x方向插值:上方左右两点、下方左右两点
      let ix0 = interpolate(dots[0], dots[1], sx);
      let ix1 = interpolate(dots[2], dots[3], sx);
      
      // y方向插值:上下插值结果
      let finalValue = interpolate(ix0, ix1, sy);
      
      // 映射到0-255灰度值,适配实际点积范围
      stroke(map(finalValue, -spacement, spacement, 0, 255));
      point(i, j);
    }
  }
}

function createGrid(amountX, amountY) {
  let array = Array(amountX + 1);
  for (let i = 0; i <= amountX; i++) {
    array[i] = Array(amountY + 1);
    for (let j = 0; j <= amountY; j++) {
      array[i][j] = p5.Vector.random2D();
    }
  }
  return array;
}

function getNearestPoints(x, y) {
  let x1 = Math.floor(x / spacement);
  let x2 = x1 + 1;
  let y1 = Math.floor(y / spacement);
  let y2 = y1 + 1;
  return [x1, x2, y1, y2];
}

function getGradientVectors(x1, x2, y1, y2) {
  let topLeft = grid[x1][y1];
  let topRight = grid[x2][y1];
  let bottomLeft = grid[x1][y2];
  let bottomRight = grid[x2][y2];
  return [topLeft, topRight, bottomLeft, bottomRight];
}

function getDistanceVectors(x1, x2, y1, y2, x, y) {
  let topLeft = [x - x1 * spacement, y - y1 * spacement];
  let topRight = [x - x2 * spacement, y - y1 * spacement];
  let bottomLeft = [x - x1 * spacement, y - y2 * spacement];
  let bottomRight = [x - x2 * spacement, y - y2 * spacement];
  return [topLeft, topRight, bottomLeft, bottomRight];
}

function getDotProduct(v1, v2) {
  return createVector(v1[0], v1[1]).dot(v2);
}

function interpolate(a0, a1, w) {
  return (a1 - a0) * (3.0 - w * 2.0) * w * w + a0;
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>

内容的提问来源于stack exchange,提问作者Bluesun 2

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 21:55:06