简体   繁体   中英

Javascript How to make multiple light effects in a canvas

I'm trying to make some light effects for a game and when i use one only light it works fine but i can't make multiple lights.

app.js

let canvas = document.getElementById("game")
let context = canvas.getContext('2d')

// Drawing: background, tilemap, objects, player, monsters, npc, etc.
context.fillStyle = 'white'
context.fillRect(0, 0, canvas.width, canvas.height)

let x = 100, y = 100, radio = 70
let gradient = context.createRadialGradient(x, y, 0, x, y, radio)
// Gradient's color from 0% to 100% is white with alpha 0.
gradient.addColorStop(0, 'rgba(255, 255, 255, 0)')
// Gradient's color from 100% to out is black with alpha 1.
gradient.addColorStop(1, 'rgba(0, 0, 0, 1)')
context.fillStyle = gradient
context.fillRect(0, 0, canvas.width, canvas.height)

// Draw UI: Buttons, Hp/Sp bars, etc.

I'm trying to make a black rect and print it some circles with alpha 0 then use context.fillRect(0, 0, canvas.width, canvas.height) to draw it over my tilemap, is this the way to do this?.

Edit:

I don't want brightness effect, what i'm trying to make is a black screen(covering all canvas size), print it "holes"(circles, lights) and then draw it over my game making shadow effects.

Use globalCompositeOperation

This will control how the rendered content is composited with the canvas. In the example use ctx.globalCompositeOperation = "lighter"; to add to the pixel RGB channels.

Use transform

Rather than create a new gradient each time you want to move a light. Create the gradient for the light once, and move it via the transform. In the example I use setTransform to position a light. Then draw the arc at 0, 0 so I don't need to create a new gradient each time.

Fool the eye

We humans know how lights should look so drawing a light that does not take a some of lights properties will not fool the eye. Using a linear (only 2 color stops) gradient will never look right

Thought the 2D canvas does not provide a way to do high quality lighting in real time, you can create the gradient using fall off of intensity due to distance from the light source, and reflected intensity due to the angle the light ray hits the surface as path of the gradient

As we only need to create the gradient once per light we can add a lot of detail to it when creating. See example function Light(x, y, size, r, g, b)

Example

Click and hold on canvas to add lights. Lights slowly move of canvas and fade as they go. Some devices may have trouble (slow down) with lots of lights.

The lights have been set up to have many different levels , some barely visible, others over exposing. Best viewed full screen.

 var W, H; const LIGHT_ADD_TIME = 10; const lights = []; var canAddTimer = 0; const ctx = canvas.getContext("2d"); function Light(x, y, size, r, g, b) { // color must be in the form #FFFFFF this.x = x; this.y = y; this.alpha = 1; const dir = rand(Math.PI * 2); const speed = rand(0.1, 2); this.dx = Math.cos(dir) * speed; this.dy = Math.sin(dir) * speed; this.alphaStep = rand(1) < 0.08 ? rand(0.1, 0.01) : rand(0.001, 0.005); this.size = size; this.grad = ctx.createRadialGradient(0,0,0, 0, 0, size); const step = 1 / size; var i = 0; var h = size / 2, hSqr = h * h; const br = r * hSqr, bg = g * hSqr, bb = b * hSqr, aa = 256 * hSqr; h = rand(h, size * 2), hSqr = h * h; while(i < 1) { const distSqr = (i * size) ** 2 + hSqr; const ref = (h / distSqr ** 0.5) / distSqr; this.grad.addColorStop(i, hexCol(br * ref, bg * ref, bb * ref, aa * ref * (1-i))); i += step; } this.grad.addColorStop(1,"#0000"); } Light.prototype = { update() { this.x += this.dx; this.y += this.dy; this.alpha -= this.alphaStep; if (this.alpha < 0 || this.x + this.size < 0 || this.x - this.size > W || this.y + this.size < 0 || this.y - this.size > H) { return false; } return true; }, draw() { ctx.setTransform(1,0,0,1,this.x, this.y); ctx.globalCompositeOperation = "lighter"; ctx.globalAlpha = this.alpha ** 2; ctx.fillStyle = this.grad; ctx.beginPath(); ctx.arc(0, 0, this.size, 0, Math.PI * 2); ctx.fill(); } } function update(){ var i = 0; sizeCanvas(); const size = Math.min(W,H); ctx.setTransform(1,0,0,1,0,0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.fillStyle = "#321"; ctx.fillRect(0,0,W,H); ctx.fillStyle = "#354"; ctx.fillRect(size / 12,size / 12,W -size /6,H -size /6); ctx.fillStyle = "#234"; ctx.fillRect(size / 8,size / 8,W -size /4,H -size /4); ctx.fillStyle = "#435"; ctx.fillRect(size / 4,size / 4,W -size /2,H -size /2); if (canAddTimer <= 0) { if(mouse.button) { canAddTimer = LIGHT_ADD_TIME; const size = rand(Math.min(W,H) / 3, Math.min(W,H) * 2 ); const col = randI(128,256); const L = new Light(mouse.x, mouse.y, size, randI(400,1256), randI(400,1256), randI(400,1256)); lights.push(L); } } else { canAddTimer--; } while (i < lights.length) { const L = lights[i]; if(L.update() === false) { lights.splice(i,1); } else { i++ } } lights.forEach(L => L.draw()); ctx.globalCompositeOperation = "source-over"; requestAnimationFrame(update); } const mouse = {x: 0, y: 0, button: false}; function mouseEvents(e){ mouse.x = e.pageX; mouse.y = e.pageY; mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button; } ["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents)); const randI = (min = 2, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0; const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min; const randHex = (m, M) => randI(m, M).toString(16).padStart((M > 16 ? 2 : 1), "0"); const randCol = (m, M) => "#" + randHex(m,M) + randHex(m,M) + randHex(m,M); const hexCol = (r,g,b,a) => "#" + ((r > 255 ? 255 : r < 0 ? 0 : r) | 0).toString(16).padStart(2, "0") + ((g > 255 ? 255 : g < 0 ? 0 : g) | 0).toString(16).padStart(2, "0") + ((b > 255 ? 255 : b < 0 ? 0 : b) | 0).toString(16).padStart(2, "0") + ((a > 255 ? 255 : a < 0 ? 0 : a) | 0).toString(16).padStart(2, "0"); function sizeCanvas() { if (innerWidth !== W || innerHeight !== H) { W = canvas.width = innerWidth; H = canvas.height = innerHeight; } } requestAnimationFrame(update);
 body { user-select: none } canvas { position: absolute; top: 0px; left: 0px; } div { position: absolute; color: white; top: 10px; left: 10px; pointer-events: none; }
 <canvas id="canvas"></canvas> <div>Click to add moving lights</div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM