【问题标题】:How can I animate a gradient with large pixels?如何为大像素的渐变设置动画?
【发布时间】:2019-07-23 01:51:03
【问题描述】:

我正在做一个项目,我希望让黑暗覆盖屏幕,让角色在黑暗中发光。我尝试为场景设置动画,然后使用以下代码在其上绘制黑暗:

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;

var pixelSize = 30;
var width = canvasWidth/pixelSize;
var height = canvasHeight/pixelSize;

var lightX = canvasWidth/2;
var lightY = canvasHeight/2;

var lightDiameter = 100;
var a = lightDiameter*pixelSize;

for(var x = 0; x < width; x++) {
  for(var y = 0; y < height; y++) {
    var alpha = 1.25 - a/(Math.pow(x*30 - lightX, 2) + Math.pow(y*30 - 
lightY, 2));
    ctx.fillStyle = "rgba( 25, 25, 30," + alpha + ")";
    ctx.fillRect(x*pixelSize, y*pixelSize, pixelSize, pixelSize);
  }
}

这运行得很好,我喜欢它的外观,但是当它与其他代码一起重复动画时,它显着减慢了其余代码。我认为一个可能的解决方案可能是以某种方式绘制具有较低“质量?”的渐变,我考虑的另一个解决方案是将此绘图保存在单独的画布中并将其绘制到播放器位置,但这将使其无法添加多个光源,我想通过简单地添加它们的效果来做到这一点。我可能只需要处理滞后问题,而且我对这些东西很陌生,但如果有人能帮助我,那就太好了。

为了澄清,我在绘图循环中使用此代码,并且在每次迭代中都重新计算它。我更愿意以这种方式重新计算,这样我就可以拥有多个移动的光源。

【问题讨论】:

  • 我对 Canvas 了解不多,但你可以使用 CSS,可能:rgraph.net/canvas/docs/…
  • 如果我们能看到你如何将它与你的渲染循环集成,那么回答这个问题会更容易。例如:如果渐变图像没有变化,您可以将其缓存在虚拟画布中并使用 drawimage 将其放在画布上。如果您在渲染循环内调用 document.getElementById,这可能会减慢您的代码,您可以通过在循环外执行此操作来加快速度。
  • 感谢您的建议。希望编辑能解决问题,对此感到抱歉。
  • 你可能想要迁移到 WebGL,为此我建议使用 Pixi.js
  • @aptriangle 我认为调用document.getElementById 不会对性能产生任何严重影响:stackoverflow.com/questions/12514970/…。

标签: javascript html5-canvas radial-gradients


【解决方案1】:

这是因为fillRect 与其他方法相比非常慢。您可以改用ImageData 对象来加快速度。

这样做的方法是将所有内容渲染到画布上,获取相应的ImageData,修改其内容并将其放回画布上:

var ctx = canvas.getContext("2d");
// render stuff here
var imageData = ctx.getImageData(0,0,canvasWidth,canvasHeight);
for (let x=0;x<canvasWidth;x++){
    for (let y=0;y<canvasHeight;y++){
        let i = (x+y*canvasWidth)*4;
        let alpha = calculateAlpha(x,y); // your method here (should result in a value between 0 and 1)
        imageData.data[i] = (1-alpha)*imageData.data[i]+alpha*25;
        imageData.data[i+1] = (1-alpha)*imageData.data[i+1]+alpha*25;
        imageData.data[i+2] = (1-alpha)*imageData.data[i+2]+alpha*30;
        imageData.data[i+3] = 1-(1-alpha)*(1-imageData.data[i+3]);
    }
}
ctx.putImageData(imageData,0,0);

这应该在每个像素的基础上进行照明,并且比一直使用clearRect 快得多。但是,它可能仍然会减慢速度,因为您每帧都要进行大量计算。在这种情况下,您可以通过使用 css 在位于主画布上方的第二个画布中进行照明来加快速度:

<div id="container">
    <canvas id="canvas"></canvas>
    <canvas id="lightingCanvas"></canvas>
</div>

CSS:

#container {
     position: relative;
}
#canvas, #lightingCanvas {
    position: absolute;
    top: 0;
    left: 0;
}
#container, #canvas, #lightingCanvas {
     width: 480px;
     height: 360px;
}

Javascript:

var canvas = document.getElementById("lightingCanvas")
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(25,25,30)";
ctx.fillRect(0,0,canvas.width,canvas.height);
var imageData = ctx.getImageData(0,0,canvasWidth,canvasHeight);
for (let x=0;x<canvasWidth;x++){
    for (let y=0;y<canvasHeight;y++){
        let i = (x+y*canvasWidth)*4;
        let alpha = calculateAlpha(x,y); // your method here (should result in a value between 0 and 1)
        imageData.data[i+3] = 255*alpha;
    }
}
ctx.putImageData(imageData,0,0);

这样浏览器会为您处理混合,您只需要插入正确的 alpha 值 - 所以现在渲染应该更快。

这也将允许您将大像素带回 - 只需在第二个画布上使用较低的分辨率并使用一些 css 效果(如image-rendering: -webkit-crisp-edges)使画布在放大时像素化。

【讨论】:

  • fillRect 实际上相当快。我在这里用一些正在运行的计时器创建了一个小提琴,它看起来比你的第二个函数快四倍:jsfiddle.net/9hco6zyr 在 300x300 的图像上,在我的电脑上,运行他的代码需要大约三分之一毫秒,所以我认为这更多是关于在他的渲染循环中运行的其他事情,而不是 fillrect 的实际性能问题
  • 哦,好吧,我没注意他用的是什么像素大小。如果我将像素大小更改为 10 或以下(只是在您的小提琴中编辑了值),那么 ImageData 方法肯定会更快。另外,请记住,这也可以通过使用较小的画布来加快速度,如我的答案后半部分所述。
  • 好点。看到性能跨越的点很有趣。您可能需要编辑代码以修复“让 i = x+ycanvasWidth;”应该是“让 i = (x+ycanvasWidth)*4;”并将右括号放在“x++”之后
  • 老实说,我以前没有使用过“let”,但它似乎像“var”一样工作,但为什么要使用它呢?它更快吗?它只在循环中运行良好吗?
  • @OtherMe 通常,let 在块级范围内定义一个变量,因此在这种情况下,i 只能在 for 循环内访问。我不知道为什么我在那里使用它,var 可能会同样好用。
猜你喜欢
  • 2020-12-29
  • 1970-01-01
  • 1970-01-01
  • 2018-02-04
  • 2017-07-25
  • 2013-06-27
  • 2019-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多