如何避免在html5画布上出现永久粒子轨迹?

3

在HTML5画布上有成千上万个运动粒子,我的目标是在每个粒子后面绘制一个短暂的淡出轨迹。一种不错且快速的方法是不完全清除画布每帧,而是用半透明颜色覆盖它。以下是仅包含一个粒子的示例:

var canvas = document.getElementById('display');
var ctx = canvas.getContext('2d');
var displayHeight = canvas.height;

var backgroundColor = '#000000';
var overlayOpacity = 0.05;

var testParticle = {
 pos: 0,
  size: 3
};

function render(ctx, particle) {
  ctx.globalAlpha = overlayOpacity;
  ctx.fillStyle = backgroundColor;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.globalAlpha = 1.0;
  
  ctx.fillStyle = '#FFF';
  ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}

function update(particle) {
 particle.pos += 1;
}

// Fill with initial color
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);

function mainLoop() {
  update(testParticle);
  render(ctx, testParticle);
  requestAnimationFrame(mainLoop);
}

mainLoop();
<canvas id="display" width="320" height="240"></canvas>

有一个明显的问题:当透明度值较低时,拖尾永远不会完全消失。您可以在我的单粒子示例中看到几乎不褪色的水平线。我理解为什么会出现这种情况。半透明的ColorB覆盖在ColorA上基本上是一种线性插值,如果我们反复执行以下操作,则ColorA永远无法完全收敛到ColorB:

ColorA = lerp(ColorA, ColorB, opacityOfB)

我的问题是,我该怎么做才能使它收敛到背景颜色,这样轨迹就不会永远留在那里了?使用WebGL或手动绘制轨迹都不是有效的选择(因为兼容性和性能原因)。一种可能的解决方案是循环所有画布像素并手动将亮度较低的像素设置为背景颜色,尽管对于大画布来说可能会很昂贵。我想知道是否有更好的解决方案。
3个回答

2
作为一种解决方法,可以将overlayOpacity设置为0.1(此值会收敛),但仅在每x次绘制时绘制它,而不是在每个渲染调用中都绘制它。因此,只有每隔一段时间绘制它时,才能保持大致相同的轨迹长度。
var renderCount = 0;
var overlayOpacity = 0.1;

function render(ctx, particle) {

    if((renderCount++)%2 == 0) {
        ctx.globalAlpha = overlayOpacity;
        ctx.fillStyle = backgroundColor;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    }

    ctx.globalAlpha = 1.0;
    ctx.fillStyle = '#FFF';
    ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}

显然,缺点是它看起来更加抽搐,也许在你的情况下这是不可接受的。

这是一个有趣的想法!问题在于,不透明度为0.1仍然会在其他背景颜色(例如#100030)留下痕迹。0.2效果更好,但为了保持相同的痕迹长度,它应该在每4帧绘制一次,这开始看起来有点抖动。 - interphx

1
最佳解决方案是使用复合操作“destination-out”并淡出到透明背景。对于下降到globalAlpha = 0.01甚至更低的淡出速率效果良好,但在此以下可能会有些麻烦。如果需要更慢的淡出效果,只需每2或3帧执行一次淡出即可。
ctx.globalAlpha = 0.01;           // fade rate
ctx.globalCompositeOperation = "destination-out"  // fade out destination pixels
ctx.fillRect(0,0,w,h)
ctx.globalCompositeOperation = "source-over"
ctx.globalAlpha = 1;           // reset alpha

如果您想要一个带颜色的背景,您需要在离屏画布上渲染动画,并在每个帧上将其渲染到屏幕画布上。或者将画布背景设置为所需的颜色。

0

如果有人遇到这个问题,这里是一个对我有效的解决方法:

// Do this instead of ctx.fillStyle some alpha value and ctx.fillRect  
if(Math.random() > 0.8){
  ctx.fillStyle = 'rgba(255, 255, 255, '+getRandomNumber(0.1,0.001)+')';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

// Define this helper function somewhere in your code
function getRandomNumber(minValue, maxValue) {
  return Math.random() * (maxValue - minValue) + minValue;
}

它也适用于不同颜色的背景。通过调整 Math.random()> 0.8 和 getRandomNumber(0.1,0.001)来改变拖尾长度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接