Search code examples
androidopengl-eslibgdx2d-games

explosion shockwave effect on entire screen in libgdx with opengl shaders and framebuffers trouble with coordinates


I am trying to implement a shockwave effect for explosions in my 2d game these are steps I am trying 1) draw all stuff to a frame buffer (texture regions and meshes) 2) get texture from frame buffer and flip vertical 3) draw that resulting texture to screen with the shaders from this http://empire-defense.crystalin.fr/blog/2d_shock_wave_texture_with_shader

this is my render method

    resultBuf.begin();
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    Gdx.gl20.glViewport(0, 0, resultBuf.getWidth(), resultBuf.getHeight());
    Gdx.gl20.glEnable(GL20.GL_TEXTURE_2D);

    batch.begin();
    renderbackground();
    batch.setProjectionMatrix(camera.combined);



    if(imageObjDraw){
        for(Integer type:imageObjects.keySet()){
            if(type!=ImageObject.mountains&&type!=ImageObject.steelbridge1&&type!=ImageObject.steelbridge2&&type!=ImageObject.steelbridge3)for(ImageObject o:imageObjects.get(type))if(o.isInSquare(camRec))o.draw(batch);
        }
    }
    batch.end();

    if (meshDraw) {
        //DRAWING MESHES
        shaderPassThrough.begin();
        Gdx.graphics.getGL20().glEnable(GL20.GL_BLEND);
        shaderPassThrough.setUniformMatrix("u_projTrans", camera.combined);
        shaderPassThrough.setUniformi("u_texture", 0);
        for(Integer key:meshes.keySet()){
            if(key!=MeshInfo.bridge){
                LinkedList<myMesh> mlist=meshes.get(key);
                Texture tex=meshTextures.get(key);
                if(tex!=null)tex.bind(0);
                for(myMesh m:mlist){
                    m.render();
                }
            }
        }
        shaderPassThrough.end();
    }

    batch.begin();
    if(drawObjEnable){
        drawObjects(batch);
        vehicle.drawState0(batch);
        drawParticles(batch);
    }

    if(particleDraw)PushTheLimits.animMan.render(batch);
    if(bulletEnable)BulletManager.render(batch);

    if(imageObjDraw){
        for(Integer type:imageObjects.keySet()){
            if(type==ImageObject.steelbridge1||type==ImageObject.steelbridge2||type==ImageObject.steelbridge3)for(ImageObject o:imageObjects.get(type))if(o.isInSquare(camRec))o.draw(batch);
        }
    }

    batch.end();

    if (meshDraw) {
        //DRAWING MESHES
        shaderPassThrough.begin();
        Gdx.graphics.getGL20().glEnable(GL20.GL_BLEND);
        shaderPassThrough.setUniformMatrix("u_projTrans", camera.combined);
        shaderPassThrough.setUniformi("u_texture", 0);
        for(Integer key:meshes.keySet()){
            if(key==MeshInfo.bridge){
                LinkedList<myMesh> mlist=meshes.get(key);
                Texture tex=meshTextures.get(key);
                if(tex!=null)tex.bind(0);
                for(myMesh m:mlist){
                    m.render();
                }
            }
        }
        shaderPassThrough.end();
    }

    resultBuf.end();

    batch.begin();
    batch.setShader(shaderWave);
    shaderWave.begin();
    shaderWave.setUniformMatrix("u_projTrans", camera.combined);
    shaderWave.setUniformi("sceneTex", 0);
    shaderWave.setUniformf("center", new Vector2((explCoorX-camera.position.x+camera.effectivewidth/2f)/camera.zoom/screenW,(explCoorY-camera.position.y+camera.effectiveheight/2f)/camera.zoom/screenH));
    shaderWave.setUniformf("time", waveElaps);
    shaderWave.setUniformf("shockParams", new Vector3(10.0f, 0.8f, 0.1f));
    resultTexReg.setRegion(resultBuf.getColorBufferTexture());
    resultTexReg.flip(false, true);
    resultTexReg.getTexture().bind();
    batch.draw(resultTexReg, camera.position.x-camera.effectivewidth/2f,camera.position.y-camera.effectiveheight/2f,camera.effectivewidth,camera.effectiveheight);
    batch.end();
    batch.setShader(null);

here the shaders fragment shader

 #ifdef GL_ES
precision highp float;
#endif
uniform sampler2D sceneTex; // 0
uniform vec2 center; // Mouse position
uniform float time; // effect elapsed time
uniform vec3 shockParams; // 10.0, 0.8, 0.1
varying vec2 v_texCoords;
varying vec2 proj;
void main() 
{ 
  float distance = distance(v_texCoords, center);
  if ( (distance <= (time + shockParams.z)) && (distance >= (time - shockParams.z)) ) {
    float diff = (distance - time); 
    float powDiff = 1.0 - pow(abs(diff*shockParams.x), shockParams.y); 
    float diffTime = diff  * powDiff; 
    vec2 diffUV = normalize(v_texCoords-center); 
    v_texCoords = v_texCoords + (diffUV * diffTime);
  }
  gl_FragColor = texture2D(sceneTex, v_texCoords);
}

vertex shader

    attribute vec4 a_position;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
varying vec2 v_texCoords;
varying vec2 proj;
void main() {     
 //proj=u_projTrans.xy;
 v_texCoords = a_texCoord0;
 gl_Position =  u_projTrans * a_position; 
} 

as to define problem the shockwave effect always applied regard to screen coordinates I mean that center variable I pass to fragment shader -that is 0,0- must be origin of my box2d world coordinates but effect always applied to left down corner of the screen even if I move the camera to end of the map effect is at left down corner of the screen but I expect it to be at very left down out of the screen at world's origin which is 0,0 I suspect the problem is related with I am drawing the resulting texture at camera's position and also I am using camera for projection matrix in shader but I don't have well understanding of this shader topic and graphics coordinates actually I studied it well enough this seems correct to me but I cant see the bug and need help at this point thanks in advance please comment if I didnt tell the problem clear enough I will edit and add required info even perhaps u may want to see some screen shots?


Solution

  • The uniform called 'center' in your fragment shader is not specified in world coordinates. It's specified in the texture coordinates when you blit your render-to-texture to screen.

    So regardless of where your world's camera is, the coordinates 0,0 are always the bottom left of the screen, and 1,1 is the top right.

    In order to pass an appropriate value for the vec2 'center', you must transform the coordinates of the explosion (or whatever causes the shockwave) from your world-space coordinate system into texture space.

    One approach for doing so would be to multiply the world coordinates by your camera WVP matrix (camera.combined). If it's a perspective camera, you might need to divide the xy of the result by the w. This should give you clip-space coordinates which are from -1,-1 to 1, 1. You then need to remap these to texture coordinates by adding 1 and halving. You could do this in the vertex shader and pass the result in through a varying, or you could do it on the CPU and pass the result through as a uniform.