

Illuminated.js – 2D lights and shadows rendering engine for HTML5 applications - gren
http://blog.greweb.fr/2012/05/illuminated-js-2d-lights-and-shadows-rendering-engine-for-html5-applications/

======
marcusf
This is very cool -- I love that we're seeing more and more open source work
on making web game engines that are not Flash. Stuff like Angry Birds and Cut
the Rope were very cool, and it's good to see more frameworks and libraries
for taking some of the effort out of it and making it more mass-market.

One thing that excites me especially is that a simple game creation engine in
the browser might be a great chance to excite kids about programming with a
very low barrier to entry.

------
Jimmie
It's neat but it runs at about 5fps on my fairly new laptop. No-one is putting
that in a game and I can't imagine how it'd work in an app.

Still cool as a tech demo though.

~~~
gren
Performance has been my focus on this work, I use a lot a cache canvas to not
redraws gradient and path things but just copy caches. But i'm stuck on Canvas
and requestAnimationFrame performances. Let's hope it will continue to
improve, otherwise I could try to use WebGL but it will not be supported by
every browsers for today.

Do you use a lot of sampling in the lights? I reach good fps on my macbook
pro, especially on Chrome (but also on my home computer under Linux).

I hope we can use it with less sampling and less compute() calls for our games
:)

~~~
ajuc
I feel you. I'm doing 2d game in html5 (I have 2 rendering engines - pure
canvas, and webgl). Webgl works great, but works on small percentage of
browsers, canvas version runs with ~ 45 FPS in 1280x960 on my 2 core celeron
2Ghz. And the main problem is just fill rate - I have background, and
scrolling terrain, so I need to clear whole window every frame, and less than
50 (64x64 pixel) sprites at one time on screen. That's already too much for
canvas to redraw at 60 FPS.

~~~
de90
Are you currently prerendering to a separate canvas? Drawing it to a different
(hidden) canvas, grabbing the image data, and just pushing that to the actual
canvas might be a bit performance boost.

~~~
ajuc
I've just tested. The fastest I get (37% time in drawing according to chrome
profile, and ~45 fps) is when I draw everything moving to one, visible canvas,
that I clear at the start of each frame, and everything static (background) is
on another canvas, that isn't changed when it's not needed.

When I draw everything moving to off screen canvas, and then drawImage this
off screen canvas into visible canvas, I get 48% of time in draw function, and
~35 fps.

It is indeed faster to draw everything to off screen canvas, than to on screen
one (when I commented out copying off screen canvas to on screen canvas, and
left only drawing sprites and tiles to off screen canvas - I got steady 60
fps, but obviously nothing is visible then :)). And copying whole canvas to
the visible one is still slower, than drawing the sprites and tiles on the
visible canvas in the first place.

But thanks for ideas.

~~~
de90
Are you redrawing every time to the invisible canvas, or are you saving the
data, and just pushing that to the screen? I'm not entirely sure if this is
plausible for you situation, but take the event of a game like mario. The
ground on the bottom can be drawn one time grabbing each tile from a tile
sheet, and then you can save that bitmap data, so you cut out having to 'pick
out' each tile, and just push that data to the screen. Any chance you have an
example of what you're doing for your sprite\tile layer? I believe it can be a
bit fast this way, but we may just not be on the same page.

~~~
ajuc
I have 4096x4096 tiles 64x64 pixels each. I can't draw that to off screen
canvas at once. I only draw tiles that are visible, and terain is mostly empty
(some platform in the air), than I don't draw anything obviously.

My fastest code:

    
    
        for (var y=y1; y>=y0; --y) {
            var resultY = (0.5+
                (level.topLeft.y - camera.position.y + camera.screen.height / 2 +
                level.cellHeight + y * level.cellHeight-level.cellHeight)
            )|0; // fast clip to int
            for (var x=x1; x>=x0; --x) {
                var tileImageNo = level.layers[z].cells.valueAt([x, y])-1;
                if (tileImageNo==null || tileImageNo<=0) {
                    //nothing to do
                } else {
                    ctxOnScreenCanvas.drawImage(
                        tiles[tileImageNo],
                        (0.5+
                            (level.topLeft.x - camera.position.x + camera.screen.width / 2 +
                            level.cellWidth + x * level.cellWidth- level.cellWidth)
                        )|0, // fast clip to int
                        resultY
                    );
                }
            }
        }
    

