P5.js使用curveVertex绘制闭合形状时首尾衔接尖边问题咨询
P5.js curveVertex 绘制闭合平滑形状首尾硬边问题
问题描述
编写了适配圆形生成逻辑的噪声函数,用来实现视觉效果自然的随机噪声圆形,实际运行时curveVertex除首尾衔接段外其余部分表现正常。
已知endShape(CLOSE)会通过直线段闭合图形,但未找到平滑闭合形状的可行方案。当前绘制出的图形正中间右侧存在明显尖边,已添加辅助线标注这段未被curveVertex平滑处理的线段。
补充说明:顶点数量较多时该问题视觉影响较小,但顶点数减少(如绘制三角形、正方形等低顶点数图形)时,缺陷会被明显放大。
现有实现代码
核心逻辑代码
let start = Array(50).fill(0); // dont change let amount = 1; // 图形数量 let gap = 30; // 图形间距 let amplify = 50; // 噪声扰动幅度 let colorSpeed = 1; // 颜色变化速度 1-9 let colorSeparation = 3; // 颜色分离度 推荐0-10,最大80 function setup() { createCanvas(windowWidth, windowHeight); for(let i = 0 ; i < start.length; i++){ start[i] = random(i); } } function draw() { background(0); for(let dnc = (amount + 1) * gap; dnc > gap; dnc -= gap){ drawNoiseCircle(dnc, getNoise(start.length)); } start = start.map( c => c + 0.01 ); } function getNoise(amount){ let lengths = []; for(let i = 1; i < amount + 1; i++){ let n1 = noise(start[i - 1]); let noise1 = map(n1, 0, 1, -amplify, amplify); lengths.push(abs(-noise1)); } return lengths; } function drawNoiseCircle(radius, lengths){ colorMode(HSB); fill(((frameCount + radius) * colorSeparation)/-map(colorSpeed, 1, 10, -10, -1) % 360, 100, 50); noStroke() let x; let y; beginShape(); for(let l = 0; l < lengths.length; l++){ x = Math.cos(radians(l * 360 / lengths.length)) * (radius + lengths[l]) + width/2; y = Math.sin(radians(l * 360 / lengths.length)) * (radius + lengths[l]) + height/2; curveVertex(x, y); } endShape(CLOSE); stroke("black"); line(width/2, height/2, width, height/2); line(width/2, height/2 + 9, width, height/2 + 9); }
依赖引入
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.js"></script>
问题原因
curveVertex基于Catmull-Rom样条曲线实现平滑效果,每一段曲线的生成都需要依赖当前顶点前后各一个控制点:传入的第一个curveVertex仅作为后续曲线的控制点,不会生成实际绘制的线段;最后一个传入的curveVertex同理,仅作为前一段曲线的控制点存在。
直接调用endShape(CLOSE)时,程序只会在最后一个实际顶点和第一个实际顶点之间连接直线,不会自动补全首尾衔接需要的控制点,因此必然会出现硬边。
修复方案
手动补全首尾衔接需要的控制点:在正式绘制所有顶点前,先把顶点序列末尾的两个顶点作为开头段的控制点传入;所有顶点绘制完成后,再把顶点序列开头的两个顶点作为结尾段的控制点传入,最后调用endShape()时不要传入CLOSE参数,即可实现全段无硬边的平滑闭合。
修改后的drawNoiseCircle函数绘制部分代码如下:
function drawNoiseCircle(radius, lengths){ colorMode(HSB); fill(((frameCount + radius) * colorSeparation)/-map(colorSpeed, 1, 10, -10, -1) % 360, 100, 50); noStroke() let x; let y; const total = lengths.length; const angleStep = 360 / total; beginShape(); // 补开头段所需的两个控制点(取数组末尾两个顶点) for (let l = total - 2; l < total; l++) { x = Math.cos(radians(l * angleStep)) * (radius + lengths[l]) + width/2; y = Math.sin(radians(l * angleStep)) * (radius + lengths[l]) + height/2; curveVertex(x, y); } // 绘制所有正常顶点 for(let l = 0; l < total; l++){ x = Math.cos(radians(l * angleStep)) * (radius + lengths[l]) + width/2; y = Math.sin(radians(l * angleStep)) * (radius + lengths[l]) + height/2; curveVertex(x, y); } // 补结尾段所需的两个控制点(取数组开头两个顶点) for (let l = 0; l < 2; l++) { x = Math.cos(radians(l * angleStep)) * (radius + lengths[l]) + width/2; y = Math.sin(radians(l * angleStep)) * (radius + lengths[l]) + height/2; curveVertex(x, y); } // 不传入CLOSE参数,避免直线闭合 endShape(); stroke("black"); line(width/2, height/2, width, height/2); line(width/2, height/2 + 9, width, height/2 + 9); }
- 该方案对任意顶点数的闭合形状都生效,即使是3个顶点的三角形、4个顶点的正方形,也能实现全段平滑无硬边。
- 原有噪声动画、颜色渐变逻辑完全不需要改动,仅调整
curveVertex的传入顺序即可。
内容的提问来源于stack exchange,提问作者KoderM
相关产品推荐
相关产品推荐

