GameMaker Deferred Normal Map Lighting w/ Spine

Note: This asset is no longer being supported or updated.
Description

This is a normal map lighting engine using deferred rendering, made and designed for GameMaker Studio 2.

Features:
  • Easy to drop into any project and implement lighting
  • Highly optimized
  • Supports sprites and tilemap layers
  • Supports Spine Animations with a tool to streamline the process! (download)
  • No set limit on number of lights (and performance doesn't take much of a hit for more)
  • Includes functionality for Specular Highlights and Ambient Occlusion
What does this do for you?

Normal map lighting makes your flat 2d world pop out and look 3d. Specular highlights give sprites shininess like metallic armor or glass. Ambient occlusion gives sprites realistic shadows that disappear in light.

How do you use it?

Manual covering all the functions in detail.
Code:
\\\create event lighting controller object
if(view_enabled){
    lighting_init(camera_get_view_width(view_camera[0]),camera_get_view_width(view_camera[0]),true,true,false,false);
} else {
    lighting_init(room_width,room_height,true,true,false,false);
}

\\\draw end event lighting controller object
lighting_draw_all();

\\\create event player
lighting_instance_create();
lighting_instance_normals(sprPlayerNormals);
lighting_instance_specular(sprPlayerSpecular); //optional
lighting_instance_ao(sprPlayerOcclusion); //optional

\\\draw event player
draw_self();
lighting_instance_add();

\\\create event light
lighting_light_create(true);
lighting_light_radius(64);

\\\draw event light
lighting_light_add();
It's as easy as that!

For spine support, I created a tool (download) to generate the required code and materials. Takes the spine atlas image, atlas file, and json file and exports the creation code for the object and a map. Spine lighting has never been easier to add!
Functions:
Code:
lighting_init(view_width,view_height,specular,ao)
lighting_draw_all()
lighting_set_ambience(color)

lighting_instance_create()
lighting_instance_add()
lighting_instance_normals(sprite/layerName)
lighting_instance_specular(sprite/layerName)
lighting_instance_ao(sprite/layer name)
lighting_instance_tilemap()
lighting_instance_spine()
lighting_instance_bonemap(sprite)

lighting_light_create(pointLight)
lighting_light_add()
lighting_light_color(color,alpha)
lighting_light_radius(radius)
lighting_light_direction(x,y,z)

lighting_spine_get_bone_states()
lighting_spine_draw(instance_id,function)
 
Last edited:
If you're asking if the asset itself includes the example objects then no. If you're asking if there is an example project source available with the asset then yes. It's just a download link to the project and spine files outside game maker so you can see everything. Although I can upload the example objects and room into the asset if that is more convenient.
 

Zerb Games

Member
Haha wow, I was actually looking for your old example to download it :). I recall you doing this before. Oof I really need to buy this, but idk if it's exactly what I want. I'll see! Looks great tho.

Do you think you could include an example file somewhere? I really want to test it before using it. For performance reasons. I need to support a few hundred lights of all different colors.
 
Working on shadows and depth. Right now, the lighting calculations treat the depth as 0. Here is a new demo of my progress. :) (LINK)

Controls:
  • Numbers 1-4 Enable/Disable options (Specular,AO,Depth,Shadows)
  • C toggles cel shading
  • F toggles full screen
  • Left mouse button adds a light at the mouse with the mouse light z value
  • Right mouse button adds a spine instance at the mouse
  • Mouse wheel moves the mouse light z value up or down
  • Arrow keys move the view
  • ESC ends the demo
This demo should give a good idea on performance with each of the different options.
 

Luke Peña

Member
It's possible to run this without the drop-shadows on the background, right? I know the answer is probably yes, but I just want to make sure.
 
A

Alsekond

Guest
Hi! Could you please add simple demo code to asset?
 
Last edited by a moderator:
It works well with the tile layers. Asset layer should work too (just treat it the same a tile layer). Asset layer might be harder to work with though. For tiles, you create a duplicate layer and swap the tileset for the one with normals. For sprites, that won't be as easy, but it's still possible.
 
F

FYVE

Guest
Was wondering if I could get some help importing this into my project? I'm getting errors following the instructions...
 
J

joakimFF

Guest
Does anyone know how to apply normal mapping to a tile layer as a whole, when I assign a tile layer the shader treats each tile separately creating a "bump seam" around every tile.
 
Does anyone know how to apply normal mapping to a tile layer as a whole, when I assign a tile layer the shader treats each tile separately creating a "bump seam" around every tile.
The shader treats the all the normal maps as one image, not per tile. All the normal maps are drawn to a surface. Then that normal map surface is used by the shader.

If you have a bump seam, you should check the normal maps to make sure they are tilable. A lot of programs that build normal maps for you are unable to deal with individual tiles because they use the image shape along with differences in luminosity. The shape part creates edges.

