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

p5.js高分辨率下调用beginShape()引发帧率下降问题咨询

问题背景

程序在高渲染分辨率下运行帧率下降明显,经排查未调用beginShape()的逻辑不会造成帧率负面影响,需要确认问题诱因、成因及可用的规避优化方案。

复现代码
function w(v) {if (v == null) return width;return width * v;}
function h(v) {if (v == null) return height;return height * v;}

let zoff = 0;
let irregCircs = []
let numCircs;

function setup() {
  createCanvas(windowWidth, windowHeight);
  exType = chooseExpandType()
  pixelDensity(1)
  setIrregCircles(exType)
}

function draw() {
  translate(width/2,height/2)
  background(255,50);
  drawIrregCircles()
  console.log(frameRate())
}

function chooseExpandType() {
  expandType = 'ellipses'
  return expandType
}

function irregExpand(radius,noiseVal) {
  beginShape();
  for (let a = 0; a <= TWO_PI; a += radians(6)) {
    let xoff = map(cos(a), -1, 1, noiseVal/3, noiseVal);
    let yoff = map(sin(a), -1, 1, noiseVal/3, noiseVal);
    let diff = map(noise(xoff, yoff, zoff), 0, 1, .65, 1.35);
    let x = radius * diff * cos(a);
    let y = radius * diff * sin(a);
    vertex(x,y)
  }
  endShape(CLOSE);
  zoff += 0.0001;
}

function setIrregCircles(expandType) {
  numCircs = 4;
  for (let i = 0; i < numCircs; i++) {
    radius = map(i,0,numCircs,w(.03),w(.65))
    noiseVal = random(1,3)
    circ = new IrregCircle(radius,noiseVal,expandType,numCircs);
    irregCircs.push(circ);
  }
}

function drawIrregCircles() {
  for (let i = 0; i < irregCircs.length; i++){
    irregCircs[i].run();
  }
}

class IrregCircle{
  constructor(_radius,_noiseVal,_expandType,_numC) {
    this.radius = _radius;
    this.noiseVal = _noiseVal;
    this.expandType = _expandType;
    this.numC = _numC;
  }

  run() {
    this.update()
    this.checkEdges()
    this.show()
  }

  update() {
    this.radius += w(.0015)
  }

  checkEdges() {
    if (this.radius > w(.73)) {
      this.radius = w(.01)
    }
  }

  show() {
    noFill()
    if (this.expandType === 'ellipses'){
      push()
      rotate(frameCount / 60)
      stroke(35,20)
      strokeWeight(w(.002)) 
      irregExpand(this.radius,this.noiseVal)
      irregExpand(this.radius*1.15,this.noiseVal*1.35)
      irregExpand(this.radius*1.3,this.noiseVal*1.7)
      irregExpand(this.radius*1.45,this.noiseVal*2)
      irregExpand(this.radius*1.6,this.noiseVal*2.3)
      irregExpand(this.radius*1.75,this.noiseVal*2.8)
      pop()
    }
  }
}
问题确认与成因

该性能问题确实和beginShape()的使用方式直接相关,并非API本身存在缺陷,核心原因如下:

  • 现有代码每帧会触发24次独立的beginShape()/endShape()调用(4个不规则圆实例,每个实例绘制6层轮廓),每调用一次就会向渲染上下文提交一次独立的绘制指令。分辨率越高,单条描边路径的栅格化计算量、GPU绘制调用的状态切换开销会线性上涨,分散的独立绘制批次会把这部分开销放大多倍。
  • 对比内置图形API(如ellipse()),p5.js对内置图形做了绘制合批优化,而手动调用beginShape()生成的形状默认不会自动合批,因此高分辨率下性能差距明显。
  • 额外叠加的CPU开销:每次绘制轮廓时都会在顶点循环里重复计算柏林噪声,单帧噪声计算量超过1400次,这部分计算耗时不会随分辨率变化,但会和渲染开销叠加,进一步拉低帧率。
可行优化方案
  • 合并绘制批次:对同变换层级、同描边填充样式的轮廓,不要每画一个闭合路径就新开一次beginShape(),可以在同一个形状块内通过beginContour()/endContour()接口添加多组闭合轮廓,直接把绘制调用数量降到原来的1/6。
  • 预计算静态顶点数据:可以提前生成每个不规则轮廓的顶点偏移模板,运行时只需要做半径缩放、旋转变换的矩阵运算,不用每帧重复执行噪声计算,能砍掉近60%的CPU开销。
  • 降低冗余顶点密度:当前代码每6°取一个轮廓顶点,单圈轮廓共60个顶点,对细描边场景可以把角度步长调整到10°~12°,肉眼几乎看不出轮廓锯齿,但顶点数量能减少近一半,渲染速度提升明显。
  • 离屏缓存低更新频率内容:由于轮廓形变速度很慢,可以把已绘制的圆形缓存到半透明离屏画布上,主画布每帧只需要粘贴离屏纹理、叠加最新的轮廓帧,不用全量重绘所有路径。

内容的提问来源于stack exchange,提问作者Anton E

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:01:41