Last active
May 24, 2022 13:43
-
-
Save mattdesl/4653464 to your computer and use it in GitHub Desktop.
LibGDX Port of ShaderLesson6, dealing with normal mapping in 2D games
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.lwjgl.input.Mouse; | |
import org.lwjgl.opengl.Display; | |
import com.badlogic.gdx.ApplicationListener; | |
import com.badlogic.gdx.Gdx; | |
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.glutils.ShaderProgram; | |
import com.badlogic.gdx.math.Vector3; | |
import com.badlogic.gdx.utils.GdxRuntimeException; | |
/** | |
* LibGDX port of ShaderLesson6, i.e. normal mapping in 2D games. | |
* @author davedes | |
*/ | |
public class ShaderLesson6 implements ApplicationListener { | |
public static void main(String[] args) { | |
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration(); | |
cfg.useGL20 = true; | |
cfg.width = 640; | |
cfg.height = 480; | |
cfg.resizable = false; | |
new LwjglApplication(new ShaderLesson6(), cfg); | |
} | |
Texture rock, rockNormals; | |
SpriteBatch batch; | |
OrthographicCamera cam; | |
ShaderProgram shader; | |
//our constants... | |
public static final float DEFAULT_LIGHT_Z = 0.075f; | |
public static final float AMBIENT_INTENSITY = 0.2f; | |
public static final float LIGHT_INTENSITY = 1f; | |
public static final Vector3 LIGHT_POS = new Vector3(0f,0f,DEFAULT_LIGHT_Z); | |
//Light RGB and intensity (alpha) | |
public static final Vector3 LIGHT_COLOR = new Vector3(1f, 0.8f, 0.6f); | |
//Ambient RGB and intensity (alpha) | |
public static final Vector3 AMBIENT_COLOR = new Vector3(0.6f, 0.6f, 1f); | |
//Attenuation coefficients for light falloff | |
public static final Vector3 FALLOFF = new Vector3(.4f, 3f, 20f); | |
final String VERT = | |
"attribute vec4 "+ShaderProgram.POSITION_ATTRIBUTE+";\n" + | |
"attribute vec4 "+ShaderProgram.COLOR_ATTRIBUTE+";\n" + | |
"attribute vec2 "+ShaderProgram.TEXCOORD_ATTRIBUTE+"0;\n" + | |
"uniform mat4 u_projTrans;\n" + | |
" \n" + | |
"varying vec4 vColor;\n" + | |
"varying vec2 vTexCoord;\n" + | |
"void main() {\n" + | |
" vColor = "+ShaderProgram.COLOR_ATTRIBUTE+";\n" + | |
" vTexCoord = "+ShaderProgram.TEXCOORD_ATTRIBUTE+"0;\n" + | |
" gl_Position = u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" + | |
"}"; | |
//no changes except for LOWP for color values | |
//we would store this in a file for increased readability | |
final String FRAG = | |
//GL ES specific stuff | |
"#ifdef GL_ES\n" // | |
+ "#define LOWP lowp\n" // | |
+ "precision mediump float;\n" // | |
+ "#else\n" // | |
+ "#define LOWP \n" // | |
+ "#endif\n" + // | |
"//attributes from vertex shader\n" + | |
"varying LOWP vec4 vColor;\n" + | |
"varying vec2 vTexCoord;\n" + | |
"\n" + | |
"//our texture samplers\n" + | |
"uniform sampler2D u_texture; //diffuse map\n" + | |
"uniform sampler2D u_normals; //normal map\n" + | |
"\n" + | |
"//values used for shading algorithm...\n" + | |
"uniform vec2 Resolution; //resolution of screen\n" + | |
"uniform vec3 LightPos; //light position, normalized\n" + | |
"uniform LOWP vec4 LightColor; //light RGBA -- alpha is intensity\n" + | |
"uniform LOWP vec4 AmbientColor; //ambient RGBA -- alpha is intensity \n" + | |
"uniform vec3 Falloff; //attenuation coefficients\n" + | |
"\n" + | |
"void main() {\n" + | |
" //RGBA of our diffuse color\n" + | |
" vec4 DiffuseColor = texture2D(u_texture, vTexCoord);\n" + | |
" \n" + | |
" //RGB of our normal map\n" + | |
" vec3 NormalMap = texture2D(u_normals, vTexCoord).rgb;\n" + | |
" \n" + | |
" //The delta position of light\n" + | |
" vec3 LightDir = vec3(LightPos.xy - (gl_FragCoord.xy / Resolution.xy), LightPos.z);\n" + | |
" \n" + | |
" //Correct for aspect ratio\n" + | |
" LightDir.x *= Resolution.x / Resolution.y;\n" + | |
" \n" + | |
" //Determine distance (used for attenuation) BEFORE we normalize our LightDir\n" + | |
" float D = length(LightDir);\n" + | |
" \n" + | |
" //normalize our vectors\n" + | |
" vec3 N = normalize(NormalMap * 2.0 - 1.0);\n" + | |
" vec3 L = normalize(LightDir);\n" + | |
" \n" + | |
" //Pre-multiply light color with intensity\n" + | |
" //Then perform \"N dot L\" to determine our diffuse term\n" + | |
" vec3 Diffuse = (LightColor.rgb * LightColor.a) * max(dot(N, L), 0.0);\n" + | |
"\n" + | |
" //pre-multiply ambient color with intensity\n" + | |
" vec3 Ambient = AmbientColor.rgb * AmbientColor.a;\n" + | |
" \n" + | |
" //calculate attenuation\n" + | |
" float Attenuation = 1.0 / ( Falloff.x + (Falloff.y*D) + (Falloff.z*D*D) );\n" + | |
" \n" + | |
" //the calculation which brings it all together\n" + | |
" vec3 Intensity = Ambient + Diffuse * Attenuation;\n" + | |
" vec3 FinalColor = DiffuseColor.rgb * Intensity;\n" + | |
" gl_FragColor = vColor * vec4(FinalColor, DiffuseColor.a);\n" + | |
"}"; | |
@Override | |
public void create() { | |
rock = new Texture(Gdx.files.internal("data/rock.png")); | |
rockNormals = new Texture(Gdx.files.internal("data/rock_n.png")); | |
ShaderProgram.pedantic = false; | |
shader = new ShaderProgram(VERT, FRAG); | |
//ensure it compiled | |
if (!shader.isCompiled()) | |
throw new GdxRuntimeException("Could not compile shader: "+shader.getLog()); | |
//print any warnings | |
if (shader.getLog().length()!=0) | |
System.out.println(shader.getLog()); | |
//setup default uniforms | |
shader.begin(); | |
//our normal map | |
shader.setUniformi("u_normals", 1); //GL_TEXTURE1 | |
//light/ambient colors | |
//LibGDX doesn't have Vector4 class at the moment, so we pass them individually... | |
shader.setUniformf("LightColor", LIGHT_COLOR.x, LIGHT_COLOR.y, LIGHT_COLOR.z, LIGHT_INTENSITY); | |
shader.setUniformf("AmbientColor", AMBIENT_COLOR.x, AMBIENT_COLOR.y, AMBIENT_COLOR.z, AMBIENT_INTENSITY); | |
shader.setUniformf("Falloff", FALLOFF); | |
//LibGDX likes us to end the shader program | |
shader.end(); | |
batch = new SpriteBatch(1000, shader); | |
batch.setShader(shader); | |
cam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); | |
cam.setToOrtho(false); | |
//handle mouse wheel | |
Gdx.input.setInputProcessor(new InputAdapter() { | |
public boolean scrolled(int delta) { | |
//LibGDX mouse wheel is inverted compared to lwjgl-basics | |
LIGHT_POS.z = Math.max(0f, LIGHT_POS.z - (delta * 0.005f)); | |
System.out.println("New light Z: "+LIGHT_POS.z); | |
return true; | |
} | |
}); | |
} | |
@Override | |
public void resize(int width, int height) { | |
cam.setToOrtho(false, width, height); | |
batch.setProjectionMatrix(cam.combined); | |
shader.begin(); | |
shader.setUniformf("Resolution", width, height); | |
shader.end(); | |
} | |
@Override | |
public void render() { | |
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); | |
//reset light Z | |
if (Gdx.input.isTouched()) { | |
LIGHT_POS.z = DEFAULT_LIGHT_Z; | |
System.out.println("New light Z: "+LIGHT_POS.z); | |
} | |
batch.begin(); | |
//shader will now be in use... | |
//update light position, normalized to screen resolution | |
float x = Mouse.getX() / (float)Display.getWidth(); | |
float y = Mouse.getY() / (float)Display.getHeight(); | |
LIGHT_POS.x = x; | |
LIGHT_POS.y = y; | |
//send a Vector4f to GLSL | |
shader.setUniformf("LightPos", LIGHT_POS); | |
//bind normal map to texture unit 1 | |
rockNormals.bind(1); | |
//bind diffuse color to texture unit 0 | |
//important that we specify 0 otherwise we'll still be bound to glActiveTexture(GL_TEXTURE1) | |
rock.bind(0); | |
//draw the texture unit 0 with our shader effect applied | |
batch.draw(rock, 0, 0); | |
batch.end(); | |
} | |
@Override | |
public void pause() { | |
} | |
@Override | |
public void resume() { | |
} | |
@Override | |
public void dispose() { | |
batch.dispose(); | |
rock.dispose(); | |
rockNormals.dispose(); | |
shader.dispose(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
wouldn't this not work if you have 2 objects with different normals