If they are tilable, check if you have edge color bleed turned on (especially if the view is scaled or your view is not exactly snapped to the pixels).
https://www.yoyogames.com/blog/3/seamless-tile-scaling-in-gamemaker
 
J

joakimFF

Guest
The shader treats the all the normal maps as one image, not per tile. All the normal maps are drawn to a surface. Then that normal map surface is used by the shader.

If you have a bump seam, you should check the normal maps to make sure they are tilable. A lot of programs that build normal maps for you are unable to deal with individual tiles because they use the image shape along with differences in luminosity. The shape part creates edges.

If they are tilable, check if you have edge color bleed turned oeverything n (especially if the view is scaled or your view is not exactly snapped to the pixels).
https://www.yoyogames.com/blog/3/seamless-tile-scaling-in-gamemaker

Thank you! it was indeed a scaling issue, everything works like a charm now. :)
 
J

joakimFF

Guest
Hi maybe someone here can spot were I am going wrong, I am trying to add and populate Diffuse / Normal and normal map control layer trough GML instead of using the IDE. But the lights just turns out as black splotches...
I have confirmed the layers are created / populated and at the right depth from each other.
It works fine if I manually add the tiles / layers in the IDE.

Code:
 lidd = layer_get_id(layerName);
        
                if lidd == -1 {
                   
                  lay_idd = layer_create(layerDepth,layerName);
                   map_idd = layer_tilemap_create(lay_idd,0,0,tDungeonTiles,32,32)
                
                   nrm_lay_id = layer_create(layerDepth+100,layerName+string("_NRM"));
                   nrm_map_idd = layer_tilemap_create(nrm_lay_id,0,0,tDungeonTilesNRM,32,32);
                  
                   nrm_lay_ctrl_id = layer_create(layerDepth+200,layerName+string("_NRM_CTRL"));
                  
                   tile_nrm_controler = instance_create_layer(0,0,nrm_lay_ctrl_id,obj_normalmap_tile_controller);
                     with tile_nrm_controler {
                        lighting_instance_create();
                        lighting_instance_tilemap(other.lay_idd);
                        lighting_instance_normals(layer_get_name(other.nrm_lay_id));
                        
                    }
                  
                 }
              
                   if layer_get_name(lay_idd) == layerName {
                      if layer_tilemap_exists(lay_idd,map_idd) {
                          t =  tilemap_set(map_idd, ind, mx, my);
                          n =  tilemap_set(nrm_map_idd, ind, mx, my);
                          
                       }
                   }
 

BigGrimm

Member
It's a very good asset.
I have two questions.

1. Can I use natural colors instead of black in the shadows?

2. Normal map works well for instance. Can I apply a normal map to draw_sprite?
 
Hi maybe someone here can spot were I am going wrong, I am trying to add and populate Diffuse / Normal and normal map control layer trough GML instead of using the IDE. But the lights just turns out as black splotches...
I have confirmed the layers are created / populated and at the right depth from each other.
It works fine if I manually add the tiles / layers in the IDE.
Code:
 lidd = layer_get_id(layerName);
      
                if lidd == -1 {
                 
                  lay_idd = layer_create(layerDepth,layerName);
                   map_idd = layer_tilemap_create(lay_idd,0,0,tDungeonTiles,32,32)
              
                   nrm_lay_id = layer_create(layerDepth+100,layerName+string("_NRM"));
                   nrm_map_idd = layer_tilemap_create(nrm_lay_id,0,0,tDungeonTilesNRM,32,32);
                
                   nrm_lay_ctrl_id = layer_create(layerDepth+200,layerName+string("_NRM_CTRL"));
                
                   tile_nrm_controler = instance_create_layer(0,0,nrm_lay_ctrl_id,obj_normalmap_tile_controller);
                     with tile_nrm_controler {
                        lighting_instance_create();
                        lighting_instance_tilemap(other.lay_idd);
                        lighting_instance_normals(layer_get_name(other.nrm_lay_id));
                      
                    }
                
                 }
            
                   if layer_get_name(lay_idd) == layerName {
                      if layer_tilemap_exists(lay_idd,map_idd) {
                          t =  tilemap_set(map_idd, ind, mx, my);
                          n =  tilemap_set(nrm_map_idd, ind, mx, my);
                        
                       }
                   }
Sorry for the delay. I didn't see this post for some reason. I'm not sure if you figured it out yet. I'm not too familiar with generating layers during runtime. I know the IDE flips the draw order on instances. I'll run some tests myself and see what I can find out.

It's a very good asset.
I have two questions.

1. Can I use natural colors instead of black in the shadows?

