Jump to content


Photo
- - - - -

2D directional/radial light and shadows engine


  • Please log in to reply
5 replies to this topic

#1 silverlq

silverlq

    GMC Member

  • GMC Member
  • 117 posts

Posted 26 June 2011 - 10:03 PM

  • Title: 2D directional/radial light and shadows engine (Now with 2 possible light drawing methods)
  • Description: An example of how surfaces can be used to obtain good looking directional or radial light plus realistic directional shadows.
  • GM Version: GM8
  • Registered: Yes
  • File Type: .gmk
  • File Size: 1MB - 3.3MB
  • File Links:
  • .GMK FILE (1 MB)
  • EXECUTABLE (3.3 MB)
Additional Info:

Screenshots:

Posted Image Posted Image Posted Image
Posted Image

This is how it works:
METHODS:
There are 2 methods when drawing the light:
  • Shapes Method: Draws many superimposed shapes to form a desired source of light. This can be very slow.
  • Sprites Method: Draws a sprite to form a desired source of light. This is much faster and can support many different light shapes, but requires sprites.

SURFACES:
  • There are 3 surfaces that are very crucial and are called "darkness_surface", "flashlight_surface" and "shadow_surface" (flashlight surface is also used to draw the lamp).
  • The darkness_surface is the "final" surface that is actually drawn on the screen.
  • The object obj_engine refreshes the darkness_surface every step (in the begin step event) by drawing a rectangle that covers the screen. Otherwise, the light that is being drawn would sum up every step and give a weird unwanted effect.
  • The light is drawn on the flashlight_surface after the darkness_surface is refreshed.
  • The shadow is drawn on the shadow_surface.
  • The shadow_surface is subtracted [code: "draw_set_blend_mode(bm_subtract)"] from the flashlight_surface, so that the light does not reach the other side of the blocks.
  • Finally the flashlight_surface is subtracted from the darkness_surface, so that in the darkness alpha value is lower where the light is.
  • If the engine is using shapes method, the flashlight_surface is refreshed and this process (drawing light) is repeated a number of times equal to the number of "levels of light"(Depends on light graphical quality).

DRAWING IN SHAPES METHOD:
  • In the Directional light the levels are triangles and in the radial light they are circles.
  • One corner of the triangle is the origin of the light and the other two are projetions of it towards the beam angle.
  • The levels of light exist so that the center of the beam/sphere is more lightened than its borders.
  • My scripts draw the inner levels first and the outer later.
  • The alpha value is equal in all levels and it depends on the number of levels. (value=1/numberoflevels)

DRAWING IN SPRITES METHOD:
  • A sprite with a certain shape is drawn where the light is desired.
  • In this sprite the alpha value must be lower where less light is desired and higher where more light is desired.

SHADOWS:
  • Shadows in this example only works for rectangles.
  • Before the light is subtracted from the darkness, the shadows are subtracted from the light.
  • The shadows are drawn as 2 primitives with 4 vertexes each, 2 are opposite corners of the rectangle and 2 are its projections on the opposite direction of the lightsource
  • Also, after the primitives the body of the rectangle is also drawn.

HOW TO PROJECT SOMETHING:
To project a point in space I use trigonometric functions (a.k.a. sin and cos). This is used both in shapes method and when drawing directional shadows. This is how it works:
Posted Image
This triangle is the solution to 99% of your projection problems. Let's say the point (x,y) in question is point A in the drawing. And you want to project it to B. You know the distance you want to project it, it's the hypotenuse (h), and you know the angle 'A', you just want to know how much is the distance 'b' and 'a' so that you can simply sum them to the 'xy' values.

Sine equals 'opposite side / hypotenuse', and Cosine equals 'adjacent / hypotenuse'

That is great because you know two of three things in each equation:

sin(angle_you_want_to_project_in_RADIANS)= -a/projection_lenght
(The negative sign exists because in gamemaker "y" values grow downwards while in trigonometry it grows upward)

cos(angle_you_want_to_project_in_RADIANS)= b/projection_lenght
(Gamemaker uses angles in radians iside 'sin' and 'cos' functions, I suggest converting your degree angles using 'degtorad' function)

Now, if you multiply these 'sin' and 'cos' values by the projection_lenght (hypotenuse) you will obtain the value of x and -y. Final code is as follows
projected_x=x+projection_lenght*cos(degtorad(angle))

