What is the best 'simple light' effect for 2D Android Games?

Hello.

My game is almost finished and I’m trying several ways to deal with light effect in my game, my focus is for Android, so I need optimization.
I don’t want shadows or complex light effect, I only want a circle light with fade in the corner to position in different places (chandelier, table lamp, lamp post…)

I’m just following these tutorials:

Testing 10 lights in my screen (and one following the character), works perfectly in desktop version, testing without vsync and FPS Lock I have something like 3500-4500FPS.
Testing the same thing in different Android devices, the game run almost smooth, when I have more than 7-8 lights in the screen and moving the character I have some 15-30FPS drop.

So, what is the best and optmized way to make simple circle light effect for 2D Android Games? I’m also curious to understand how various games on google play have so many lights at the same time without drop frames.

Note: I have a ‘test project’ without all my game logic (map, enemies, weapons…), so I can test more precisely.

I mean you could always draw a yellow circle/oval or whatever yourself and make it transparent to your liking, and place this underneath the light sources you described. This would be the easiest and most efficient way for what you’re asking for. Cheers.

You didn’t click any of his links, did you? :stuck_out_tongue:

With the framebuffer mask method, it should actually work quite well. You should post your devices specs and the test-code. Maybe it’s something else. Posting on the libGDX forum will also probably lead to more answers.

I mean that’s not the same thing I was saying, he asked the best and optimized way to make simple circle light effect for 2D Android Games, and what I said is the most simple way to achieve what he wanted. :stuck_out_tongue: The tutorial has extra junk in there he wasn’t asking for in his post.

Title misleading, post content not.

Back on topic: I would be willing to bet that it has to do with the branching in the shader, how are you sending the lights and testing the number of lights to compute? After how many lights do you see a performance drop? is it in fact 10 lights? Seeing your shader code would help :).

Hello and sorry for the long wait, I was working on a project from my job.

Using the Shader technique:

Shader:

public class LightShaderBatch
{
	public static final float DEFAULT_LIGHT_Z = 0.084f;
	public static final float AMBIENT_INTENSITY = 0.42f;
	public static final float LIGHT_INTENSITY = 0.9f;
	
	public static final Vector3 LIGHT_POS = new Vector3(0,0,DEFAULT_LIGHT_Z);
	
	//Light RGB and intensity (alpha)
	//public static final Vector3 LIGHT_COLOR = new Vector3(1f, 1f, 1f);
	public static final Vector3 LIGHT_COLOR = new Vector3(0.5168301f, 0.6015233f, 0.78029454f);
 
	//Ambient RGB and intensity (alpha)
	public static final Vector3 AMBIENT_COLOR = new Vector3(0.9f, 0.9f, 0.9f);
 
	//Attenuation coefficients for light falloff
	public static final Vector3 FALLOFF = new Vector3(0.4f, 0.5f, 0.2f);
	
	
	public static 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" +
			"}";
	
	public static 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_texture1;   //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" + 
			"}";
}

Initialize:

	public void initializeShader()
	{
		ShaderProgram.pedantic = false;
		shader = new ShaderProgram(LightShaderBatch.VERT, LightShaderBatch.FRAG);
		if (!shader.isCompiled())
			throw new GdxRuntimeException("Could not compile shader: "+shader.getLog());
		if (shader.getLog().length()!=0)
			System.out.println(shader.getLog());
		
		shader.begin();
 
		shader.setUniformi("u_normals", 1);
		shader.setUniformf("LightColor", LightShaderBatch.LIGHT_COLOR.x, LightShaderBatch.LIGHT_COLOR.y, LightShaderBatch.LIGHT_COLOR.z, LightShaderBatch.LIGHT_INTENSITY);
		shader.setUniformf("AmbientColor", LightShaderBatch.AMBIENT_COLOR.x, LightShaderBatch.AMBIENT_COLOR.y, LightShaderBatch.AMBIENT_COLOR.z, LightShaderBatch.AMBIENT_INTENSITY);
		shader.setUniformf("Falloff", LightShaderBatch.FALLOFF);
		
		Texture a = new Texture("data/images/maps/pixel_test_shader.png");
		Texture b = new Texture("data/images/maps/pixel_test.png");
		a.bind(1);
		b.bind(0); 
		
		shader.end();
		game.batch = new SpriteBatch(1000, shader);
		game.batch.setProjectionMatrix(camera.combined);
		
		Gdx.input.setInputProcessor(new InputAdapter() {
			public boolean scrolled(int delta) {
				LightShaderBatch.LIGHT_POS.z = Math.max(0f, LightShaderBatch.LIGHT_POS.z - (delta * 0.005f));
				System.out.println("New light Z: "+LightShaderBatch.LIGHT_POS.z);

				//camera.zoom = Math.max(0f, camera.zoom - (delta * 0.05f));
				return true;
			}
		});
	}

