Create Blackhole in Libgdx using GLSL Shaders and FBO

In this Tutorial, we will be creating a Blackhole using smooth step in GLSL to create a gravitational effect on light and thus it’s bending.
As the blackhole shader will be used using FBO it is necessary for the readers to know about FBO and how to use them in LibGDX.
With and Without Shader

 

Without and With Shader

Without and With Shader

If you are new to shader please go through these tutorials before proceeding here.

Here is the Vertex Shader, copy it in a text file and rename the file as “blackhole.vert” save it in assets/shader folder

attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;    //used by libgdx
varying vec4 v_color;     //used by pass color data to fragment shader
varying vec2 v_texCoord0;    //used by pass pixel coordinate data to fragment shader
void main() {
  gl_Position = u_projTrans * a_position;
  v_color = a_color;
  v_texCoord0 = a_texCoord0;
}

Here is the Fragment Shader, copy it in a text file and rename the file as “blackhole.frag” save it in assets/shader folder

#ifdef GL_ES
#define LOWP lowp
precision mediump float;
#else
#define LOWP
#endifvarying vec2 v_texCoord0;
varying vec4 v_color;uniform LOWP sampler2D u_texture;
uniform LOWP vec2 center;
uniform LOWP float time;
void main (void)
{
  float distance = distance(v_texCoord0,center);
  gl_FragColor = texture2D(u_texture, v_texCoord0 + smoothstep(time,0,distance));
}

And below is the implementation code using LibGDX, note the use of FBO as this shader is processed on the final output render which might hurt the performance but not that much as it is running at 60fps with ease on a quite cheap android device so no need to worry about fps in this case.

package main;
import main.util.Cam;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.FPSLogger;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
public class Test implements ApplicationListener {
  Cam camera;
  TextureRegion fboTexRegion;
  SpriteBatch batch;
  FrameBuffer fbo;
  Sprite testing;
  Texture nebula,star;
  ShaderProgram shaderprogram;
  @Override
  public void create () {
    Gdx.graphics.setVSync(false);
    camera=new Cam();fbo = new FrameBuffer(Pixmap.Format.RGB565,Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),false);
    fboTexRegion = new TextureRegion(fbo.getColorBufferTexture());
    fboTexRegion.flip(false, true);batch = new SpriteBatch();
    nebula = new Texture(Gdx.files.internal("images/neb3.png"));
    star = new Texture(Gdx.files.internal("images/starmed.png"));String FRAG = Gdx.files.internal("shaders/blackhole.frag").readString();
    String VERT = Gdx.files.internal("shaders/blackhole.vert").readString();

    shaderprogram = new ShaderProgram(VERT,FRAG);
    System.out.println(shaderprogram.getLog());

    batch.setProjectionMatrix(Cam.camera.combined);
    camera.update();
  }

  public void render () {

    Data.dt_touch.set(Gdx.input.getX(),Gdx.input.getY(),0);
    Cam.camera.unproject(Data.dt_touch);

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

    fbo.begin(); //start rendering to fbo

    Gdx.gl.glClearColor(0,0,0, 1); //clearing fbo
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    batch.setShader(null);
    batch.begin();
    batch.draw(nebula,0,0,512,512);
    batch.draw(star,0,0,512,512);
    batch.end();

    fbo.end();

    batch.setShader(shaderprogram);  //setting up blackhole shader as the default shader
    batch.begin();
    shaderprogram.setUniformf("time",.15f); // size of blackhole
    shaderprogram.setUniformf("center",.5f,.5f); // 0,0=lower left and 1,1 =upper righ corner
    batch.draw(fboTexRegion,0,0,Data.vwidth,Data.vheight);
    batch.end();

  }

  @Override
  public void resize(int width, int height) {
    camera.resize(width,height);
    batch.setProjectionMatrix(Cam.camera.combined);
  }

  @Override
  public void pause() {}

  @Override
  public void resume() {}

  @Override
  public void dispose () {}
}

In the above code in the create method we must initialize FBO and fboTexRegion.In the render function

fbo.begin();

was to initiate rendering to framebuffer which will be post-processed further, then

batch.setShader(null);

was to set default shader so everything post this will be rendered as is. After that 2 textures are drawn namely nebula and star, and then

fbo.end();

this stops rendering to the framebuffer. Now we set our blackhole shader as the current shader by using

batch.setShader(shaderprogram);

then we draw fboTexRegion which was holding our framebuffer and tada we are done with light bending super powerful blackhole. Remember the center color of blackhole will be your

Gdx.gl.glClearColor(0,0,0,1);

if it is set to green then your blackhole will be greenhole 😛
These shaders are based GLSL so you can use them in any game which is in OpenGL, also you can port them to HLSL and use in DirectX Games.

With a slight modification in code using input processor you can achieve what is shown in the video above.

Was this post useful? If yes then please share.



Comments (3)

  1. Interesting but there is missing some code?

    shader.log = ” No matching function for call to smoothstep(float, int, float)”

  2. Try using smoothstep(time,0.0,distance), if the error still persists try creating a new method as descibed in https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/smoothstep.xhtml

Leave a Comment

All fields marked with an asterisk (*) are required


shares