I've also tried drawing to OffScreenCanvas in the loop, and then drawing that
canvas to screen, but it was slower.

I could try drawing to off screen canvas only when player moves out off
current off screen canvas, but that will trade small delay each turn into big
delay every N turns, and that's even worse. But I'll try that.

EDIT: cuting out not important code.

~~~
de90
Gotcha, my suggestion, might require too much change to be worthwhile, but
won't hurt to say it. Here is the assumptions I am working off of:

\- You have a background layer, lets just say it's a blue background \- A
middle layer, lets say clouds that move as you move to the right \- And a tile
layer, which draws the sprites, and the tiles of the maps

For the tile layer, we can break it down into two separate groups, animated
sprites, and static sprites.

The static sprites, can be drawn to the offscreen canvas, and you get the
imageData from that one time, and then push that data to the screen there
after. Then the animated sprites will just be drawn to the on screen canvas
every time. <http://imgur.com/n6RFF> The stuff that should be drawn to the
offscreen canvas, then grab the image data has gray around it, and the
animated sprites are in the green.

So for drawing the tiles of the map we only have to loop through the tile
data, one time, and after that if the imgData remains valid (a separate flag)
we can just push that imgData to the screen.

This would also allow drawing section of the screens on the offscreen canvas
in chunks, and storing that draw to just be pushed to the screen at
appropriate times.

I don't know if I explained it very well, I could probably through together a
crappy little demo if you'd like.

------
maigret
Nice. Looks like the lights are represented as single points, so even making
it bigger still produces the "on/off" effect when moving objects in front of
the light.

~~~
gren
you are right, I ignore the light if its center is inside an object, it was
before I implement these samples. I should still fix it, maybe it's just by
removing this check but I remember having some strange effects.

~~~
pavlov
Your comments in this thread are informative and polite, I have no idea why
someone downvoted all your comments...

------
emddudley
I found a bug. At a certain distance the shadow disappears for an object.

[http://demo.greweb.fr/illuminated.js/#{%22lights%22:[{%22ins...](http://demo.greweb.fr/illuminated.js/#{%22lights%22:\[{%22instance%22:%22Lamp%22,%22position%22:{%22x%22:377.5,%22y%22:215.316650390625},%22distance%22:170,%22diffuse%22:0.8,%22color%22:%22rgba%28254,191,136,0.8718296881574732%29%22,%22radius%22:3,%22samples%22:9,%22angle%22:0,%22roughness%22:0})],%22objects%22:[{%22instance%22:%22DiscObject%22,%22center%22:{%22x%22:313.5,%22y%22:190.316650390625},%22radius%22:34},{%22instance%22:%22DiscObject%22,%22center%22:{%22x%22:175.5,%22y%22:146.316650390625},%22radius%22:38},{%22instance%22:%22PolygonObject%22,%22points%22:[{%22x%22:426.5,%22y%22:82.316650390625},{%22x%22:449.5,%22y%22:189.316650390625},{%22x%22:481.5,%22y%22:143.316650390625},{%22x%22:487.5,%22y%22:94.316650390625},{%22x%22:487.5,%22y%22:87.316650390625},{%22x%22:433.5,%22y%22:32.316650390625},{%22x%22:381.5,%22y%22:42.316650390625},{%22x%22:346.5,%22y%22:136.316650390625},{%22x%22:346.5,%22y%22:138.316650390625}]}],%22globals%22:{%22maskcolor%22:%22rgba%280,0,0,0.9%29%22}}

------
fsckin
After playing around a bit, my browser history has thousands of entries from
this site.

~~~
gren
Sorry for that, I though having this

    
    
        history.replaceState({}, title, "#"+hash);
    

would have fixed it.

(
[https://github.com/gre/illuminated.js/blob/master/demo.js#L1...](https://github.com/gre/illuminated.js/blob/master/demo.js#L162)
)

Any idea?

~~~
talmand
I'm curious behind the reasoning in putting that information in the URL in the
first place.

~~~
gren
you can refresh / bookmark / share the page URL at any time and you will
retrieve your work.

And also I don't have to store anything on my server, bit.ly like services are
the new way of storing the information :)

------
silverlight
Have you considered MIT (or at least LGPL) instead of GPL licensing? Only
other open source games or apps will be able to make use of this as-is. Of
course that's certainly your right to do, just wanted to ask.

~~~
gren
AFAIK, with GPL you are allow to use the library in a proprietary project but
if you modify it, you must publish the library modification, not the whole
project.

Am I wrong?

~~~
silverlight
That's not correct (at least not according to my understanding). GPL license
"attaches" itself to any project that uses it. So by including any GPL
licensed library, you must also release the source code of the thing you are
including it in. The LGPL allows you to use the library in a project and only
commit back changes to the library but not open source the project you use it
in. So if that's your intent (to just make sure that library-level changes are
contributed back, but not to only restrict usage of the library to fully open-
source projects), I would suggest going with LPGL (or, again, MIT, if you want
people to be the most comfortable including your library in their projects).
More info here:

