【问题标题】:Multiple light sources on canvas画布上的多个光源
【发布时间】:2013-11-05 10:51:32
【问题描述】:

我想在我正在制作的游戏的背景上放置多个光源,这与一个光源配合使用效果很好,如下所示:

这是通过将 .png 图像放在其他所有内容之上来实现的,该图像对中心变得更加透明,如下所示:

适用于一个光源,但我需要另一种方法来添加更多光源并移动光源。

我考虑过为每一帧逐个像素地绘制一个类似的“阴影层”,并根据到每个光源的距离计算透明度。但是,这可能会很慢,我相信有更好的解决方案来解决这个问题。

图片只是示例,每一帧都会有更多的内容可以使用 requestAnimationFrame 移动和更新。

有没有一种轻量级和简单的方法来实现这一点?提前致谢!

编辑

在 ViliusL 的帮助下,我想出了这个遮罩解决方案:

http://jsfiddle.net/CuC5w/1/

// Create canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 300;
document.body.appendChild(canvas);

// Draw background
var img=document.getElementById("cat");
ctx.drawImage(img,0,0);

// Create shadow canvas
var shadowCanvas = document.createElement('canvas');
var shadowCtx = shadowCanvas.getContext('2d');
shadowCanvas.width = canvas.width;
shadowCanvas.height = canvas.height;
document.body.appendChild(shadowCanvas);

// Make it black
shadowCtx.fillStyle= '#000';
shadowCtx.fillRect(0,0,canvas.width,canvas.height);

// Turn canvas into mask
shadowCtx.globalCompositeOperation = "destination-out";

// RadialGradient as light source #1
gradient = shadowCtx.createRadialGradient(80, 150, 0, 80, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);

// RadialGradient as light source #2
gradient = shadowCtx.createRadialGradient(220, 150, 0, 220, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);

【问题讨论】:

  • 好吧,如果你有兴趣,我还有另一个建议:看这里看结果(尤其是正确的蜡烛)jsfiddle.net/gamealchemist/ABfVj
  • 很好的解决方案!我会在实际游戏中试一试,看看哪个效果最好。
  • 关于您的 1 px 问题:请注意,即使恕我直言,这是一个错误,几何体的笔划是居中的,它不会被几何体尺寸所覆盖。所以当你做 ctx.arc(.,., r ,...) + ctx.stroke();你的意思可能是 ctx.arc(.,., r - ctx.lineWidth/2 , ...) + ctx.stroke();

标签: javascript canvas effects


【解决方案1】:

另一种使用光的方法是使用 globalCompositeOperation 模式 'ligther' 来照亮事物,而只需使用 globalAlpha 来使事物变暗。

首先这是一张图片,左边是卡通灯光,右边是更逼真的灯光,但你宁愿看小提琴,因为它是动画的:
http://jsfiddle.net/gamealchemist/ABfVj/

那么我是怎么做的:

变暗:
- 选择变暗的颜色(最有可能是黑色,但您可以选择红色或其他颜色来修饰结果)。
- 选择不透明度(0.3 似乎是一个不错的起始值)。
- fillRect 想要变暗的区域。

function darken(x, y, w, h, darkenColor, amount) {
    ctx.fillStyle = darkenColor;
    ctx.globalAlpha = amount;
    ctx.fillRect(x, y, w, h);
    ctx.globalAlpha = 1;
}

减轻:
- 选择亮色。请注意,此颜色的 r,g,b 将添加到前一点的 r,g,b :如果您使用高值,您的颜色会被烧毁。
- 将 globalCompositeOperation 更改为“更轻”
- 您也可以更改不透明度,以更好地控制亮度。
- fillRect 或圆弧你想要变亮的区域。

如果在较亮模式下绘制多个圆圈,结果会相加,因此您可以选择一个相当低的值并绘制多个圆圈。

function ligthen(x, y, radius, color) {
    ctx.save();
    var rnd = 0.03 * Math.sin(1.1 * Date.now() / 1000);
    radius = radius * (1 + rnd);
    ctx.globalCompositeOperation = 'lighter';
    ctx.fillStyle = '#0B0B00';
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * π);
    ctx.fill();
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(x, y, radius * 0.90+rnd, 0, 2 * π);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x, y, radius * 0.4+rnd, 0, 2 * π);
    ctx.fill();
    ctx.restore();
}

请注意,我添加了一个正弦变化以使光线更加生动。

Ligthen:另一种方式:
您还可以在仍然使用 'ligther' 模式的同时,使用渐变来获得更平滑的效果(第一个更像卡通,除非你画了很多圆圈。)。