2. Normal map works well for instance. Can I apply a normal map to draw_sprite?
The shadows are naturally created in that they use depth to block light from pixels from each light source. If you have 1 light, the shadow color will end up being whatever color you set as the ambient lighting. In the video I show, I chose no ambient lighting so it was black. I've played around with a dark blue/purple as well but it can really be any color.

draw_sprite should work. You may need to add some code to the scripts but I believe there is a custom draw option you can use.
 
L

Luk

Guest
I have a question
What did you use for the shadows?
Primitives?
 
It's built into the lighting shader using ray tracing. Shadows are the result of the pixel being occluded by another pixel closer to the light source and therefore add no light at that point.
 
L

Luk

Guest
Thanks for answering.
And it works with curved surfaces through normal mapping?
I mean, are the shadows deformed?
Can they climb the walls?
 
Yeah but there is a lot of limitations since there is only the depth map to compare to. The shadows work much like the 2d shadow engines out there like this. But I use the depth map and the light source height to determine if the light is occluded. Also light doesn't pass behind an object even if the object isn't connected to the ground. The depth map only gives the height at a pixel so it treats all pixels as connecting to the ground.

Aside from the limitations though, the resulting effect looks good.
 
M

Molestium

Guest
Hello. I try your asset, this works well. I want divide by two shadows height for fake 3d effect. I tried to use Depth map, but it looks bad.

How I can do that?
Thanks.
 
Hello. I try your asset, this works well. I want divide by two shadows height for fake 3d effect. I tried to use Depth map, but it looks bad.

How I can do that?
Thanks.
To divide the height by 2, you can either change the angle of the light to more straight down or change the depth maps by making them 50% darker. There are 2 functions to deal with instances:
Code:
lighting_instance_depthScale(depthScale) //scales depth. for example 0.5 will make the depth divide by 2.
lighting_instance_depthVertex(z1,z2,z3,z4) //adds to depth z value per vertex in pixels. 255 is the max by default. this is useful for instances like the character in the picture to have more height at his head.
Until Yoyo adds color options to tiles, you will have to manually add depth tiles for the correct depths if you have multiple different depth options per tile. So to create the effect, in an image editor, you would first lock the alpha channel of the depth map, then add a layer with transparency, make it black and set the alpha to the scale you want (0.5 for half height), merge the layers, add a layer, using colors between black and white with gradients or not, merge this layer with additive blending to add to the z value.
 
R

RagTagRadical

Guest
Hey Strawbry_jam, i love the effect of your lighting system, and I'm hoping to use it in my next project, but I'm running into an issue where the Depth map and its shadow does not seem to be drawing from the origin of the sprite, but always from the top left.

I've just been doing small tests with it to learn the system and see if it will work for how I intend.
I changed the origin of all of the sprPlayer sprites to the center, and the shadows still seem to be cast as though it's from the top left.
You can also see that with the paw print test image I was playing with.

The other maps seem to be drawing from their origin, even when rotating the sprite.
Screenshot 2018-11-21 20.28.35.jpg
 
Hey Strawbry_jam, i love the effect of your lighting system, and I'm hoping to use it in my next project, but I'm running into an issue where the Depth map and its shadow does not seem to be drawing from the origin of the sprite, but always from the top left.

I've just been doing small tests with it to learn the system and see if it will work for how I intend.
I changed the origin of all of the sprPlayer sprites to the center, and the shadows still seem to be cast as though it's from the top left.
You can also see that with the paw print test image I was playing with.

The other maps seem to be drawing from their origin, even when rotating the sprite.
Sorry for the late reply. I don't get any sleep from work during this time of the year.

:) Good catch. I didn't notice since all the sprites had origins at 0,0 for all of my tests. I'll upload a fix. If you want to patch your version now though, you just need to change 1 line of code.

In lighting_draw_all() change the following line:
Code:
draw_sprite_general(depthSprite,image_index,0,0,sprite_width/image_xscale,sprite_height/image_yscale,x-cam_x,y-cam_y,image_xscale,image_yscale,image_angle,z1col,z2col,z3col,z4col,1);
to
Code:
var off_len = point_distance(sprite_xoffset,sprite_yoffset,0,0);
var off_ang = point_direction(sprite_xoffset,sprite_yoffset,0,0);
var x_off = lengthdir_x(off_len,image_angle+off_ang);
var y_off = lengthdir_y(off_len,image_angle+off_ang);
draw_sprite_general(depthSprite,image_index,0,0,sprite_width/image_xscale,sprite_height/image_yscale,x-cam_x+x_off,y-cam_y+y_off,image_xscale,image_yscale,image_angle,z1col,z2col,z3col,z4col,1);
It's in the section
//////setup depth map
 
R

RagTagRadical

Guest
Sorry for the late reply. I don't get any sleep from work during this time of the year.
That did it! Thanks so much. Get some sleep! I'll post my results here when I get a bit further along on the project visuals
 
Top