[http://stackoverflow.com/questions/94346/can-i-legally-
incor...](http://stackoverflow.com/questions/94346/can-i-legally-incorporate-
gpl-lgpl-open-sourced-software-in-a-proprietary-cl)

~~~
gren
Thanks for that, it was always unclear in my mind, now it's clear!

ok so I'll move it to LGPL soon then ;)

Yep, This is what I want for Illuminated.js. I prefer to have benefits of the
library modifications. It's a bit more restrictive coming from MIT (I used to
use it for some projects) but it's not as restrictive as a "non-commercial
only" license, so IMO it's quite ok, it's a minimum of thanks to the library.

Regards

~~~
Arelius
The problem with the LGPL is that it's designed for code that is compiled and
binary linked, which makes it harder to reason about for JS code.

Additionally, afaict, it's problematic to use on embedded, closed platforms,
such as iOS.

------
Arelius
So, if I have one light, and an object casting a shadow, and then another
object inside the first's shadow, the area that is obscured by both objects is
darker than one obscured by just a single, this is an inaccurate lighting
model, and looks sorta ugly.

------
erikb
Now the site seems down. Does anyone have an alternative link?

------
darkstalker
cool, but I think it should've done in webGL

~~~
gren
you are right! For sure it would be more efficient, and shaders are incredibly
powerful. I have considered that, but it wouldn't work everywhere today. So
for now I have this implementation I will use for my web games ;)

------
jhugg
I was really hoping this would put gold-leaf illustrations in the margins of
my webpage for me.

------
cao825
Very cool. One issue I saw was that the shadows actually show on top of other
objects.

~~~
gren
Thanks.

Yeah, I need to fix that, this is link to my wrong sampling implementation.

If you use only 1 sampling you will see it is fixed.

~~~
talmand
Related to that is that objects overlapping create overlapping shadows. Which
makes sense considering how your render engine seems to work.

Might I suggest instead of a solution in real time, how about a static deal
that essentially generates a lightmap to overlay on top of the scene? I can
think of several uses for that.

------
hackermom
This was pretty cool, but I couldn't help being struck by how utterly poorly
it performs, especially as soon as you add just a handful of objects and two
light sources. Sure, it's written in JS, but JS has come a long way in terms
of performance, and so have our computers.

This demonstration instantly makes me think of Blizzard's Diablo II from 2000,
which features an identical effect during gameplay, with a seemingly infinite
number of shadow-generating obstacles and as many light sources casting
shadows as there are players on the screen, and it runs perfectly smooth even
on a 500mhz P3.

~~~
talmand
I think you're comparing an apple tree with a matter replicator that makes
apples.