function ligthenGradient(x, y, radius) {
    ctx.save();
    ctx.globalCompositeOperation = 'lighter';
    var rnd = 0.05 * Math.sin(1.1 * Date.now() / 1000);
    radius = radius * (1 + rnd);
    var radialGradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
    radialGradient.addColorStop(0.0, '#BB9');
    radialGradient.addColorStop(0.2 + rnd, '#AA8');
    radialGradient.addColorStop(0.7 + rnd, '#330');
    radialGradient.addColorStop(0.90, '#110');
    radialGradient.addColorStop(1, '#000');
    ctx.fillStyle = radialGradient;
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * π);
    ctx.fill();
    ctx.restore();
}

我还在这里添加了一个 sin 变体。
Rq:在每次绘制时创建渐变会产生垃圾:如果使用单个渐变,请存储渐变,如果要为渐变设置动画,则将它们存储在数组中。
如果您在多个地方使用相同的灯光,请构建一个以 (0,0) 为中心的渐变,并在始终使用该单一渐变进行绘制之前平移画布。

Rq 2 :您可以使用剪辑来防止屏幕的某些部分变亮(如果有障碍物)。 我在示例中添加了蓝色圆圈来显示这一点。

因此,您可能希望使用这些效果直接照亮您的场景,或者单独创建一个光层,在将其绘制到屏幕上之前根据需要将其变暗/变亮。

这里有太多的场景来讨论它们(灯光动画与否,剪辑与否,预计算灯光图层与否,...)但就速度而言,对于 Safari 和 iOS safari,使用 rect/arc 绘制的解决方案 - 使用渐变或实心填充 - 将比绘制图像/画布更快。
在 Chrome 上,情况正好相反:当几何数增加时,绘制图像比绘制每个几何图形要快。
Firefox 在这方面与 Chrome 很相似。

【讨论】:

  • 事实证明这对我正在做的事情要好得多,并且还解决了我遇到的许多其他问题。非常感谢您花时间整理这些!在移动灯光时,我确实遇到了一个小问题。一个 1px 的圆圈/边框出现在光环的边缘,并在动画停止时再次消失。可能只是我的实现出了点问题。
  • 好吧,对于 1px 的问题,我想如果没有更多信息,我就无能为力了……但我想你会弄清楚的。很高兴我能帮助你 ! @MarkE:是的,我喜欢这种效果:做起来很简单,而且看起来很棒!
  • 我在动画期间未关闭的先前路径中发现了问题。这个解决方案的唯一缺点是在darken 上较深的颜色和黑色不会低于零,但在ligthen 上会增加到零以上,这使得一切都失去对比度并且看起来褪色了。特别是在我的游戏中,因为我使用像素化图形,其中对象、角色和物品都有黑色轮廓。
  • 正如我在帖子中所说的,有很多可能的方法来构建灯光效果。您可以先变亮,也可以在变暗后进行剪辑,以避免对屏幕的某些部分产生影响,并且可以使用不透明度来降低效果的强度。这完全取决于你做事的顺序。如果您想在具有色调的卡通图像上获得高对比度,请使用另一种方法分割轮廓绘制,并始终以完全不透明的黑色绘制轮廓。
【解决方案2】:
  1. 您的 png 应该有完全透明的角,而不是中间的透明白色。
  2. 或者你可以画这个,但不能像这里 jsfiddle.net/pr9r7/2/ 那样逐像素绘制

更多示例:jsfiddle.net/pr9r7/3/http://codepen.io/cwolves/pen/prvnb

【讨论】:

  • 据我所知,这只会创建一个覆盖背景的白色(或彩色)斑点。不是真正的光源,增加下面的亮度。我的例子中的火炬在它下面是不可见的。我想在这里得到正确的结果:imgur.com/LLRt3wW
  • 取决于 png - jsfiddle.net/pr9r7/3 更好?还要检查这个例子codepen.io/cwolves/pen/prvnb
  • codepen 示例与我刚刚提出的解决方案非常接近! jsfiddle.net/CuC5w。我将仔细研究该示例,看看是否有任何改进。谢谢!
【解决方案3】:

这是我的看法:

A. 在您尝试之前不要担心性能。 Canvas 的绘图速度非常快。

B. 而不是具有暗角和透明中间的图像。为什么不尝试让它更“IRL”,让整个世界更黑暗,让光源照亮该区域?突出一个小区域,而不是使除小区域之外的所有内容变暗。

【讨论】:

  • A) 你是对的!只是因为我还没有完全弄清楚那个解决方案,所以我想我会在尝试之前看看是否有更好的方法。 B)这正是我想做的!在不覆盖背景的情况下,我只是想不出一种方法。当然,我可以从光源中淡出白色而不是从边缘淡出黑色,但这不会使背景变亮,只会覆盖它。像这样(在左边):imgur.com/LLRt3wW
  • 嗯,GameAlchemist 似乎也有一个不错的解决方案^^
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 2019-06-26
  • 2015-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多