package atmos;
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.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
/**
* Simple illumination model with shaders in LibGDX.
* @author davedes / mattdesl
*/
public class Illumination2D implements ApplicationListener {
Texture texture, texture_n;
OrthographicCamera cam;
SpriteBatch fxBatch, batch;
TextureRegion sprite, spriteNormals;
Matrix4 transform = new Matrix4();
// the color of our light
Vector3 lightColor = new Vector3(1f, 1f, 1f);
// the position of our light in 3D space
Vector3 lightPos = new Vector3(0f, 0f, 2f);
// the ambient color (color to use when unlit)
Vector3 ambientColor = new Vector3(1f, 1f, 1f);
// the resolution of our game/graphics
Vector2 resolution = new Vector2();
// the attenuation factor: x=constant, y=linear, z=quadratic
Vector3 attenuation = new Vector3(0.75f, 0.0f, 0.00001f);
// the ambient intensity (brightness to use when unlit)
float ambientIntensity = 0.1f;
// whether to use attenuation/shadows
boolean useShadow = true;
// whether to use lambert shading (with our normal map)
boolean useNormals = true;
ShaderProgram program;
public void create() {
// load our textures
texture = new Texture(Gdx.files.internal("data/rock.png"));
texture_n = new Texture(Gdx.files.internal("data/rock_n.png"));
// we'll use texture regions instead of Stage2D/Actors for simplicity's sake
sprite = new TextureRegion(texture);
sprite.flip(false, true);
spriteNormals = new TextureRegion(texture_n);
spriteNormals.flip(false, true);
// a simple 2D orthographic camera
cam = new OrthographicCamera(Gdx.graphics.getWidth(),
Gdx.graphics.getHeight());
cam.setToOrtho(true);
// create our shader program...
program = createShader();
// now we create our sprite batch for our shader
fxBatch = new SpriteBatch(100, program);
// setShader is needed; perhaps this is a LibGDX bug?
fxBatch.setShader(program);
fxBatch.setProjectionMatrix(cam.combined);
fxBatch.setTransformMatrix(transform);
// usually we would just use a single batch for our application,
// but for demonstration let's also show the un-affected image
batch = new SpriteBatch(100);
batch.setProjectionMatrix(cam.combined);
batch.setTransformMatrix(transform);
// quick little input for debugging -- press S to toggle shadows, N to
// toggle normals
Gdx.input.setInputProcessor(new InputAdapter() {
public boolean keyDown(int key) {
if (key == Keys.S) {
useShadow = !useShadow;
return true;
} else if (key == Keys.N) {
useNormals = !useNormals;
return true;
}
return false;
}
});
}
private ShaderProgram createShader() {
// see the code here: http://pastebin.com/7fkh1ax8
// simple illumination model using ambient, diffuse (lambert) and attenuation
// see here: http://nccastaff.bournemouth.ac.uk/jmacey/CGF/slides/IlluminationModels4up.pdf
String vert = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
+ "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ "uniform mat4 u_proj;\n" //
+ "uniform mat4 u_trans;\n" //
+ "uniform mat4 u_projTrans;\n" //
+ "varying vec4 v_color;\n" //
+ "varying vec2 v_texCoords;\n" //
+ "\n" //
+ "void main()\n" //
+ "{\n" //
+ " v_color = " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
+ " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ " gl_Position = u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ "}\n";
String frag = "#ifdef GL_ES\n" +
"precision mediump float;\n" +
"#endif\n" +
"varying vec4 v_color;\n" +
"varying vec2 v_texCoords;\n" +
"uniform sampler2D u_texture;\n" +
"uniform sampler2D u_normals;\n" +
"uniform vec3 light;\n" +
"uniform vec3 ambientColor;\n" +
"uniform float ambientIntensity; \n" +
"uniform vec2 resolution;\n" +
"uniform vec3 lightColor;\n" +
"uniform bool useNormals;\n" +
"uniform bool useShadow;\n" +
"uniform vec3 attenuation;\n" +
"\n" +
"void main() {\n" +
" vec4 color = texture2D(u_texture, v_texCoords.st);\n" +
" vec3 normal = normalize(texture2D(u_normals, v_texCoords.st).rgb * 2.0 - 1.0);\n" +
" vec3 light_pos = normalize(light);\n" +
" float lambert = useNormals ? max(dot(normal, light_pos), 0.0) : 1.0;\n" +
" \n" +
" //now let's get a nice little falloff\n" +
" float d = distance(gl_FragCoord.xy, light.xy * resolution);\n" +
" d *= light.z;\n" +
" \n" +
" float att = useShadow ? 1.0 / ( attenuation.x + (attenuation.y*d) + (attenuation.z*d*d) ) : 1.0;\n" +
" \n" +
" vec3 result = (ambientColor * ambientIntensity) + (lightColor.rgb * lambert) * att;\n" +
" result *= color.rgb;\n" +
" \n" +
" gl_FragColor = v_color * vec4(result, color.a);\n" +
"}";
ShaderProgram program = new ShaderProgram(vert, frag);
// u_proj and u_trans will not be active but SpriteBatch will still try to set them...
program.pedantic = false;
if (program.isCompiled() == false)
throw new IllegalArgumentException("couldn't compile shader: "
+ program.getLog());
// set resolution vector
resolution.set(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
// we are only using this many uniforms for testing purposes...!!
program.begin();
program.setUniformi("u_texture", 0);
program.setUniformi("u_normals", 1);
program.setUniformf("light", lightPos);
program.setUniformf("ambientIntensity", ambientIntensity);
program.setUniformf("ambientColor", ambientColor);
program.setUniformf("resolution", resolution);
program.setUniformf("lightColor", lightColor);
program.setUniformf("attenuation", attenuation);
program.setUniformi("useShadow", useShadow ? 1 : 0);
program.setUniformi("useNormals", useNormals ? 1 : 0);
program.end();
return program;
}
public void dispose() {
fxBatch.dispose();
batch.dispose();
texture.dispose();
texture_n.dispose();
}
public void render() {
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
cam.update();
// draw our sprites without any effects
batch.begin();
batch.draw(sprite, 0, 0);
batch.draw(spriteNormals, sprite.getRegionWidth() + 10, 0);
batch.end();
// start our FX batch, which will bind our shader program
fxBatch.begin();
// get y-down light position based on mouse/touch
lightPos.x = Gdx.input.getX() / (float) Gdx.graphics.getWidth();
lightPos.y = (resolution.y - Gdx.input.getY() - 1)
/ (float) Gdx.graphics.getHeight();
// update our uniforms
program.setUniformf("light", lightPos);
program.setUniformi("useNormals", useNormals ? 1 : 0);
program.setUniformi("useShadow", useShadow ? 1 : 0);
// bind the normal first at texture1
texture_n.bind(1);
// bind the actual texture at texture0
texture.bind(0);
// we bind texture0 second since draw(sprite) will end up binding it at
// texture0...
fxBatch.draw(sprite, 0, sprite.getRegionHeight() + 10);
fxBatch.end();
}
public void resize(int width, int height) {
cam.setToOrtho(true, width, height);
resolution.set(width, height);
program.setUniformf("resolution", resolution);
}
public void pause() {
}
public void resume() {
}
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "lighting";
cfg.useGL20 = true;
cfg.width = 1024;
cfg.height = 768;
new LwjglApplication(new Illumination2D(), cfg);
}
}