news 2026/10/23 5:18:02

p5.js 二维 Perlin 噪声(2D Noise)实战指南:从一维到二维的逐像素生成原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
p5.js 二维 Perlin 噪声(2D Noise)实战指南:从一维到二维的逐像素生成原理
  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

Perlin 噪声(Perlin Noise)是创意编程中生成平滑、自然、非随机抖动效果的核心算法,而二维噪声则把这种平滑性从一条曲线扩展到一个平面。本篇文章以 Coding Train(The Coding Train)Perlin 噪声系列第 0.5 节 "2D Noise" 为骨架,结合仓库中可运行的 p5.js 源码,讲解如何在 p5.js 中通过noise(xoff, yoff)逐像素绘制一幅灰度噪声图,并给出inc、xoff、yoff等关键参数的取值规律,以及从 2D 升级到 3D 地形渲染的延伸思路。读完本文,你将能够独立写出「像素级二维噪声纹理」,并理解它为什么是云雾、地形、流体等效果的基础。

本讲在系列中的位置:从一维噪声到二维噪声

0.5-2d-noise.md是仓库 Perlin 噪声学习系列 的第 5 讲(编号 0.5),该系列还包括:

  • 0.2 引言:Perlin 噪声是什么、如何产生以及历史背景;
  • 0.3 noise() 与 random() 对比:说明 p5.js 中两个「返回随机数」的函数本质差异;
  • 0.4 一维噪声作图:把一维噪声值画成平滑曲线并与纯随机对比;
  • 0.5 二维噪声(本文主题):把一维噪声拓展到二维平面,逐像素生成噪声纹理。

理解 2D 噪声的关键在于先理解 1D:一维噪声相当于沿着一条直线(x 轴方向)连续采样noise()的值,得到的曲线是平滑波动的;二维噪声则是在一个平面(x-y 网格)上连续采样,每个采样点都拥有「相邻点相似」的特性,从而形成连续渐变的空间纹理。

配套的官方示例代码位于 learning/noise/0.5-2d-noise/P5/,含 sketch.js 与 index.html;仓库的旧版(ES5 语法)对应文件在 Tutorials/perlin_noise/04_perlin_noise_2D/。另外,0.5-2d-noise.md的 front matter 中还记录了本讲的 p5.js Web Editor 草图 id:2_hBcOBrF(即线上可运行版本)。

核心思路:用 xoff 与 yoff 遍历整个像素平面

二维噪声相比一维噪声,最大的变化是采样坐标从「一个递增的游标」变成「一组双游标」:

  • xoff(x-offset):沿画布 x 方向移动的噪声采样偏移量,控制噪声在水平方向的变化快慢;
  • yoff(y-offset):沿画布 y 方向移动的噪声采样偏移量,控制噪声在垂直方向的变化快慢。

绘制策略是:外层循环逐行扫描 y,内层循环逐列扫描 x,对每个像素调用noise(xoff, yoff)得到 0~1 之间的平滑值,再乘以 255 映射为灰度写入像素数组。每扫描完一列,xoff增加inc;每完成一行,yoff也增加inc。这样 x-y 平面上每个相邻像素对应的噪声采样点都「相邻」,于是灰度是连续渐变的,而不是 random() 那种杂乱跳变。

这就是本讲最核心的算法骨架:双层循环 + 双偏移游标 + 像素数组写入。

逐行解读官方示例源码

以下为 learning/noise/0.5-2d-noise/P5/sketch.js 的完整 ES6 版本(该版本较旧版做了语法升级,逻辑一致):

// 2D Noise // The Coding Train / Daniel Shiffman // This example has been updated to use es6 syntax. let inc = 0.01; function setup() { createCanvas(200, 200); pixelDensity(1); } function draw() { let yoff = 0; loadPixels(); for (let y = 0; y < height; y++) { let xoff = 0; for (let x = 0; x < width; x++) { let index = (x + y * width) * 4; // let r = random(255); let r = noise(xoff, yoff) * 255; pixels[index + 0] = r; pixels[index + 1] = r; pixels[index + 2] = r; pixels[index + 3] = 255; xoff += inc; } yoff += inc; } updatePixels(); //noLoop(); }

