• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

OFFICIAL Realtime 2D lighting - Part 1

Mike

nobody important
GMC Elder
We have a new tech blog up at YoYo Games, and it's all about the lights!

Realtime 2D Lighting in GameMaker Studio 2 - Part 1

"One of the more advanced things developers want to do is create their own 2D lighting engine, but the reality of it sometimes gets a little overwhelming. In this post I'd like to cover the basics of a light, what "blockers" are, and how to cast some shadows from them in a reasonably efficient manner."



http://www.yoyogames.com/blog/419
 
P

P-Star7

Guest
I'm sad to see that this hasn't got any replies. Although I'm not interested in using it (due to wanting to make a sidescroller instead of top-down) this looks very comprehensive.
 
Didn't feel the need to reply(I put a like on the post instead), but on second thoughts actually fair enough. I read the blog post previously, it's great.

Thanks @Mike for taking the time to write this up. One of the clearest explanations I've read on 2d lighting, plus easy to understand implementation details for GMS 2.
 
P

Pudsy

Guest
Nice to see another tech blog! Always an interesting read. Thanks for keeping them going.
 

AndrewN

Member
I really appreciated this blog. I have tried a few times to create a lighting routine like this but could never get the performance I needed. Now in two days based on this code I have coded a full lighting system with mutliple coloured lights and over lapping shadows running at a very decent speed. Thanks Mike for posting this.
 
K

Kios

Guest
Hey Mike, first of great post, this is one of those needed topics that as Andrew said gets caught on performance.

Now i have a few comments/questions about the post.
First I think that "tilemap_get" should have a mention, at least in the docs that cell x is not coordinate x(as in pixel), but rather a number that tells you which tile we are talking about. Maybe an OBS at the end of the doc.
I found this confusing when i saw "var px1 = xx*tile_size;", Until i realized that tilemap_get had to be position and not absolute values.

Further down you confused me because it seemed you said that c² = a²+b² would always have c = 1. o_O
Then later on i realized you were actually talking about using pitagoras to get c, then do a/c, b/c to get sin/cos then sin²+cos² = 1.
I think that's what you were referring to.

Later on i didn't really get how the whole thing worked, i mean what makes the projections be draw with the mouse_pos as a point of origin.
For me, looking at the code there is no indication of direction in any moment, so assuming 0,0 as a starting point and positive x,y values going right and down respectively; Looking at adx(for instance), it is the x of a +[ the_shadow* the proportional(sin/cos) length of the respective side of the triangle], or in other words a really big positive number.
So in my world this is some x value(positive) + a really big x value(+) , which would lead exclusively to a bigger x value, meaning something to the right.

And at the very end, in the "cross product" that you made, unless I missed something it will only equal 0, and not fail the if statement, if you compare top left with bottom right, which you never will since it's the diagonal of the square. So i didn't really see how that helps.

Another thing, and this might be a gms way of coding, you did 1.0*SHADOW_LENGTH, is this you way to casting the number to a floating point with a 0.1 precision, or maybe normal f.point precision? Why did you add the 1.0?

If you got the time let me know,
thanks either way, and feel free to also pm me if necessary. :)
 

Mike

nobody important
GMC Elder
tilemap_get() is vastly different from tilemap_get_pixel(), and we have the online manual if you need help with these API calls :)

I also said sqrt( (Adx*Adx)+(Ady*Ady) )==1.0 The sqrt() is the important part for the length of 1.0. This is basic Pythagoras's theorem

The 1.0*length was left in as long hand explanation, but I'll accept it perhaps doesn't help. If you look at the code above on how to get a unit vector, it divides the 2 deltas by the square root. Now divide is a slow instruction (well...was, not so much now), and a common speed up was to do len=1.0/len, then do adx*len and ady*len ad multiply is a faster. You would then multiply these values by the shadow length. So basically 1.0*shadow_length was a way of reducing the multiples needed, but I left in the 1.0 as an indicator that it's now a scaler, not a divisor. But you're perfectly right, you don't need it.

There are lots of things in there you can optimise that I've long handed for clarity. The shadow_length should be a #macro so it's a constant for a start, technically you can get rid of the colour in the vertex format if you have your own shader - lots of things.


As to the rest..... If you paste all the code together it does work, you can then play with it and figure it out more at your leisure.
 

Mike

nobody important
GMC Elder
Well done! I like seeing it with 3/4 perspective walls, that's really nice. I should add it to the Dungeon demo in GMS2 :)
Still a little way to go with performance though! ;) you can optimise this basic idea a lot!

Here's mine. 16 moving lights.... 800+ fps

I may well stick this up on the Marketplace for free later. I want to expand it a bit first.
 
K

Kios

Guest
tilemap_get() is vastly different from tilemap_get_pixel(), and we have the online manual if you need help with these API calls :)

I also said sqrt( (Adx*Adx)+(Ady*Ady) )==1.0 The sqrt() is the important part for the length of 1.0. This is basic Pythagoras's theorem

