Search code examples
openglopengl-esshaderfragment-shader

GLSL Fragment Shader to intelligently tile texture


I am creating a game where I have platforms of different sizes - but always in multiplications of 8 pixels and always 8 pixels high.

All platforms use the same texture like the one here:

Tiling texture image with description

The texture is divided up into three 8x8 sections. I want to be able to "tile" this texture via the fragment shader so that the first 8 pixels are applied only at the left end - the middle 8 pixels tiles for as many pixels as the width of the geometry minus the last 8 pixels where the "end" tile is rendered. Something like this geometry which is 32 pixels wide and 8 pixels high:

Tiled platform

The framework I use does not allow setting the vertices and UVs (just a simple textured quad) so I cannot fix this in the geometry.

I get the following uniforms in the fragment shader:

  • sampler2D u_texture - The texture above

  • vec2 u_sprite_size - The size of the geometry I want to tile the texture in (in pixels)

.. and this varying:

  • vec2 v_tex_coord - The coordinates used to access the texture. These are normalized so that the point (0.0,0.0) is in the bottom-left corner of the texture.

It is possible to add other uniforms via code.

Can someone point me in the direction of how to create the fragment shader?


Solution

  • I tried to solve your problem so I wrote a simple fragment shader. It is probably not the cleanest solution so you can try to optimize it.

    The first thing you need is to calculate the number of tiles in the object (only vertically):

    float verticalTiles = u_sprite_size.x / 8.0;
    

    After that you have to calculate the tile you are currently processing and the UV for this tile. To do this you multiply the uv coordinates with the amount of vertical tiles and floor the value because you want only whole number. Then you also need to fract the value to get uv coordinates:

    float tile = floor(verticalTiles * v_tex_coord.x);
    float tileUV = fract(verticalTiles  * v_tex_coord.x); // [0; 1] for each tile
    

    Now you want to apply the first 8 pixels of the texture only to the first tile and last 8 pixels to the last tile. Other tiles would have the middle 8 pixels. It can be sampled using a simple if statement. We are only calculating the U value because V won't change anyway:

    float resultU; 
    
    if(tile == 0.0){ 
        // If this is first tile, then we want the UV coordinates of the first 8 pixels.
        // We divide tileUV by 3 to get UV coordinates [0; 0.3333].
        resultU = tileUV / 3.0;
    
    }else if(tile == verticalTiles - 1.0){
        // If this is last tile we do the same thing but add two thirds
        // to the coordinates to get last 8 pixels [0.6666; 1]
        resultU = tileUV / 3.0 + (2.0 / 3.0);
    
    }else{
        // Else we want the middle 8 pixels [0.3333; 0.6666]
        resultU = tileUV / 3.0 + (1.0 / 3.0);
    }
    

    Then use this value to sample from the texture:

    gl_FragColor = texture2D( u_texture, vec2(resultU, v_tex_coord.y));