为何我的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
相关产品推荐
相关产品推荐

