import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.InputAdapter; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.badlogic.gdx.graphics.Texture.TextureWrap; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; /** * Per-pixel shadows on GPU: https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows * @author mattdesl */ public class GpuShadows implements ApplicationListener { public static void main(String[] args) { new LwjglApplication(new GpuShadows(), "Test", 800, 600, true); } /** * Compiles a new instance of the default shader for this batch and returns it. If compilation * was unsuccessful, GdxRuntimeException will be thrown. * @return the default shader */ 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; } private int lightSize = 256; private float upScale = 1f; //for example; try lightSize=128, upScale=1.5f SpriteBatch batch; OrthographicCamera cam; BitmapFont font; TextureRegion shadowMap1D; //1 dimensional shadow map TextureRegion occluders; //occluder map FrameBuffer shadowMapFBO; FrameBuffer occludersFBO; Texture casterSprites; Texture light; ShaderProgram shadowMapShader, shadowRenderShader; Array lights = new Array(); boolean additive = true; boolean softShadows = true; class Light { float x, y; Color color; public Light(float x, float y, Color color) { this.x = x; this.y = y; this.color = color; } } @Override public void create() { 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"); //the light sprite light = new Texture("data/light.png"); //build frame buffers 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); font = new BitmapFont(); cam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); cam.setToOrtho(false); 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(); } @Override public void resize(int width, int height) { cam.setToOrtho(false, width, height); batch.setProjectionMatrix(cam.combined); } @Override public void render() { //clear frame Gdx.gl.glClearColor(0.25f,0.25f,0.25f,1f); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); float mx = Gdx.input.getX(); float my = Gdx.graphics.getHeight() - Gdx.input.getY(); if (additive) batch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE); for (int i=0; i