Logic:

		LightShaderBatch.LIGHT_POS.set(
				lightPos.x/viewport.getWorldWidth(),
				lightPos.y/viewport.getWorldHeight(),
				LightShaderBatch.LIGHT_POS.z);
//viewport.getWorldWidth/Height is to set the correct position padding.

Draw:

shader.setUniformf("LightPos", LightShaderBatch.LIGHT_POS);

Using the Mask technique:

Initialize:

		//FrameBuffer
	   lightSprite = new Texture(Gdx.files.internal("data/images/sheet/light.png"));
	   
	if (lightBuffer!=null) lightBuffer.dispose();
	   lightBuffer = new FrameBuffer(Format.RGBA8888, resW, resH, false);
	   lightBuffer.getColorBufferTexture().setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
	   lightBufferRegion = new TextureRegion(lightBuffer.getColorBufferTexture(), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
	   lightBufferRegion.flip(false, true);

Draw:

		drawGame();
		drawFBO();
		drawUI; //stage user interface

drawFBO:

	public void drawFBO()
	{
		//LightBuffer
     	lightBuffer.begin();
	      Gdx.gl.glClearColor(0f, 0f, 0f, 1f); //bg
			Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
			game.batch.begin();
				game.batch.setBlendFunction(-1, -1);
				Gdx.gl.glBlendFuncSeparate(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA, GL30.GL_ONE, GL30.GL_ONE);
				game.batch.setColor(1f, 1f, 0f, 1f); //Light 1
				game.batch.draw(lightSprite, resW/2-radius-150, resH/2-radius, diam, diam);
				game.batch.setColor(0f, 1f, 1f, 1f); //Light 2
				game.batch.draw(lightSprite, resW/2-radius+150, resH/2-radius, diam, diam);
			game.batch.end();
		lightBuffer.end();
		
		Gdx.gl.glBlendFunc(GL30.GL_DST_COLOR, GL30.GL_ZERO);
		game.batch.begin();
			game.batch.setColor(1f, 1f, 1f, 1f); //FBO to texture.
			game.batch.draw(lightBufferRegion, 0, 0);   
		game.batch.end();
	}

Notes:

  • Shaders: I was using the light to follow the front of a car (car headlight).
  • Mask: I’m using in pre-defined positions, light from sources (lamps). I’m using only two BIG lights in the code above only to show, but I’m testing 10+ with different sizes, colors.
  • The Car example is just my old game, but I tried the same code.
  • Screens:
    http://s9.postimg.org/d0yr53em7/car.png
    http://s18.postimg.org/lri2fxqsp/light.png

As you can see, the lights work perfectly, but I’m not confident that I’ll can use them as adding more lights. The mobile version loses some frames (and I have not tested it yet using all the objects and enemies of the game at same time), maybe because I need to call batch.begin/end a lot of times for FBO?

How games can have glow/light effect on tons of bullets or on each enemy without losing performance. =S

[quote=“craftm,post:6,topic:55626”]
Sprites.