画布 - 在所有完全透明的区域填充一个矩形 [英] Canvas - Fill a rectangle in all areas that are fully transparent
问题描述
我正在使用 HTML5 画布编写一个简单的 2D 游戏引擎.我来添加一个照明引擎.每个光源都有一个半径值和一个强度值(0-1,例如 1 表示非常亮).还有一个环境光值用于照亮世界上不靠近光源的所有其他事物(0-1,例如 0.1 将是月光).照明过程在主画布上方的单独画布上完成:
- 对于每个光源,在该位置绘制一个径向渐变,其半径与光源相同.渐变有两个停靠点:中心为黑色,alpha 为 1 强度的光,末端/边缘为黑色,alpha 为 1 环境光值.一切正常.
- 这就是出错的地方:/我需要用黑色和 1 环境光值的 alpha 填充整个画布,目前我通过将 context.globalCompositeOperation 设置为 source-out 然后填充整个画布来做到这一点帆布.
我的代码是:
var amb = 'rgba(0,0,0,' + (1-f.ambientLight) + ')';for(i in f.entities) {var e = f.entities[i], p = f.toScreenPoint(e.position.x, e.position.y), radius = e.light.radius;如果(半径> 0){var g = cxLighting.createRadialGradient(p.x, p.y, 0, p.x, p.y, radius);g.addColorStop(0, 'rgba(0,0,0,' + (1-e.light.intensity) + ')');g.addColorStop(1, amb);cxLighting.fillStyle = g;cxLighting.beginPath();cxLighting.arc(p.x, p.y, radius, 0, Math.PI*2, true);cxLighting.closePath();cxLighting.fill();}}//环境光cxLighting.globalCompositeOperation = 'source-out';cxLighting.fillStyle = amb;cxLighting.fillRect(0, 0, f.width, f.height);cxLighting.globalCompositeOperation = 'source-over';
然而,我得到了一种反向渐变(右),而不是从引擎中得到我想要的东西(左).我认为这是因为当我使用 source-out
复合操作绘制矩形时,它会影响渐变本身的颜色,因为它们是半透明的.
有没有办法以不同或更好的方式做到这一点?可能使用剪裁,还是先在所有内容上绘制矩形?
此外,我修改了 Mozila 开发中心关于堆肥的
额外优化:您实际上可以在不使用任何路径的情况下实现效果,只需将完全相同的径向渐变绘制到矩形而不是圆形路径上即可:
http://jsfiddle.net/a2Age/2/希望有帮助!
I'm writing a simple 2D game engine using the HTML5 canvas. I've come to adding a lighting engine. Each light source has a radius value and an intensity value (0-1, eg 1 would be very bright). There's also an ambient light value that is used to light everything else in the world that isn't near a light source (0-1, eg 0.1 would be moonlight). The process of lighting is done on a separate canvas above the main canvas:
- For each light source, a radial gradient is drawn at that position with the same radius as the light source. The gradient is given two stops: the center is black with an alpha of 1-intensity of the light and the end/edge is black with alpha of 1-ambient light value. That all works fine.
- This is where it goes wrong :/ I need to fill the whole canvas with black with and alpha of 1-ambient light value and at the moment I do this by setting the context.globalCompositeOperation to source-out then fillRecting the whole canvas.
My code for this stuff is:
var amb = 'rgba(0,0,0,' + (1-f.ambientLight) + ')';
for(i in f.entities) {
var e = f.entities[i], p = f.toScreenPoint(e.position.x, e.position.y), radius = e.light.radius;
if(radius > 0) {
var g = cxLighting.createRadialGradient(p.x, p.y, 0, p.x, p.y, radius);
g.addColorStop(0, 'rgba(0,0,0,' + (1-e.light.intensity) + ')');
g.addColorStop(1, amb);
cxLighting.fillStyle = g;
cxLighting.beginPath();
cxLighting.arc(p.x, p.y, radius, 0, Math.PI*2, true);
cxLighting.closePath();
cxLighting.fill();
}
}
//Ambient light
cxLighting.globalCompositeOperation = 'source-out';
cxLighting.fillStyle = amb;
cxLighting.fillRect(0, 0, f.width, f.height);
cxLighting.globalCompositeOperation = 'source-over';
However instead of getting what I wan't out of the engine (left) I get a kind of reversed gradient (right). I think this is because when I draw the rectangle with the source-out
composite operation it affects the colours of the gradient itself because they are semi-transparent.
Is there a way to do this differently or better? Using clipping maybe, or drawing the rect over everything first?
Also, I modified the Mozila Dev Centre's example on composting to replicate what I need to do and none of the composite modes seemed to work, check that out if it would help.
Thanks very much, any answer would be great :)
One trivial way would be to use imageData
but that would be painfully slow. It's an option, but not a good one for a game engine.
Another way would be to think of the ambient light and the light-source as if they were one path. That would make it very easy to do:
Or see it with an image behind: http://jsfiddle.net/HADky/10/
The thing you're taking advantage of here is the fact that any intersection of a path on canvas is always only unioned and never compounded. So you're using a single gradient brush to draw the whole thing.
But it gets a bit trickier than that if there's more than one light-source. I'm not too sure how to cover that in an efficient way, especially if you plan for two light-sources to intersect.
What you should probably do instead is devise an alpha channel instead of this overlay thing, but I can't currently think of a good way to get it to work. I'll revisit this if I think of anything else.
EDIT: Hey! So I've done a bit of thinking and came up with a good solution.
What you need to do is draw a sort of alpha channel, where the dark spots mark the places where you want light to be. So if you had three light sources it would look like this:
Then you want to set the fill style to your ambient color and set the globalCompositeOperation to xor and xor the whole thing.
ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0,0,500,500);
That will leave you with the "opposite" image except the transparent parts will be correctly ambient:
Here's a working example of the code:
Extra optimization: You can actually achieve the effect without using any paths at all, by simply drawing the exact same radial gradients onto rects instead of circular paths:
http://jsfiddle.net/a2Age/2/ Hope that helps!
这篇关于画布 - 在所有完全透明的区域填充一个矩形的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!