Tutorial 02 – Vertex shader for tiled water

 downloadDownload the ready to use Eclipse project for this tutorial

Overview

In this page you will learn how to create a very simple vertex shader compatible with the libgdx SpriteBatch’s default vertex usage to animate 2D tiles. We are trying to simulate the water wobbles of water surface. Although this uses specifically libgdx, it can easily be ported to C++, XNA, etc, just like the first tutorial.

This is the effect that will be produced:

In this video you can notice at first the soothing wavy water, but after that I tweak some settings to show in fact what’s really happening behind the scenes.

Note: assets used for this tutorial

The tilset used in this game is the same than the first one and is a free resource found on OpenGameArt.org.

RPG tileset

Credit: Zabin, Daneeklu, Jetrel, Hyptosis, Redshrike, Bertram. License: CC BY SA 3.0

Introduction

Tiled games don’t have to be look static and boring. You can make them more lively through techniques like the lighting described in the first tutorial, but you can also directly make things move. You can play around with pixel shaders and UV coordinates, but one thing that I find really cool with tiles based games is vertex shaders. Because by default a tiled game uses fixed blocks to build a map, vertex shader comes in handy to animate these boring quads. This technique will work with basically any 2D game by the way, I just wanted to point out it seems very appropriate for games based on tiles.

Before we go intro programming, you will have to create a map using the tileset. For this I used Tiled map editor, but I guess any kind of map editor would do. The thing I like with Tiled is that I can export the map to JSON and then can work directly with the map raw values rather than having to write a specific loader/renderer.

So first I defined a water layer:

 

The water layer

The water layer

And that’s actually all that is needed for this tutorial… But just for good measure and for a more artsy effect, I just added 2 layers of objects. Combined together they form the full map:

layer1 layer2

 

Everything combined together!

Everything combined together!

 

The code

Now, the code is very simple to understand. We want the water layer to be animated so that it makes the illusion of waves, and we want the other two layers to be drawn on top, on a traditional, static way. When the game opens, we are going to set up a few variables, declares our sprite batch and 2 shaders: the static normal one, and our custom one for the water:

@Override
	public void create() {

		ShaderProgram.pedantic = false;

		batch = new SpriteBatch();

		final String vertexShader = new FileHandle("data/vertexShader.glsl").readString();
		final String fragmentShader = new FileHandle("data/defaultPixelShader.glsl").readString();
		shader = new ShaderProgram(vertexShader, fragmentShader);
		defaultShader = SpriteBatch.createDefaultShader();
		tilemap = new Tilemap();

“Tilemap” is a class that holds the map data from Tiled and draw it layer by layer. There is nothing fancy behind this.

Now, of course, what’s interesting is the vertex shader:

attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
uniform vec2 waveData; //allows to make waaaves
varying vec4 vColor;
varying vec2 vTexCoord;

void main() {
	vColor = a_color;
	vTexCoord = a_texCoord0;
	vec4 newPos = vec4(a_position.x + waveData.y * sin(waveData.x+a_position.x+a_position.y), a_position.y + waveData.y * cos(waveData.x+a_position.x+a_position.y), a_position.z, a_position.w);
	gl_Position = u_projTrans * newPos;	
}

Now to understand this piece of code, don’t forget one thing: the goal of the “regular” vertex shader is to take the coordinates of your sprite, multiply them by the projection/translation camera matrix so that they are in the screen coordinates, and that is it.

So the normal vertex shader of libgdx does gl_Position = u_projTrans (the camera matrix) * a_position (the position of the vertex being processed) and this is it.

The difference here, is that we declare a new uniform: vec2 waveData. waveData is not a vector in itself, but we store an angle between 0 and 2PI on the x value and the wave amplitude on the y value, for convenience.

The angle fed to this vertex shader is CPU computed, just like that:

	@Override
	public void render() {

		final float dt = Gdx.graphics.getRawDeltaTime();

		angleWave += dt * angleWaveSpeed;
		while(angleWave > PI2)
			angleWave -= PI2;

Again, there is no rocket science here. We increment an angle at a given pace “angleWaveSpeed” in radiant per seconds. When we exceed 2PI we substract 2PI since x + 2PI = x with sine/cosine functions.

Sine/cosine functions? Indeed, the key to achieving a waving effect is to use a periodic, wavy mathematical function. Sine and cosine are perfect for this so we are going to use them:

a_position.x + waveData.y * sin(waveData.x+a_position.x+a_position.y)

Here, we change the original x position of your sprite for its original position + the result of a sine function. Sine oscillates between -1 and 1 and in this tutorial, I am using tile blocks of 1.0fx1.0f in size. So if I want the x coordinate of each water block to gently wobble around 10% of their original x position, I will have to assign a wave amplitude of 0.1. That’s the waveData.y.

Now, what you feed the sin function is mostly irrelevant. Of course you need the computed angle that changes every frame (waveData.x), but then you can add anything. Here I am adding a mix of the positions. But you could do something else. For the y coordinates, the same logic applies except this time I am using a cosine. No particular reason. A cosine is just a shifted sine by PI/2 anyway. If we were not using a SpriteBatch and the water surface was a single mesh made out of square quads, we could even use the UV coordinates… You can experiment and see for yourself!

Drawing

Here is the full render code in a very few lines of code:

	@Override
	public void render() {

		final float dt = Gdx.graphics.getRawDeltaTime();

		angleWave += dt * angleWaveSpeed;
		while(angleWave > PI2)
			angleWave -= PI2;

		//feed the shader with the new data
		shader.begin();
		shader.setUniformf("waveData", angleWave, amplitudeWave);
		shader.end();

		//render the first layer (the water) using our special vertex shader
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		batch.setProjectionMatrix(cam.combined);
		batch.setShader(shader);
		batch.begin();
		tilemap.render(batch, 0, dt);
		batch.flush();

		//render the other layers using the default shader
		batch.setShader(defaultShader);
		tilemap.render(batch, 1, dt);
		tilemap.render(batch, 2, dt);

		//super duper useless but cool: add a rocking boat
		tilemap.renderBoat(batch, angleWave);
		batch.end();

So the logic here: feed the shader the new value, use our special shader, draw the water layer, use the normal shader, draw the rest.

As a bonus I included a sprite of a boat oscillating according to wave speed. But this is it, a very simple and effective vertex shader!

Final words

This shader will work very nicely but drawing a whole water surface using a sprite batch is very inefficient. You could build a single mesh out of the water layer and use GL_REPEAT and smart UV mapping to achieve the same result with 4 times less vertices and thus far less processing in the vertex shader.

Nonetheless, this vertex shader is cheap and is doing a great job, so abuse it!

 downloadDownload the ready to use Eclipse project for this tutorial

9 Comments

  • […] Tutorial 02 – Vertex shader for tiled water […]

  • Wisnu Mulyadi says:

    This is great !
    I’ve been looking for something like this. But in my case, i want to create water wave river-like for my game in 3D. Can i also apply this OpenGL to 3D also?
    I have sent e-mail for your guidance, hope you can help me.
    Thanks for the tutorial :)

  • Antuan says:

    Lovely. I had to make a few mods. But still… Lovely.

  • Cryo says:

    Is it possible to use the following vertex shader with tiled maps?
    http://rotatingcanvas.com/fragment-shader-to-simulate-water-surface-in-libgdx/

    I tried but it didn’t work.

    • Tony Pottier says:

      Hello Cryo,

      No it won’t. The methods described are way too different. The pixel shader in your look distorts a texture as per a sine function. The method shown here distorts vertex coordinates as per a sine function.

  • JuKu says:

    Hi,

    I have tried out your tutorial, but i get this error:
    java.lang.IllegalArgumentException: no uniform with name ‘waveData’ in shader

    Maybe you have to set shader first?

  • JuKu says:

    Maybe this tutorial isnt compatible with newest libGDX version?

  • JuKu says:

    I have found the problem, the new uniform names are:
    – v_texCoords
    – v_color

    But it doesnt work. Here is my vertex shader:
    attribute vec4 a_position;
    attribute vec4 a_color;
    attribute vec2 a_texCoord0;
    uniform mat4 u_projTrans;

    //allows to make waves
    uniform vec2 waveData;

    varying vec4 v_color;
    varying vec2 v_texCoords;

    void main()
    {
    v_color = a_color;
    //v_color.a = v_color.a * (255.0/254.0);
    v_texCoords = a_texCoord0;

    vec4 newPos = vec4(a_position.x + waveData.y * sin(waveData.x+a_position.x+a_position.y), a_position.y + waveData.y * cos(waveData.x+a_position.x+a_position.y), a_position.z, a_position.w);

    gl_Position = u_projTrans * newPos;
    }

    And this is my fragment shader:
    #ifdef GL_ES
    #define LOWP lowp
    precision mediump float;
    #else
    #define LOWP
    #endif
    varying LOWP vec4 v_color;
    varying vec2 v_texCoords;
    uniform sampler2D u_texture;

    void main()
    {
    gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
    }

    Whats wrong?
    Can you help me?

    • Tony says:

      Unfortunately I haven’t touched the newer libgdx in ages so I am not entirely sure. I might try to port this code to the newer version this weekend.

      Cheers

Leave a Reply

css.php