...
Thanks for responding Mike, btw I didn't mean to "correct" you or anything I was trying to convey what I understood as I read it, and what I didn't.

Maybe the tilemap_get() docs(which i did read at the time) should mention tilemap_get_pixel() for sake of completion and to avoid confusion.

You mentioned pythagoras, and yes that's exactly what got me confused at the point you mentioned above, because the theorem is such that c²(hypotenuse) = a²+b².
However c isn't universally/always going to be =1, hence the standard pyt triangle of 5² = 4² + 3². So that would mean that sqrt of Adx²+Ady² inst necessarily going to equal 1, depends on the values of Adx/Ady. But in your code what happens is that Adx²+Ady² =len² and len is the "c"(hypotenuse) here. There after you do Adx = Adx/len and Ady = Ady/len, in that moment what you are doing is setting Adx/Ady to the sine and cosine values of the triangle, because the "a" div by the "c"(hypotenuse) is the sin or cos of the triangle. And then yes sin²+cos² =1, but not a²+b². That's what got me confused.

As for the other parts, I'll play with the code later on and then I'll try to explain what i didn't get. Thanks anyways for the instructions, it's clear that it works very well!:)
 

Mike

nobody important
GMC Elder
Your missing the point of the example. Once you MAKE a unit vector, C is always 1 (or as close as floating point allows). This is the point of making it a unit vector. This is why I say...

Adx and Ady now comprise a vector of a length of 1, that is sqrt( (Adx*Adx)+(Ady*Ady) ) == 1.0
 
A

amusudan

Guest
I'm working on a lighting engine too, with pointlights and support for 3/4 perspective walls, but that's still in the early stages.

Nice tutorial.
 

GMWolf

aka fel666
I only gave this a quick read, but it seemed to me like you would still cast shadows of tiles already in a shadow.

What would be the performance benefit of checking to see that a tile is not yet in shadow before casting one?
In my experience, throwing a ton of vertices at the graphics card is not a problem, so perhaps the extra overhead may not be worth it.
 
Last edited:

Mike

nobody important
GMC Elder
I only gave this a quick read, but it seemed to me like you would still cast shadows of tiles already in a shadow.
What would be the performance benefit of checking to see that a tile is not yet in shadow before casting one?
In my experience, throwing a ton of vertices at the graphics card is not a problem, so perhaps the extra overhead may not be worth it.
Yes you would, but it's much quicker to not bother doing that. Look at the video...16 moving lights at 800-900fps. Gfx card fill rate is much faster than the CPU processing to work out intersections and occlusion. This is also why I extend the shadows off the screen, you don't really have to as long as they're outside the light cone - it's just easier to do so, and when you get closer to the block you may end up with errors as the silhouette gets extreme.
 
K

Kios

Guest
Your missing the point of the example. Once you MAKE a unit vector, C is always 1 (or as close as floating point allows). This is the point of making it a unit vector. This is why I say...
Does gml know that 'mouse_x' is a position and not just a number? Thus 'Adx =_Ax-_Lx;' creates a vector and not simply subtracts a number?
I'm not sure where a vector was made.

Thanks again btw.
 
Last edited by a moderator:
C

Chungsie

Guest
a vector is a speed and direction. or magnitude and angle
 
K

Kios

Guest
a vector is a speed and direction. or magnitude and angle
Precisely, Vectors require at least a magnitude and a direction(or origin in that sense).
My assumption in my comment was that doing 'Adx =_Ax-_Lx' created a vector type that has the given magnitude and points from _Lx to _Ax.
So maybe mouse_x returns a type that has more info in it than just a number(the given x coordinate).
Otherwise I really don't know where a vector was 'made'. But that's why I asked in the first place, I'm still trying to understand how gml works.
 
C

Chungsie

Guest
I know in GMS, you have -1 and +1 as min and max for x and y on gamepad for instance. so x being the magnitude and -/+ being one side or the other in reference to a center. I don't see how it would make sense to have it forced with the mouse, as the mouse can start in any location IRL and be in the same location digitally.
 

Mike

nobody important
GMC Elder
Come on guys.... You have the code, read through it, step through it in the debugger even. There is a Adx and an Ady, both of them together form a vector. It's not complicated. GML doesn't do classes, so they're stored in separate variables.
 
A

almost

Guest
Hello , i love your engine, your tuto is very good but i can't follow all steps, i am french and sure its simple english but i am realy blocked, so i have start but the step of the end of part 1 , and part 2 is more dificult for me ( my english is not very good ) so i have take the code of that topic : https://www.reddit.com/r/gamemaker/comments/757u4y/cannot_figure_out_the_realtime_2d_lighting_in/
But sure it does'nt work ...
Plz it is possible to have all of the code ? and sure i will work a lot to know how your code work but now i cant :/ or a translate tuto in french :)
 
Top