Skip to main content Link Menu Expand (external link) Document Search Copy Copied


A 2D brick procedural shader for a brick wall. This shader is based on this tutorial.

Variable Declarations

    vec3 BRICK_COLOR = vec3(0.5451, 0.1804, 0.0353);
    vec3 MORTAR_COLOR = vec3(0.8784, 0.8667, 0.7373);
    vec2 BRICK_TILE_SIZE = vec2(.08, .03);
    vec2 BRICK_PCT = vec2(.9);
    vec3 color = vec3(0.);

First things first, we declare the different variables we’ll need in order to build the brick wall. Some of these are pretty straight forward, BRICK_COLOR and MORTAR_COLOR which are vec3 describing the colors. The color variable is a vec3 that our function will return after we modify it. Then the two variables that need a few explanations. First BRICK_TILE_SIZE. It describes the size of of a tile, containing a brick and the mortar on the top and right side around it as shown below. The BRICK_PCT variable stands for the brick pourcentage, meaning how much of a brick tile is made up of brick rather than mortar.


Next step consists of tiling our canvas in order to display more than one brick. For that we’ll use the same logic as explained in the tiling explanation, meaning we will devide our noramlised canvas by the size of a brick tile.

    vec2 position = st/BRICK_TILE_SIZE;
    vec2 norm_position = fract(position)

This gives us as the integral part of the result, the row number for position.y and the brick number for position.x. The fractional part of the result is the normalised position within each brick tile.

Brick Layout Offset

In order to make it look a bit more realistic, the bricks shouldn’t be layed out directly on top of one another, but instead be layed out with an offset on the x axis.

The goal next is to offset position.x every time the row number is odd. To do this we use the code below.

    position.x += step(fract(position.y * 0.5),0.5)/2.;

We are working on non normalised position values, so we need to insert this offset code before we normalise the brick coordinates.

First, let’s break down this part


The fract() function return the fractional part of a float. On the left fract(y), on the right fract(y*0.5):

The difference is that when y is halved, the fractional part of why covers two rows instead of one. We have a new row of bricks every 0.5, instead of every 1 units. With this new value we can deduce if a row is odd or even, because for even row fract(y*0.5) < 0.5, and for odd rows fract(y*0.5) > 0.5.

All we have to do is use the step() function which returns the 0 or 1 by comparing a given value to a threshold like so:


In this case if x i 0.2, we get 0, if x is 0.8 we get 1.

So if use the step() function with our new value and compare it to 0.5, we should get a value of 1 every 2 rows. Diagrams are sometimes better than words, and they’re always better than my words, so here:

The blue dotted line represents step(fract(y*0.5),0.5). We can then divide the result of our step(), to create a probable offset.

Color <3

Right now it doesn’t look like much, but we have managed to get normalised coordinates for each brick tile, which means we can at least visualise our coordinates using our color variable.

Rows are going through the green channel: color = vec3(0.,norm_position.y,0.);

Bricks are going through the red channel: color = vec3(norm_position.x,0.,0.);

And together, they look divine:

However this is just a visualisation help, not anywhere near the real colors we need.

The tricky part here is that we don’t want our entire brick tile to be brick colored. We also want to apply the mortar color to the right coordinates.

And that’s where our BRICK_PCT comes in handy. Because it’s a pourcentage, a value between 0 and 1, just like our normalisse coordinates, we can use step() again. This time we will use BRICK_PCT as the threshold against wich we check norm_position:

    vec2 isBrick = step(norm_ppsotion, BRICK_PCT);

We can think of this as making an alpha mask, hence the name isBrick worded like a boolean. We can visualise this mask like this:

    color = vec3(isBrick.x * isBrick.y)

The reason we multiply isBrick.x by isBrick.y is to combine both into a single float value that we’ll be able to use as an alpha with the mix() function.

    color = mix(MORTAR_COLOR, BRICK_COLOR, isBrick.x * isBrick.y);