Search code examples
javaopengllibgdxshadowframebuffer

Libgdx/Java ShadowMap and Framebuffer update using Cameras


im working on a little rpg game and today i tried to add shadows. I saw this tutorial here which is good :)

Pixel Perfect Shader

So following problem :

Test example from the tutorial site, works fine. Theres one cam and the shadows are on the right place.

As soon i use Orthographic cameras from my player sprite, the shadow isnt there where it should be and when im moving the shadow moves with me, he is on the same spot on the display screen but in the world the shadow is moving _/ that looks so :

The SpriteSheet should cast shadows ... but they are on the wrong place :/

Here i didnt moved :

It is very dark, because of the day/night cycle

It is very dark, because of the day/night cycle ...

And here i moved a little bit to left :

Moved to left

I dont know why it wont work ... im giving the Orthographic camera out of my main class to the shadow class. Or did i miss something ? :/

My code should be fine, but if anyone wanna see it here is some code :

public class LightSystemShader {


 private int lightSize = 500;

private float upScale = 1f; //for example; try lightSize=128, upScale=1.5f

SpriteBatch batch;
OrthographicCamera cam; 

BitmapFont font;
FPSLogger fps;

TextureRegion shadowMap1D; //1 dimensional shadow map
TextureRegion occluders;   //occluder map

FrameBuffer shadowMapFBO;
FrameBuffer occludersFBO;

Texture casterSprites;
Texture enmyAnimation;
Texture light;

ShaderProgram shadowMapShader, shadowRenderShader;

Array<Light> lights = new Array<Light>();

boolean additive = true;
boolean softShadows = true;

float xKoord = 600;

public static ShaderProgram createShader(String vert, String frag) {
    ShaderProgram prog = new ShaderProgram(vert, frag);
    if (!prog.isCompiled())
        throw new GdxRuntimeException("could not compile shader: " + prog.getLog());
    if (prog.getLog().length() != 0)
        Gdx.app.log("GpuShadows", prog.getLog());
    return prog;
}

public LightSystemShader() {

    batch = new SpriteBatch();
    ShaderProgram.pedantic = false;

    //read vertex pass-through shader
    final String VERT_SRC = Gdx.files.internal("data/pass.vert").readString();

    // renders occluders to 1D shadow map
    shadowMapShader = createShader(VERT_SRC, Gdx.files.internal("data/shadowMap.frag").readString());
    // samples 1D shadow map to create the blurred soft shadow
    shadowRenderShader = createShader(VERT_SRC, Gdx.files.internal("data/shadowRender.frag").readString());

    //the occluders
    casterSprites = new Texture("data/cat4.png");
    //
    enmyAnimation = new Texture("EnemyAnimations/BugIdleStand.png");
    //the light sprite
    light = new Texture("data/light.png");

    cam = new OrthographicCamera(0,0);
    cam.setToOrtho(false);

    updateMaps();

    font = new BitmapFont();




    Gdx.input.setInputProcessor(new InputAdapter() {

        public boolean touchDown(int x, int y, int pointer, int button) {
            float mx = x;
            float my = Gdx.graphics.getHeight() - y;
            lights.add(new Light(mx, my, randomColor()));
            return true;
        }

        public boolean keyDown(int key) {
            if (key==Keys.SPACE){
                clearLights();
                return true;
            } else if (key==Keys.A){
                additive = !additive;
                return true;
            } else if (key==Keys.S){ 
                softShadows = !softShadows;
                return true;
            }
            return false;
        }
    });

    clearLights();

}






public void renderLightSystemShader(OrthographicCamera screenCam){

    screenCam = new OrthographicCamera(0,0);
    screenCam.setToOrtho(false);

    Gdx.gl.glClearColor(0,0,0,0);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    float mx = Gdx.input.getX();
    float my = Gdx.graphics.getHeight() - Gdx.input.getY();

    if (additive)
        batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);

    for (int i=0; i<lights.size; i++) {
        Light o = lights.get(i);
        if (i==lights.size-1) {
            o.x = mx;
            o.y = my;
        }   
        renderLight(o, screenCam);
    }

    if (additive)
        batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

    //STEP 4. render sprites in full colour     
    batch.setProjectionMatrix(screenCam.combined);
    batch.begin();
    batch.setShader(null); //default shader


    drawShaderBatch(batch);


    //DEBUG RENDERING -- show occluder map and 1D shadow map
    /*batch.setColor(Color.BLACK);
    batch.draw(occluders, Gdx.graphics.getWidth()-lightSize, 0);
    batch.setColor(Color.WHITE);
    batch.draw(shadowMap1D, Gdx.graphics.getWidth()-lightSize, lightSize+5);

    //DEBUG RENDERING -- show light 
    batch.draw(light, mx-light.getWidth()/2f, my-light.getHeight()/2f); //mouse
    batch.draw(light, Gdx.graphics.getWidth()-lightSize/2f-light.getWidth()/2f, lightSize/2f-light.getHeight()/2f);
    */

