每当绘制到主上下文时,PGraphics 似乎都会被清除或冻结

PGraphics seems to get cleared or frozen whenever drawn to main context

我的最终目标是创建一个 "tunnel effect",因为我将一个矩形绘制到缓冲区,将缓冲区复制到另一个缓冲区,然后在随后的 draw() 中,将第二个缓冲区复制回第一个缓冲区,仅稍微小一点,然后在上面画画并重复。

我完全被这里发生的事情难住了。

首先,考虑这段代码,它按预期工作了 1 次(没有绘制循环):

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();

  canvas.beginDraw();
  canvas.image(buffer, 100, 100, width-200, height-200);
  canvas.endDraw();

  image(canvas, 0, 0);

  noLoop();
}

这是一个非常愚蠢的例子,但它证明了这个概念是合理的:我绘制到 canvas,复制到 buffer,将 buffer 复制回 canvas缩小比例然后输出到主要上下文。

但看看当我尝试在 draw() 循环中执行此操作时会发生什么:

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

void draw(){
  canvas.beginDraw();
  canvas.image(buffer, 0, 0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  image(canvas, 0, 0);

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

在这里,最终发生的是在 setup() 中创建的原始矩形每帧都被复制到 canvas。所以效果是有一个矩形不移动,然后第二个矩形每帧都被绘制和替换。

它变得更奇怪了。观察当我简单地将绘制到主上下文的 image() 函数移动时会发生什么:

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

void draw(){
  canvas.beginDraw();
  canvas.image(buffer, 0, 0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();

  image(canvas, 0, 0);
}

这应该不会改变什么,但结果是屏幕上的图像 "freezes" 有两个矩形。出于某种原因,它似乎一遍又一遍地绘制相同的东西,即使 canvas 每次都被重写。

将最后一行改为阅读

image(buffer, 0, 0);

相反,返回到 "freezing" 缓冲区的先前行为,但每次都在其顶部绘制一个新的矩形。

任何人都可以阐明正在发生的事情吗?

想一想你在每张 PGraphics 图像中的确切内容。

每个 PGraphics 是一张 500x500 的图片,背景为白色,上面有一个黑色矩形。

然后你拍摄一张图片并将其绘制在另一张图片之上。它们仍然是白色图像,上面有一个黑色矩形。需要注意的重要一点是,由于它们都有白色背景,您将无法看到 "old" 图像 "through" 新图像。所以你只是来回绘制同一个矩形。

您可以通过在第二个代码块中删除对 canvas.background() 的调用来证明这一点。然后您会看到矩形堆叠在一起。它仍然不是隧道效应,因为您每次都只是绘制 same-ish 矩形,但这是一个单独的问题。

因此,要解决您的问题,您需要注意每张图片中的确切内容。特别注意背景是否透明

我还会注意到,您可以只使用一个缓冲区图像来实现这种效果,您只需绘制越来越小的缓冲区图像,或者甚至根本不使用缓冲区图像,方法是对主 canvas.

查看源代码,问题出在 image()image() 通过调用 imageImpl() 设置 PGraphics 纹理,这恰好在 PGraphics 中被覆盖。通过设置纹理,而不是直接设置像素,纹理参考被保留和缓存,这解释了(至少在某种程度上满足我的好奇心)使用 PGraphics.image() 的原因(至少在主绘图上下文中) 似乎 "lock" 缓冲区 PGraphics 对象达到了对后续 draw() 操作无用的程度。

有两种解决方案可以避免这种情况:

  1. 仍然使用两个屏幕外缓冲区(在我的示例中canvasbuffer),继续使用canvas.image()以便能够将缓冲区写入图像并可能对其进行缩放;但就将 canvas 写入主绘图上下文而言,请改用 set(x, y, canvas)PGraphics.set()继承自PImage.set(),没有被覆盖,直接设置像素pixel-by-pixel,所以没有引用原始对象。它在 Java2D 上下文中也更快(尽管在 GL 上下文中可能更慢)因为它不绘制纹理。

  2. 另一种选择(至少在我的情况下)是完全绕过 canvas 对象,而是使用 g.copy() 直接处理主绘图上下文的像素,其中 returns 一个新的 PImage 对象,包含主绘图 canvas 的完整副本(g 实际上是 this.g,或 PApplet.g,所有绘图函数都会影响的主要 PGraphics 上下文)。因为这是像素的副本,而不是 PGraphics 对象,所以您可以使用 image() 而不受惩罚,利用该函数允许的缩放。

这里有一些例子:

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

void draw(){
  canvas.beginDraw();
  canvas.background(255);
  canvas.image(buffer, 10, 20, width-20, width-20);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  set(0, 0, canvas);

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

以上是"two buffer"版本。请注意使用 set() 而不是 image()。这里与我的原始示例的唯一区别是,我对 image() 应用了一些缩放比例以获得我最初寻找的 "warp" 效果。

第二个(更短的)示例仅使用一个 off-screen 缓冲区并通过 g.copy():

复制主绘图上下文的 PGraphics 对象
PImage buffer;

void setup(){
  size(500, 500);

  background(255);
  noFill();
  stroke(0);

  buffer = g.copy();

}

void draw(){
  background(255);
  image(buffer, 10, 20, width-20, width-20);
  rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));

  buffer = g.copy();
}

我非常喜欢后者,原因有二:第一,代码明显更少,内存可能更少,因此性能应该更高(不过我还没有测试过这个假设),第二,它允许你继续使用主绘图上下文,它更清晰、更惯用,让您可以轻松地将技术应用于任何现有草图,而无需重写每个图形调用,在它前面加上 beginDraw() 和屏幕外缓冲区的名称。