减去不透明度而不是乘以
Subtract opacity instead of multiplying
我试图让它看起来好像我的 <canvas>
上的运动创建了运动轨迹。为了做到这一点,我没有清除帧之间的 canvas,而是通过用这样的东西替换 clearRect
调用来减少现有内容的不透明度:
// Redraw the canvas's contents at lower opacity. The 'copy' blend
// mode keeps only the new content, discarding what was previously
// there. That way we don't have to use a second canvas when copying
// data
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = 0.98;
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
但是,由于设置 globalAlpha
会乘以 alpha 值,因此轨迹的 alpha 值可能接近零,但实际上永远不会达到零。这意味着图形永远不会完全褪色,在 canvas 上留下这样的痕迹,即使在几分钟内经过数千帧也不会褪色:
为了解决这个问题,我一直在逐个像素地减去 alpha 值,而不是使用 globalAlpha。减法保证像素不透明度将达到零。
// Reduce opacity of each pixel in canvas
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Iterates, hitting only the alpha values of each pixel.
for (let i = 3; i < data.length; i += 4) {
// Use 0 if the result of subtraction would be less than zero.
data[i] = Math.max(data[i] - (0.02 * 255), 0);
}
ctx.putImageData(imageData, 0, 0);
这解决了问题,但它非常慢,因为我手动更改每个像素值,然后使用昂贵的 putImageData()
方法。
是否有更高效的方法来减去而不是乘以绘制在 canvas 上的像素的不透明度?
不幸的是,我们对此无能为力,只能像您一样手动迭代像素以清除低值 alpha 像素。
问题与integer math and rounding (more details at this link有关,来自回答)。
有一些混合模式,例如“光度”(在一定程度上是“相乘”)可以用来减去亮度,问题是它适用于整个表面,而复合模式只适用于 alpha - 复合操作中没有等效项。所以这对这里没有帮助。
通过 CSS 还有一个新的亮度蒙版,但问题是图像源(理论上可以使用例如对比度进行操作)必须每帧更新,基本上,性能会很差。
解决方法
一种解决方法是使用“粒子”。也就是说,不是使用反馈循环而是记录和存储路径点,然后每帧重绘所有记录的点。在许多情况下,使用最大值并重新使用它来设置 alpha 可以正常工作。
这个简单的示例只是一个概念验证,可以通过多种方式实现,例如预填充数组、绘图顺序、alpha 值计算等。但我想你会明白的。
var ctx = c.getContext("2d");
var cx = c.width>>1, cy = c.height>>1, r = c.width>>2, o=c.width>>3;
var particles = [], max = 50;
ctx.fillStyle = "#fff";
(function anim(t) {
var d = t * 0.002, x = cx + r * Math.cos(d), y = cy + r * Math.sin(d);
// store point and trim array when reached max
particles.push({x: x, y: y});
if (particles.length > max) particles.shift();
// clear frame as usual
ctx.clearRect(0,0,c.width,c.height);
// redraw all particles at a log. alpha, except last which is drawn full
for(var i = 0, p, a; p = particles[i++];) {
a = i / max * 0.6;
ctx.globalAlpha = i === max ? 1 : a*a*a;
ctx.fillRect(p.x-o, p.y-o, r, r); // or image etc.
}
requestAnimationFrame(anim);
})();
body {background:#037}
<canvas id=c width=400 height=400></canvas>
我试图让它看起来好像我的 <canvas>
上的运动创建了运动轨迹。为了做到这一点,我没有清除帧之间的 canvas,而是通过用这样的东西替换 clearRect
调用来减少现有内容的不透明度:
// Redraw the canvas's contents at lower opacity. The 'copy' blend
// mode keeps only the new content, discarding what was previously
// there. That way we don't have to use a second canvas when copying
// data
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = 0.98;
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
但是,由于设置 globalAlpha
会乘以 alpha 值,因此轨迹的 alpha 值可能接近零,但实际上永远不会达到零。这意味着图形永远不会完全褪色,在 canvas 上留下这样的痕迹,即使在几分钟内经过数千帧也不会褪色:
为了解决这个问题,我一直在逐个像素地减去 alpha 值,而不是使用 globalAlpha。减法保证像素不透明度将达到零。
// Reduce opacity of each pixel in canvas
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Iterates, hitting only the alpha values of each pixel.
for (let i = 3; i < data.length; i += 4) {
// Use 0 if the result of subtraction would be less than zero.
data[i] = Math.max(data[i] - (0.02 * 255), 0);
}
ctx.putImageData(imageData, 0, 0);
这解决了问题,但它非常慢,因为我手动更改每个像素值,然后使用昂贵的 putImageData()
方法。
是否有更高效的方法来减去而不是乘以绘制在 canvas 上的像素的不透明度?
不幸的是,我们对此无能为力,只能像您一样手动迭代像素以清除低值 alpha 像素。
问题与integer math and rounding (more details at this link有关,来自回答)。
有一些混合模式,例如“光度”(在一定程度上是“相乘”)可以用来减去亮度,问题是它适用于整个表面,而复合模式只适用于 alpha - 复合操作中没有等效项。所以这对这里没有帮助。
通过 CSS 还有一个新的亮度蒙版,但问题是图像源(理论上可以使用例如对比度进行操作)必须每帧更新,基本上,性能会很差。
解决方法
一种解决方法是使用“粒子”。也就是说,不是使用反馈循环而是记录和存储路径点,然后每帧重绘所有记录的点。在许多情况下,使用最大值并重新使用它来设置 alpha 可以正常工作。
这个简单的示例只是一个概念验证,可以通过多种方式实现,例如预填充数组、绘图顺序、alpha 值计算等。但我想你会明白的。
var ctx = c.getContext("2d");
var cx = c.width>>1, cy = c.height>>1, r = c.width>>2, o=c.width>>3;
var particles = [], max = 50;
ctx.fillStyle = "#fff";
(function anim(t) {
var d = t * 0.002, x = cx + r * Math.cos(d), y = cy + r * Math.sin(d);
// store point and trim array when reached max
particles.push({x: x, y: y});
if (particles.length > max) particles.shift();
// clear frame as usual
ctx.clearRect(0,0,c.width,c.height);
// redraw all particles at a log. alpha, except last which is drawn full
for(var i = 0, p, a; p = particles[i++];) {
a = i / max * 0.6;
ctx.globalAlpha = i === max ? 1 : a*a*a;
ctx.fillRect(p.x-o, p.y-o, r, r); // or image etc.
}
requestAnimationFrame(anim);
})();
body {background:#037}
<canvas id=c width=400 height=400></canvas>