2D game Terraria-like lighting

Started by
9 comments, last by StarMire 9 years, 5 months ago

Hello guys. This is my first post, so if I posted in the wrong section please let me know.

Ok, so I'm making a game similar to Terraria/Starbound (but much more modest, of course). I made an algorithm to calculate the lighting that, despite the lack of optimization right now, works pretty well (at least for my taste). But this algorithm is not important and it's not what I want to talk about here. How to render this is what I came here to discuss.

To render the tiles, I was coloring them accordingly to the "light map" and it worked "ok". This is how it looked like using this method:

Then I changed the way I was doing, so now I render the tiles "full bright" and then I render "black/colored" tiles in front of them, adjusting the alpha channel to "match" the light level (each corner using an average of the surrounding lighting). But the performance of this, of course, is not that great because I doubled the number of triangles being rendered and the "smoothing calculations". Here's how it looks like using this method:

Then I made a quick test and "rendered" (by hand?) every "tile in the light map" as a single pixel on a texture to represent the "light level", then render a single quad in front of the screen with the texture (with filters) stretched. The visual results are exactly the same as the previous method. But, to my surprise, the performance was even worse.

So I was thinking if there's a better way to do it using pixel shaders or other techiniques?

I'm using DirectX9 and C++.

Any hints will be appreciated. Thank you in advance. smile.png (sorry about my english, I can't even grammar)

Advertisement

Looking at the video this should not be slow. 1000 Tris was fast on my very first cheap 3d vidoe card. You have like 10000 that should be 60 fps on todays hardware. Apparently not fill-rate bound and no overdraw. Look for bugs! Compare with code by others (examples) and merge.

It's not slow on my computer. But when you zoom out and many more tiles are on screen, like (480x270 tiles (259200 triangles per frame)), plus the lighting calculations, plus the same amount of trigs for the shadow it drops to ~30 fps in my i3 with hd6850, which is pretty bad.

But today I revisited the method where I paint a texture with the lightmap and draw a single quad all over the tiles and it's now much faster. I was definitely doing something wrong. It's now getting ~52 fps, compared to ~30 that was before (480x270 tiles, zoom 0.4x). This is much better. The intented zoom for you to play is around 2x and it's now running at ~610 fps. It will do for now, but I want it to run as intended (ie 60 fps) on a weaker hardware.

I think the best thing to do now is to try to improve the lighting calculation. Here's how I do it:


//GenerateLighting
void BitWorld::GenerateLighting( int x1, int y1, int x2, int y2 )
{
    //validate the range of the input values
    if( y1<0 ) y1 = 0;
    if( y2>=world_height ) y2 = world_height-1;

    //propagate the light to the other blocks left-top to right-bottom
    for( int ey=y1; ey<=y2; ++ey )
        for( int ex=x1; ex<=x2; ++ex )
            CalcLighting( ex, ey );

    //propagate the light to the other blocks right-bottom to left-top
    for( int ey=y2; ey>=y1; --ey )
        for( int ex=x2; ex>=x1; --ex )
            CalcLighting( ex, ey );
}

inline void BitWorld::CalcLighting( int x, int y )
{
    SWorldBlock *block;
    SWorldLighting *lptr;
    int l1=0, l2=0, lt, absorb;
    SBlockInfo *fg, *bg;

    block = GetWorldBlockPtr( x, y );
    lptr = GetLightPtr( x, y );

    fg = BlockInfo->GetBlockInfo( block->foreground_id );
    bg = BlockInfo->GetBlockInfo( block->background_id );
    
    //calculate the max lighting from the neighbour blocks and light absorption
    lt = 0;

    int z[8];
    z[0] = (GetLightLevel( x-1, y-1 )*15)>>4;
    z[1] = GetLightLevel( x,   y-1 );
    z[2] = (GetLightLevel( x+1, y-1 )*15)>>4;
    z[3] = GetLightLevel( x-1, y   );
    z[4] = GetLightLevel( x+1, y   );
    z[5] = (GetLightLevel( x-1, y+1 )*15)>>4;
    z[6] = GetLightLevel( x,   y+1 );
    z[7] = (GetLightLevel( x+1, y+1 )*15)>>4;
    
    //find the maximum: slight faster than the "for" method, much faster than cascading "max" functions
    if( z[1]>z[0] ) z[0] = z[1];
    if( z[2]>z[0] ) z[0] = z[2];
    if( z[3]>z[0] ) z[0] = z[3];
    if( z[4]>z[0] ) z[0] = z[4];
    if( z[5]>z[0] ) z[0] = z[5];
    if( z[6]>z[0] ) z[0] = z[6];
    if( z[7]>z[0] ) z[0] = z[7];

    if( z[0]>lt ) lt = z[0];

    if( lt>0 )
    {
        if( block->foreground_id>0 || block->background_id>0 )
        {
            if( block->foreground_id==0 )
                l1 = 8; //16
            else {
                l1 = fg->light_absorption;
                //open doors block less light
                if( fg->type==BLOCK_TYPE_DOOR ) if( block->foreground_offset>fg->frame_tile_count ) l1 = 4;
            }

            //TODO: background blocks absorb a fixed amount of light? ie equal to all? make it a "constant"?
            if( block->background_id==0 ) l2 = 8/*16*/; else l2 = 24;  //bg->light_absorption/3;
            if( block->liquid_level>16 ) { l1+=12; l2+=12; }
            if( l1>l2 ) absorb = l1; else absorb = l2;
        } else {
            absorb = 16;
            if( block->liquid_level>16 ) { l1+=12; l2+=12; }
        }
        lt -= absorb;
    }

    //blocks that emit light itself
    if( fg->light_emit>lt ) lt = fg->light_emit+(int)(flicker_cos*(float)fg->light_flicker);
    if( bg->light_emit>lt ) lt = bg->light_emit+(int)(flicker_cos*(float)bg->light_flicker); //TODO: is this really the way to do it?
            
    //the block is empty? emit sunlight! xD
    if( ( block->foreground_id==0 || fg->is_translucent ) && ( block->background_id==0 || bg->is_translucent ) )
    {
        //int l = 300; //GetWorldLight( ey ); //TODO: precompute?
        if( lt<300 ) lt = 300;
        if( block->liquid_level>16 ) lt-=32;
    }

    if( lt<0 ) lt = 0;

    //set the current light level to the block
    lptr->light_level = lt;
}

Pretty bad, I know. Any suggestions?

Cache tiles in blocks of say 4x4 or 8x8 when on screen.

Henning

Cache tiles in blocks of say 4x4 or 8x8 when on screen.

Henning

What do you mean by that?

I was thinking along the lines of what you mentioned in your first post. If the needed Parameters could be passed into a pixel shader to render to a lightmap quad and stretched to fit on the screen after, I'd expect that to be the fastest. Erm. Actually you wouldn't need another target cuz it would be like a post-process effect.

I think I should have posted this to "general programming".

Any ideas on how to handle colored lights using a similar technique like the code I posted?

I think you'd do like with light intensity but the same technique applied to all 3 channels of the color: red_level, green_level, blue_level.. One other thing you can try to speed up the algorithm, is to not finish all the lighting calculations every frame. In other words, you do enough to keep up to a desired accuracy rate in each loop (keeping track of where you left off and perhaps even focusing first more on tiles where changes occurred) and then have the alg continue where you left off in the calculations in the next loop. I don't know if I'm right but I suspect this self-correcting style of lighting might not have any distracting artifacts. It's an idea anyway.

I think you'd do like with light intensity but the same technique applied to all 3 channels of the color: red_level, green_level, blue_level.. One other thing you can try to speed up the algorithm, is to not finish all the lighting calculations every frame. In other words, you do enough to keep up to a desired accuracy rate in each loop (keeping track of where you left off and perhaps even focusing first more on tiles where changes occurred) and then have the alg continue where you left off in the calculations in the next loop. I don't know if I'm right but I suspect this self-correcting style of lighting might not have any distracting artifacts. It's an idea anyway.

This. A long as you don't have dynamic objects running around needing to occlude lights, or carrying around lights, you can just bake your lights and only update it when somebody places/removes a light.

If you do have s few dynamic lights moving around, you can also just do a local calculation, out a few tiles, and add that into the baked lighting.

This is a rather good idea, renman29. I'm going to test this right now.

As for dynamic objects, there will be. And the light from the torches do a little flickering, so I'll have to update de lighting in a fixed timestep, I think. Right now I update it every frame.

This topic is closed to new replies.

Advertisement