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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:06:34