如何用p5.js实现类似Photoshop液化效果的不规则线条?
用p5.js实现类似Photoshop液化效果的不规则线条
一、能否用数学公式生成这类线条?
可以。这类具有自然扭曲、连续不规则特征的线条,完全可以通过数学算法生成,最常用的两种方式是:
- 分形噪声(Perlin/Simplex噪声):这是生成自然随机连续图案的经典算法,p5.js内置
noise()函数可直接调用。它通过计算平滑噪声值,为线条上的每个点提供连续变化的偏移量,能模拟出液化效果的柔和扭曲感。 - 傅里叶级数叠加:将多个不同频率、振幅的正弦/余弦波叠加,生成带有周期性波动的不规则曲线,调整参数可接近液化的扭曲质感。
二、实现思路与代码示例
1. 基于Perlin噪声的路径扭曲(最推荐)
核心逻辑是先构建基础直线/路径,再对路径上的每个点用噪声计算偏移量,让线条产生连续自然的扭曲:
function setup() { createCanvas(800, 400); noiseDetail(4, 0.5); // 调整噪声细节:八度数量+衰减系数 } function draw() { background(255); stroke(0); strokeWeight(2); // 基础直线的起点、终点 const start = {x: 100, y: height/2}; const end = {x: width - 100, y: height/2}; const pointCount = 100; // 线条分段数,越多越平滑 beginShape(); for (let i = 0; i <= pointCount; i++) { const t = i / pointCount; // 计算基础直线上的点 const baseX = lerp(start.x, end.x, t); const baseY = lerp(start.y, end.y, t); // 用噪声生成偏移值(加入frameCount可实现动态扭曲) const noiseValue = noise(baseX * 0.01, frameCount * 0.005); const yOffset = map(noiseValue, 0, 1, -60, 60); // 映射偏移范围 vertex(baseX, baseY + yOffset); } endShape(); }
2. 模拟液化位移场
参考Photoshop液化工具的像素位移逻辑:先绘制基础线条,再通过噪声生成的位移场对线条像素进行偏移:
function setup() { createCanvas(800, 400); pixelDensity(1); } function draw() { background(255); // 先绘制基础直线 stroke(0); strokeWeight(3); line(100, height/2, width-100, height/2); // 获取像素数据并处理位移 loadPixels(); const displacementScale = 30; // 位移幅度 for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const index = (x + y * width) * 4; // 只处理线条区域的像素(简化判断) if (pixels[index] === 0) { // 用噪声计算位移目标点 const dx = noise(x*0.01, y*0.01) * displacementScale - displacementScale/2; const dy = noise(x*0.01 + 100, y*0.01) * displacementScale - displacementScale/2; const targetX = constrain(x + dx, 0, width-1); const targetY = constrain(y + dy, 0, height-1); const targetIndex = (targetX + targetY * width) * 4; // 替换像素颜色 pixels[index] = pixels[targetIndex]; pixels[index+1] = pixels[targetIndex+1]; pixels[index+2] = pixels[targetIndex+2]; } } } updatePixels(); noLoop(); // 静态效果,去掉可实现动态变化 }
3. 贝塞尔曲线结合随机扰动
用多段贝塞尔曲线构建线条,对每个控制点加入噪声扰动,保证曲线连续的同时产生不规则扭曲:
function setup() { createCanvas(800, 400); } function draw() { background(255); stroke(0); strokeWeight(2); noFill(); const segmentCount = 5; // 贝塞尔段数 const startX = 100; const startY = height/2; const endX = width - 100; const endY = height/2; let prevX = startX; let prevY = startY; for (let i = 1; i <= segmentCount; i++) { const t = i / segmentCount; const ctrlX1 = lerp(startX, endX, t - 0.15); const ctrlY1 = lerp(startY, endY, t) + noise(ctrlX1*0.01) * 40; const ctrlX2 = lerp(startX, endX, t + 0.15); const ctrlY2 = lerp(startY, endY, t) + noise(ctrlX2*0.01 + 50) * 40; const endSegmentX = lerp(startX, endX, t); const endSegmentY = lerp(startY, endY, t); bezier(prevX, prevY, ctrlX1, ctrlY1, ctrlX2, ctrlY2, endSegmentX, endSegmentY); prevX = endSegmentX; prevY = endSegmentY; } }
4. 粒子轨迹模拟
模拟粒子沿基础路径运动,同时受到噪声生成的“力”影响,粒子的运动轨迹即为所需的不规则线条:
let particle; function setup() { createCanvas(800, 400); particle = { pos: createVector(100, height/2), vel: createVector(2, 0), acc: createVector(0, 0) }; background(255); } function draw() { stroke(0); strokeWeight(2); // 用噪声生成加速度 const noiseVal = noise(particle.pos.x*0.01, frameCount*0.005); particle.acc.y = map(noiseVal, 0, 1, -0.5, 0.5); // 更新粒子状态 particle.vel.add(particle.acc); particle.pos.add(particle.vel); // 绘制轨迹点 point(particle.pos.x, particle.pos.y); // 到达终点后重置 if (particle.pos.x > width - 100) { particle.pos.set(100, height/2); particle.vel.set(2, 0); background(255); } }
内容的提问来源于stack exchange,提问作者user2956843
相关产品推荐
相关产品推荐

