libGDX: Draw a wavy background

Here is code to make a static picture a wavy background.

A video of what I mean:

I don’t know if there’s a better name for it, but basically, it’s an effect used in various old video games. The effect worked either scanline based if the hardware supported it (e.g. Game Boy) or simply by sliding the tile rows back and forth. It was often used in lava scenes (to show heat) or underwater scenes.

I wanted to use that effect in my game, so I came up with this code. I have a small feeling that this effect is actually really easy to code, but I was a bit proud when I did it. :stuck_out_tongue: It may be useful to someone, so I’m sharing it.

Usage:

  1. Instantiate the BGWave with the file name of the background pic you want to wave (pic should be close to the same resolution as the screen)
  2. update() every frame.
  3. render() every draw.
  4. Remember to dispose() the background texture when you’re finished with it.

You may change the field variables to tweak the wave to your liking. Here is a picture describing what the field variables do:

The code itself:


import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;

public class BGWave {

	//The background texture
	Texture bg;
	
	//the screen resolution. Update this every frame if you allow screen resizing
	int hres = 640;
	int vres = 480;
	
	//the number of segments on the screen. The higher the number, the better quality
	//the wave looks, but the more polys drawn. Max value is the vertical screen resolution.
	int segments = 32;
	
	//distance wave will travel (be pushed) left/right
	float cycstrength = 16;
	
	//number of complete wave cycles on screen
	float numcycles = 2;
	
	//speed wave will cycle
	float cyclespeed = 1/numcycles;
	
	//the current position of the wave cycle
	float cycoffset = 0;
	
	
	/** Creates the wave background, loading the background texture into memory */
	BGWave(String filename) {
		bg = new Texture(Gdx.files.internal(filename));
	}
	
	
	/** Update the wave by increasing the position of the wave cycle by the speed */
	void update() {
		//make sure you update the resolution (hres/vres) every frame
		// if you expect the screen to be resized
		cycoffset += cyclespeed;
	}
	
	/** Render the wavy background */
	void render() {
		
		//scale the image so that the background image touches the window with no black borders
		float bgscale = hres/(float)bg.getWidth();
		float temp = vres/(float)bg.getHeight();
		if (temp > bgscale) bgscale = temp;
		
		//For reference, here's how you'd draw the background normally, no wave
		/*
		batch.draw(bg,
				GameMain.hres/2-(bg.getWidth()*bgscale)/2,
				GameMain.vres/2-(bg.getHeight()*bgscale)/2,
				bg.getWidth()*bgscale,
				bg.getHeight()*bgscale);
		*/
		
		//size of segment on screen
		float segmentSize = vres/(float)segments;
		//size of segment in the picture to draw
		float picSegSize = (segmentSize/bgscale);
		float picYBorder = ((((bg.getHeight()*bgscale)-vres)/2)/bgscale)*1.5f;
		
		
		for (int i = 0; i < segments; i++) {
			batch.draw(bg,
					hres/2-(bg.getWidth()*bgscale)/2 + (pos(i*segmentSize)), //x
					i*segmentSize,                                           //y
					bg.getWidth()*bgscale,                                   //width
					segmentSize,                                             //height
					
					0,                                                       //picture x
					(int) (bg.getHeight()-picYBorder-(picSegSize*i)-picSegSize), //pic y
					bg.getWidth(),                                           //pic wid
					(int)picSegSize,                                         //pic hei
					false,false);
		}
		
	}
	
	/** returns the amount the wave should be pushed left/right in the given position */
	float pos(float num) {
		num += cycoffset;
		return (float) (Math.sin(num*(Math.PI*2/vres)*numcycles)) * cycstrength;
	}

}


Enjoy! Use it as you like.

Room for improvement:

  • don’t use quads for each segment, use triangles to make parallelograms (it still looks good with quads, though).

This could be done with a small shader. Something like this:

vert:


// Simple vertex shader
// only maps the verticies to the correct position in the matrix

void main() {
	gl_TexCoord[0] = gl_MultiTexCoord0;
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;  
}

frag:


uniform float time; 		
uniform float speed;		
uniform vec2 amplitude;		
uniform vec2 frequence;		
uniform sampler2D texture; 	

const float pi = 3.14159;

void main() 
	vec2 uv = gl_TexCoord[0].xy;
	vec2 temp = vec2(uv.x, uv.y);
	
	float angularFre = 2.0 * pi * frequence.x;
	uv.x += sin(temp.y * angularFre  + (time * (speed * 10.0))) * ((amplitude.x) / texsize.x) * 10.0;
	
	vec4 color = texture2D(texture, uv);

 	gl_FragColor = color;
}

Noooot tested. You need to change for libgdx a bit (and changed the magic numbers maybe).