public
Last active

  • Download Gist
GpuShadows.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
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<Light> lights = new Array<Light>();
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<lights.size; i++) {
Light o = lights.get(i);
if (i==lights.size-1) {
o.x = mx;
o.y = my;
}
renderLight(o);
}
if (additive)
batch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
//STEP 4. render sprites in full colour
batch.begin();
batch.setShader(null); //default shader
batch.draw(casterSprites, 0, 0);
//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
font.drawMultiLine(batch, "FPS: "+Gdx.graphics.getFramesPerSecond()
+"\n\nLights: "+lights.size
+"\nSPACE to clear lights"
+"\nA to toggle additive blending"
+"\nS to toggle soft shadows", 10, Gdx.graphics.getHeight()-10);
batch.end();
}
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 renderLight(Light o) {
float mx = o.x;
float my = o.y;
//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(GL10.GL_COLOR_BUFFER_BIT);
//set the orthographic camera to the size of our FBO
cam.setToOrtho(false, occludersFBO.getWidth(), occludersFBO.getHeight());
//translate camera so that light is in the center
cam.translate(mx - lightSize/2f, my - lightSize/2f);
//update camera matrices
cam.update();
//set up our batch for the occluder pass
batch.setProjectionMatrix(cam.combined);
batch.setShader(null); //use default shader
batch.begin();
// ... draw any sprites that will cast shadows here ... //
batch.draw(casterSprites, 0, 0);
//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(GL10.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
cam.setToOrtho(false, shadowMapFBO.getWidth(), shadowMapFBO.getHeight());
batch.setProjectionMatrix(cam.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
cam.setToOrtho(false);
batch.setProjectionMatrix(cam.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);
}
 
@Override
public void pause() {
}
 
@Override
public void resume() {
// TODO Auto-generated method stub
}
 
@Override
public void dispose() {
// TODO Auto-generated method stub
}
 
}
pass.vert
GLSL
1 2 3 4 5 6 7 8 9 10 11 12 13
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
 
varying vec2 vTexCoord0;
varying vec4 vColor;
 
void main() {
vColor = a_color;
vTexCoord0 = a_texCoord0;
gl_Position = u_projTrans * a_position;
}
shadowMap.frag
GLSL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#ifdef GL_ES
#define LOWP lowp
precision mediump float;
#else
#define LOWP
#endif
 
#define PI 3.14
varying vec2 vTexCoord0;
varying LOWP vec4 vColor;
 
uniform sampler2D u_texture;
uniform vec2 resolution;
 
//for debugging; use a constant value in final release
uniform float upScale;
 
//alpha threshold for our occlusion map
const float THRESHOLD = 0.75;
 
 
void main(void) {
float distance = 1.0;
for (float y=0.0; y<resolution.y; y+=1.0) {
//rectangular to polar filter
vec2 norm = vec2(vTexCoord0.s, y/resolution.y) * 2.0 - 1.0;
float theta = PI*1.5 + norm.x * PI;
float r = (1.0 + norm.y) * 0.5;
//coord which we will sample from occlude map
vec2 coord = vec2(-r * sin(theta), -r * cos(theta))/2.0 + 0.5;
//sample the occlusion map
vec4 data = texture2D(u_texture, coord);
//the current distance is how far from the top we've come
float dst = y/resolution.y / upScale;
//if we've hit an opaque fragment (occluder), then get new distance
//if the new distance is below the current, then we'll use that for our ray
float caster = data.a;
if (caster > THRESHOLD) {
distance = min(distance, dst);
}
}
gl_FragColor = vec4(vec3(distance), 1.0);
}
shadowRender.frag
GLSL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
#ifdef GL_ES
#define LOWP lowp
precision mediump float;
#else
#define LOWP
#endif
 
#define PI 3.14
varying vec2 vTexCoord0;
varying LOWP vec4 vColor;
 
uniform sampler2D u_texture;
uniform vec2 resolution;
 
uniform float softShadows;
 
//sample from the distance map
float sample(vec2 coord, float r) {
return step(r, texture2D(u_texture, coord).r);
}
 
void main(void) {
//rectangular to polar
vec2 norm = vTexCoord0.st * 2.0 - 1.0;
float theta = atan(norm.y, norm.x);
float r = length(norm);
float coord = (theta + PI) / (2.0*PI);
//the tex coord to sample our 1D lookup texture
//always 0.0 on y axis
vec2 tc = vec2(coord, 0.0);
//the center tex coord, which gives us hard shadows
float center = sample(vec2(tc.x, tc.y), r);
//we multiply the blur amount by our distance from center
//this leads to more blurriness as the shadow "fades away"
float blur = (1./resolution.x) * smoothstep(0., 1., r);
//now we use a simple gaussian blur
float sum = 0.0;
sum += sample(vec2(tc.x - 4.0*blur, tc.y), r) * 0.05;
sum += sample(vec2(tc.x - 3.0*blur, tc.y), r) * 0.09;
sum += sample(vec2(tc.x - 2.0*blur, tc.y), r) * 0.12;
sum += sample(vec2(tc.x - 1.0*blur, tc.y), r) * 0.15;
sum += center * 0.16;
sum += sample(vec2(tc.x + 1.0*blur, tc.y), r) * 0.15;
sum += sample(vec2(tc.x + 2.0*blur, tc.y), r) * 0.12;
sum += sample(vec2(tc.x + 3.0*blur, tc.y), r) * 0.09;
sum += sample(vec2(tc.x + 4.0*blur, tc.y), r) * 0.05;
//1.0 -> in light, 0.0 -> in shadow
float lit = mix(center, sum, softShadows);
//multiply the summed amount by our distance, which gives us a radial falloff
//then multiply by vertex (light) color
gl_FragColor = vColor * vec4(vec3(1.0), lit * smoothstep(1.0, 0.0, r));
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.