关键点逐行拆解:

  1. let inc = 0.01;——噪声增量,是整个效果的「旋钮」。inc越小,相邻采样点越近,噪声越平滑、细节越少(更像大片云层);inc越大,噪声越细碎、颗粒感越强。官方示例取0.01,在 200×200 画布上意味着整个采样区间只有约 2 个单位(200 × 0.01),整体非常平滑。

  2. createCanvas(200, 200); pixelDensity(1);——创建 200×200 的画布,并把像素密度强制设为 1。pixelDensity(1)在这里很重要:若显示器为高分屏(Retina 等),p5.js 默认像素密度可能大于 1,导致pixels数组长度与width * height不匹配、出现采样错位;设为 1 可保证「每个像素恰好对应一个噪声采样点」。

  3. loadPixels()/updatePixels()——p5.js 像素操作的固定配对。loadPixels()把画布像素读入全局pixels数组,updatePixels()在修改完毕后把数组写回画布。

  4. 像素索引公式let index = (x + y * width) * 4;——p5.js 的pixels数组按 RGBA 排列,每个像素占 4 个连续元素:index+0为 R(红)、index+1为 G(绿)、index+2为 B(蓝)、index+3为 A(透明度)。

  5. let r = noise(xoff, yoff) * 255;——二维噪声的核心调用。noise()返回 0~1 之间的平滑值,乘 255 映射到 0~255 的灰度区间。将同一个r同时赋给 R、G、B 三分量,即得到灰度图(pixels[index + 3] = 255保证完全不透明)。

  6. 游标推进——内层循环每处理一个像素xoff += inc,外层循环每换一行yoff += inc。注意两个方向使用相同的inc,因此噪声在 x、y 两个方向上的「缩放」一致;若想让水平/垂直方向细节密度不同,可分别使用incX与incY(例如地形示例中xoff += 0.2、yoff += 0.2)。

  7. 被注释掉的// let r = random(255);——官方刻意保留这行注释,用于对比:换成random(255)后每个像素的灰度完全随机、相邻像素毫无关联,画面变成雪花噪点;而noise()版本则是连续渐变纹理。这正是系列第 0.3 讲 noise() 与 random() 对比 在二维场景下的直观印证。

  8. 被注释掉的//noLoop();——若取消注释,draw()只执行一次,得到一帧静态噪声图;保留注释则每一帧重绘。由于yoff每帧都从 0 重新开始(没有像一维作图那样维护全局start),画面本身是静止的。

运行入口 index.html 通过 CDN 引入p5.min.js(仓库内为p5@1.4.1)后加载sketch.js,直接在浏览器打开即可看到 200×200 的灰度噪声纹理。

仓库内新旧版本对照

仓库中保存了本讲的两个历史版本,适合对照学习:

版本位置语法差异要点
新版本learning/noise/0.5-2d-noise/P5/sketch.jsES6(let/const)注释更丰富,含//noLoop()说明
旧版本Tutorials/perlin_noise/04_perlin_noise_2D/sketch.jsES5(var)逻辑相同,是 2016 年视频录制时的原始形态

旧版 HTML(Tutorials/perlin_noise/04_perlin_noise_2D/index.html)还额外引入了p5.sound.min.js音频插件,与噪声绘制无关,仅说明当时教程的运行环境。两版核心算法完全一致,从源码结构可以推断:该讲重点始终是「用双层循环把二维噪声写进像素数组」这一个模式。

关键参数调优:inc、画布尺寸与灰度映射

二维噪声的效果由三个因素决定,官方示例均给出明确取值:

  • inc = 0.01(官方默认):采样增量。建议尝试区间 0.005 ~ 0.05。经验规律:
    • inc越小 → 采样越密集、过渡越平滑 → 大块「云团」式纹理;
    • inc越大 → 采样越稀疏、细节越丰富 → 细碎「沙粒」式纹理;
    • 若inc过大(如 > 0.1),相邻像素噪声值几乎无关,视觉上退化为随机噪点,失去 Perlin 噪声的意义。
  • 画布尺寸 200×200:官方选择小画布,一是便于观察全貌,二是在inc = 0.01下采样区间约为[0, 2],恰好落在噪声函数平滑变化的核心区间内。若改为 600×600,建议同步调小inc(如 0.005),以保持「整幅画面只有少数几片大云层」的观感。
  • 灰度映射noise(...) * 255:noise()输出 0~1,乘以 255 得到 0~255 灰度。若想观察更极端的明暗对比,可用map(noise(xoff, yoff), 0, 1, 50, 200)压缩灰度范围;若想得到彩色纹理,可分别用三个不同偏移(如noise(xoff, yoff)、noise(xoff + 100, yoff)、noise(xoff, yoff + 100))赋给 R/G/B。

延伸:从 2D 噪声到 3D 地形(仓库源码佐证)

二维噪声最著名的进阶应用是「Perlin 噪声地形」,仓库中就有完整的可运行实现:CodingChallenges/CC_011_PerlinNoiseTerrain/P5/sketch.js。该示例与本讲一脉相承,把二维噪声从「像素平面」升级为「顶点高度场」:

var yoff = flying; // 第三维:随时间变化的起点 for (var y = 0; y < rows; y++) { var xoff = 0; for (var x = 0; x < cols; x++) { terrain[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100); xoff += 0.2; } yoff += 0.2; }

对照本讲可以清楚看到三个升级点:

  1. 同样的双游标双层循环:xoff每列递增、yoff每行递增,采样结构与 2D 噪声完全一致;
  2. 用map()而非* 255:把噪声值 0~1 映射到 -100~100,作为地形顶点高度;
  3. 第三维动画:yoff不再每帧从 0 开始,而是以全局变量flying为起点并每帧flying -= 0.1,于是整块噪声场沿 y 方向「流动」,形成飞越地形的效果——这就是「3D 噪声 = 2D 噪声切片随时间推移」的直观演示。

反过来,如果在 0.5 讲中取消//noLoop()注释并让yoff每帧偏移,同样能让 2D 噪声纹理动起来,得到类似流云的效果。系列其他相关实现还包括 CC_036_Blobby、CC_060_Butterfly_Wings、CC_103_Flames 等(均可在仓库中检索到noise(xoff, yoff)的同类用法),可作为进一步研究二维/三维噪声的素材。

常见问题排查

结合源码可以归纳本讲最易出现的三个问题:

  1. 画面出现条纹或错位:通常是没有调用pixelDensity(1),高分屏下pixels数组长度超过width * height,导致采样索引错位。解决方案即官方写法pixelDensity(1)。
  2. 忘了loadPixels()/updatePixels():直接读写pixels数组而不配对调用这两个函数,画面不会更新或直接报错。
  3. 看起来像随机噪点:inc取值过大导致相邻采样点失去连续性。回到inc = 0.01附近,并检查是否误把noise()写成了random()(官方注释中特意保留了random(255)的对比行)。

小结

0.5-2d-noise.md这一讲的技术要点可以概括为一句话:用noise(xoff, yoff)配合双层循环,把二维 Perlin 噪声逐像素写入pixels数组,即可得到平滑渐变的灰度纹理。核心参数inc控制平滑度与细节密度,pixelDensity(1)保证像素映射正确,xoff/yoff双游标则是从一维到二维的关键跳跃。掌握这一模式后,向上可扩展出 3D 地形(见 CC_011),向旁可延伸出流动纹理与彩色噪声,是进入 Perlin 噪声创意编程的重要一步。完整代码与运行入口请查看 learning/noise/0.5-2d-noise/P5/。

  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 23:18:46

DesignLife多轴随机振动疲劳分析实战指南

1. 项目概述&#xff1a;这不是又一个软件操作手册&#xff0c;而是一份“失效分析工程师”的实战手记“nCode&#xff1a;DesignLife案例教程八”——光看标题&#xff0c;你可能以为这是某套商业软件的第八个入门练习&#xff0c;点开就等着看界面截图和点击顺序。但如果你在…

作者头像 李华
网站建设 2026/10/22 19:38:45

TTL与CMOS逻辑门电气特性详解:电平匹配、扇出计算与混用避坑指南

1. 为什么还要聊TTL和CMOS这两老古董刚入行那会儿&#xff0c;我总觉得逻辑门这东西早就被FPGA和MCU吞掉了&#xff0c;谁还关心74系列芯片内部用的是TTL还是CMOS。直到有一次做一个工业现场的信号调理板&#xff0c;3.3V的MCU去驱动一个5V的老式继电器驱动芯片&#xff0c;死活…

作者头像 李华
网站建设 2026/10/22 21:43:05

嵌入式综合实训:从零拆解指纹开锁项目全流程

1. 从零拆解一个指纹开锁实训项目&#xff1a;它到底在练什么指纹开锁这四个字&#xff0c;第一次看到的人多半会觉得不过是个小玩具——按一下手指&#xff0c;锁就开了&#xff0c;能有多复杂&#xff1f;但真正上手做过的人心里都清楚&#xff0c;这个项目麻雀虽小&#xff…

作者头像 李华
网站建设 2026/10/21 15:43:07

Simulink 64位 REFPROP 物性调用:S-Function 封装与避坑指南

简介&#xff1a;这份资源面向需要在MATLAB/Simulink环境中进行热物性计算的工程师与研究人员&#xff0c;提供Refprop 9.1数据库与Simulink的64位集成接口&#xff0c;下载后即可在仿真模型中直接调用NIST标准热物性数据&#xff0c;免去手动查表与数据录入的繁琐。压缩包共11…

作者头像 李华