Tuesday 19 November 2013

LibGDX Lighting - Day / Night cycle

Recently researched the use of a Shader in libGDX to create a simple day/night cycle. Found some basic tutorials to create a fire at night affect including: Lightmap Shader This basically adds a tint to the background and overlays an image to create a spotlight.

Initiating vars etc:
Vector3 bright = new Vector3(6.3f, 6.3f, 6.7f);
// Load shaders from text files
vertexShader = Gdx.files.internal("data/shaders/vertexShader.glsl").readString();
defaultPixelShader = Gdx.files.internal("data/shaders/defaultPixelShader.glsl").readString();
finalPixelShader =  Gdx.files.internal("data/shaders/pixelShader.glsl").readString();

ShaderProgram.pedantic = false;
defaultShader = new ShaderProgram(Art.vertexShader, Art.defaultPixelShader);
finalShader = new ShaderProgram(Art.vertexShader, Art.finalPixelShader);currentShader = finalShader;
ambientColor = bright;
 
finalShader.begin();
finalShader.setUniformi("u_lightmap", 1);
finalShader.setUniformf("ambientColor", ambientColor.x, ambientColor.y, ambientColor.z, ambientIntensity);
finalShader.end();
// Image for spot light 
light = new Texture(Gdx.files.internal("data/shaders/light.png"));
fbo = new FrameBuffer(Format.RGBA8888, 1024, 788, false);

finalShader.begin();
finalShader.setUniformf("resolution", 1024, 788);
finalShader.end();

Render Loop
After setting up the variables and shader files in the render loop I adjusted the ambientColor over time to give affect of night day.

// Adjust ambientColor to give appearance of night/day  
finalShader.begin();
finalShader.setUniformi("u_lightmap", 1);
finalShader.setUniformf("ambientColor", ambientColor.x, ambientColor.y, ambientColor.z, ambientIntensity);
finalShader.end();

// I added logic to only show spotlight during night
if(show_light){
  fbo.begin();
  fbo.getColorBufferTexture().bind(1);
  light_batch.setShader(defaultShader);
  light_batch.begin();
  light.bind(0);
  light_batch.draw(light, hero.x-l_off+.4f,hero.y-l_off+.2f, light_size,light_size);
  light_batch.end();
  fbo.end();
}
       
// Draw your map here
light_batch.setShader(currentShader);
light_batch.begin();
MapGenerator.draw(gameController.camera, light_batch);
light_batch.end(); 

Night
 Evening

This solution limited me to using one spot light, also my knowledge of OpenGL Shader language is zero. At this point I went back to the LibGDX documentation and came across Box2dLights, a library that uses collision data from Box2D to create lights/shadows.

// BOX2DLIGHTS
private PointLight spriteLight;
private RayHandler rayHandler;

// Create a new Box2D World, this is required. 
World world = new World(new Vector2(), true); 
RayHandler.useDiffuseLight(true);
  
// Setup the new RayHandler, it will use the same camera as the main game
rayHandler = new RayHandler(world);
rayHandler.setCulling(true);
rayHandler.setCombinedMatrix(gameController.camera.combined);
rayHandler.setAmbientLight(1);
 
// Light to follow the hero
spriteLight = new PointLight(rayHandler, 50);
spriteLight.setPosition(hero.x,hero.y);
spriteLight.setDistance(5);
spriteLight.setColor(3, 12, 33, 0.3f);
// Keep the unique ID of the light  
hero_light = spriteLight.hashCode();

// this lights the map (Sun)  
spriteLight = new PointLight(rayHandler, 50);
spriteLight.setPosition(hero.x-20,hero.y-20);
spriteLight.setDistance(250);
spriteLight.setColor(3, 12, 33, 0.5f);
 
// Test shadow being cast by solid object 
PolygonShape tileShape = new PolygonShape();
tileShape.setAsBox(.5f, .5f);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = tileShape;
fixtureDef.filter.groupIndex = 0;
BodyDef tileBodyDef = new BodyDef();
float bodyX = hero.x-3;
float bodyY = hero.y-4f;
tileBodyDef.position.set(bodyX, bodyY); 
tileBodyDef.type = BodyType.StaticBody;
Body tileBody = world.createBody(tileBodyDef);
tileBody.createFixture(fixtureDef);

// RENDER LOOP CODE
rayHandler.setCombinedMatrix(gameController.camera.combined, gameController.camera.position.x, gameController.camera.position.y,gameController.camera.viewportWidth, gameController.camera.viewportHeight);
rayHandler.updateAndRender();

Here I set up some basic lights, a small one for the hero and a large light to act as the sun, I added one solid object to show a shadow being cast. Would need to add bodies for each object in the game and update there positions. Here the alpha is set to low on the sun light to give impression of night:

Zoomed out, earlier in the day:

Multiple Lights


Accessing and looping through all lights is simple, we stored the hashcode of the hero light so on each render loop update its position, else change the alpha value; increase/decrease this value to simulate night to day and vice versa.

for (Light light : rayHandler.lightList) {
 if (light.hashCode() == hero_light){
  light.setPosition(hero.x,hero.y); 
 }else{
  light.setColor(1, 1, 1, ambientIntensity);
 }
}

4 comments:

  1. where can I find the entire source code? Seems great.

    ReplyDelete
  2. I will set up a public repository for these tutorials. Sorry for slow response.

    ReplyDelete
    Replies
    1. Pls when you upload your code ?

      How you display UI GAME with Box2DLight ?

      With your demo code, my whole screen is Black except my light.

      Pls help me ^^

      Delete