projected_y=y+projection_lenght*-sin(degtorad(angle))
I really hope that helps.

Code Comments: There are several code comments on my scripts, they have all been translated to english :).

Graphic Quality: I was very satisfied with the final looks of the light and shadows in both methods, but in shapes method the engine is borderline unplayable in most "casual PC's". In my good computer every room runs at 30 fps in "high" quality. In my bad laptop every room besides the first 2 are unplayable even in "very poor". Therefore, the sprites method is much more viable.

Obs.: The existance of a shadows_surface is not necessary because the shadows can be drawn in subtract mode directly in the flashlight_surface, but it makes things a little bit less organised.
Obs.: This works with multiple lightsources at once, I totally forgot to include that in the file. Well, nevermind, you will have to trust me, or to go test it yourself. Hah.
Thanks for reading. Rock on.

Updates:
  • Now with 2 different methods!
  • Entire codes translated to english!

If you liked this you can thumbs up this post to help support the topic. Down here ▼


Edited by silverlq, 06 July 2011 - 12:14 AM.

  • 6

#2 icuurd12b42

icuurd12b42

    Self Formed Sentient

  • Global Moderators
  • 14392 posts
  • Version:GM:Studio

Posted 05 July 2011 - 01:11 AM

Bravo
  • 0

#3 kroart

kroart

    GMC Member

  • GMC Member
  • 56 posts

Posted 05 July 2011 - 05:29 AM

very nice:) but what about non square blocks?

Edited by kroart, 05 July 2011 - 05:30 AM.

  • 0

#4 silverlq

silverlq

    GMC Member

  • GMC Member
  • 117 posts

Posted 06 July 2011 - 12:05 AM

Bravo

Thank you :D

very nice:) but what about non square blocks?

Thanks. Well, my engine only supports rectangles (not only squares), but it is possible to modify it so that it supports other shapes. Here are some thoughts:
  • For polygons with a number of sides different from 4: You could draw a shadow primitive for each side (yes that can get slow if you want to draw many of these), this is useful for triangles, pentagons, hexagons, etc.
  • For circles: You can find the two extremes of the circle relative to the light source, this is what I mean:
    Posted Image
    You want to draw the shadow primitive with the 2 red dots as vertexes. You can find their xy values by projecting the center of the circle in the direction of the light source +90° and -90° in a distance equal to the circle radius. Note: This is not perfect (especially when the light source is very close to the circle) but most of the times it should work as intended.
  • For lines: Just use the two extremes of the line as vertexes of the shadow primitive. This can be useful for sprites.

I believe that most 2D games that want to deal with light and shadows should be satisfied with rectangles as shadow sources since they are the primordial movement constrictors for many perspectives (Platformers, Topdowns, even RPG's). But if you have some kind of isometric or fake 3D perspective in mind, things can get a little bit rough.
You could project the sprite of an object deformed as shadow, so that it fits a more isometric perspective (As if projecting the shadow of a tree). If I have the time, I'll try to make a script on this.
Best regards,
Gust.
  • 0

#5 Nocturne

Nocturne

    Nocturne Games

  • Administrators
  • 16800 posts
  • Version:GM:Studio

Posted 08 July 2011 - 09:35 PM

Lovely tutorial, well written and explained! BUT (there is always one) I donīt like how you have "magic" numbers in the shadow script. I mean this line here...

with(obj_block) scr_draw_shadow_block(x - 66, y - 66, x + 65, y + 65, xx, yy, 1); //Subtracts the shadow from the light surface

It would be FAR better to use sprite_width/height and x/yoffset like this...

with(obj_block) scr_draw_shadow_block(x - sprite_xoffset, y - sprite_yoffset, x - sprite_xoffset + sprite_width, y - sprite_yoffset + sprite_height, xx, yy, 1);

You also do this in the draw shadow script with the value 764... I think it would be easier for less experienced users to adapt and use your scripts if these values were not hard-coded.

Other than that niggle, this is a fantastic tutorial!
  • 2

#6 Conk

Conk

    GMC Member

  • New Member
  • 142 posts

Posted 19 October 2011 - 11:53 PM

Not sure if you still watch this topic, but I tried to implement your lighting engine, as it
seems great, but after moving the stuff I wanted, all that happens is this:
Posted Image

[EDIT] never mind, i had depth and sprite origin in wrong places :L

Edited by Conk, 27 October 2011 - 11:02 AM.

  • 0




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users