    //draw FPS

    batch.end();

    System.out.println(Gdx.graphics.getFramesPerSecond());


}


    // draw Lights
void renderLight(Light o, OrthographicCamera screenCam) {
        float mx = o.x;
        float my = o.y;


        screenCam = new OrthographicCamera(0,0);
        screenCam.setToOrtho(false);

        //STEP 1. render light region to occluder FBO

        //bind the occluder FBO
        occludersFBO.begin();

        //clear the FBO
        Gdx.gl.glClearColor(0f,0f,0f,0f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        //set the orthographic camera to the size of our FBO
        screenCam.setToOrtho(false, occludersFBO.getWidth(), occludersFBO.getHeight());

        //translate camera so that light is in the center 
        screenCam.translate(mx - lightSize/2f, my - lightSize/2f);

        //update camera matrices
        screenCam.update();

        //set up our batch for the occluder pass
        batch.setProjectionMatrix(screenCam.combined);
        batch.setShader(null); //use default shader
        batch.begin();
        // ... draw any sprites that will cast shadows here ... //
        batch.draw(casterSprites, 0, 0);
        batch.draw(enmyAnimation,xKoord,600,300,100);

        //end the batch before unbinding the FBO
        batch.end();

        //unbind the FBO
        occludersFBO.end();

        //STEP 2. build a 1D shadow map from occlude FBO

        //bind shadow map
        shadowMapFBO.begin();

        //clear it
        Gdx.gl.glClearColor(0f,0f,0f,0f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        //set our shadow map shader
        batch.setShader(shadowMapShader);
        batch.begin();
        shadowMapShader.setUniformf("resolution", lightSize, lightSize);
        shadowMapShader.setUniformf("upScale", upScale);

        //reset our projection matrix to the FBO size
        screenCam.setToOrtho(false, shadowMapFBO.getWidth(), shadowMapFBO.getHeight());
        batch.setProjectionMatrix(screenCam.combined);

        //draw the occluders texture to our 1D shadow map FBO
        batch.draw(occluders.getTexture(), 0, 0, lightSize, shadowMapFBO.getHeight());

        //flush batch
        batch.end();

        //unbind shadow map FBO
        shadowMapFBO.end();

        //STEP 3. render the blurred shadows

        //reset projection matrix to screen
        screenCam.setToOrtho(false);
        batch.setProjectionMatrix(screenCam.combined);

        //set the shader which actually draws the light/shadow 
        batch.setShader(shadowRenderShader);
        batch.begin();

        shadowRenderShader.setUniformf("resolution", lightSize, lightSize);
        shadowRenderShader.setUniformf("softShadows", softShadows ? 1f : 0f);
        //set color to light
        batch.setColor(o.color);

        float finalSize = lightSize * upScale;

        //draw centered on light position
        batch.draw(shadowMap1D.getTexture(), mx-finalSize/2f, my-finalSize/2f, finalSize, finalSize);

        //flush the batch before swapping shaders
        batch.end();

        //reset color
        batch.setColor(Color.WHITE);

        //xKoord+=1;

    }


void clearLights() {
    lights.clear();
    lights.add(new Light(Gdx.input.getX(), Gdx.graphics.getHeight()-Gdx.input.getY(), Color.WHITE));
}

static Color randomColor() {
    float intensity = (float)Math.random() * 0.5f + 0.5f;
    return new Color((float)Math.random(), (float)Math.random(), (float)Math.random(), intensity);
}

void drawShaderBatch(SpriteBatch batch){

    batch.draw(casterSprites, 0, 0);

    batch.draw(enmyAnimation,600,600,300,100);

}

void updateMaps(){


    occludersFBO = new FrameBuffer(Format.RGBA8888, lightSize, lightSize, false);

    occluders = new TextureRegion(occludersFBO.getColorBufferTexture());
    occluders.flip(false, true);

    //our 1D shadow map, lightSize x 1 pixels, no depth
    shadowMapFBO = new FrameBuffer(Format.RGBA8888, lightSize, 1, false);
    Texture shadowMapTex = shadowMapFBO.getColorBufferTexture();

    //use linear filtering and repeat wrap mode when sampling
    shadowMapTex.setFilter(TextureFilter.Linear, TextureFilter.Linear);
    shadowMapTex.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);

    //for debugging only; in order to render the 1D shadow map FBO to screen
    shadowMap1D = new TextureRegion(shadowMapTex);
    shadowMap1D.flip(false, true);


}



public void doDispose(){


    batch.dispose();
    font.dispose();
    shadowMapFBO.dispose();
    shadowMapShader.dispose();
    shadowRenderShader.dispose();
    casterSprites.dispose();
    enmyAnimation.dispose();
    light.dispose();
    occludersFBO.dispose();


}


}

Solution

  • Found it out myself ^^ i forgot to add this to void renderLights :

    cam.position.set(positionX,positionY, 0);