Wednesday, 18 January 2017

Procedural Generation Tutorial Brick Texture - Part 2 of 4

Procedural Generation Tutorial Brick Texture - Part 2 of 4



The end result of the full tutorial.


At the end of Part 2 we will have produced this (See image 1).
Image 1



Before you start you should know these things:

You should have gone through and completed ‘Part 1’ of this tutorial before going any further. If you find yourself not understanding some of the terminology or code of the tutorial I would also recommend going through the previous tutorial to get up to speed. I will leave a link to it below to go through at your leisure.



http://joseph-easter.blogspot.com/2016/11/procedural-generation-tutorial-brick.html



If you follow this tutorial and find I am moving too fast or if you don’t know the things in the list above, I would recommend getting up to speed and then come back to this tutorial when you are ready.

With this tutorial if you want to convert this into JavaScript by all means do so but it may be easier for you to follow this in C# and stick with the language of the tutorial to make it easier.

PROJECT RESOURCES: At the bottom of the post there is a download link to a zip file. This files includes all files needed to follow Part 2 and the completed project file of Part 2.



In this tutorial series, we will be creating a simple brick texture using code, a slight variation from the simple checker board pattern. We will learn some more programming principals.



We will:


  • Learn the basic theory of how to easily add mortar to our texture
  • Put theory into practice and add to our texture
  • Colour an entire texture easily
  • Layer different colours easily
  • Position the brick colour correctly
  • Dynamically change how thick the mortar is in the texture
  • Calculate the area to be covered using mortar and texture variables


Step 1: - Explaining the theory

In this part, we will focus on colouring the ‘LBrickTexture’. Before we start, we need to understand the theory of how we will do this. There are a few ways but this is the most efficient way I have found.

First off, we need two things, the colour of the brick and the mortar. Then we need to create another variable to say how thick we want the mortar to be (in pixels). After this we need to know how to colour the pixels with both the brick and mortar colour.

The most practical way to do this I have found is to colour the mortar first, treating it like a background colour because it goes around the brick. Then place the brick colour inside it but covering a smaller area of the texture (using the mortar variables) leaving only the top, bottom and left edges of the texture alone. See figure 1.0 for reference.

Figure 1.0

Step 2:- Adding the Mortar Colour

Before we do anything with the mortar variables we will add the mortar colour first as mentioned in step 1. This is quite quick and easy to do. First off we need to create a function called ‘CreateMortarColour’, this will colour the background of the texture which we will lay the brick colour over. Then we reference it in the ’Start’ method just after the ‘ConvertBricksToSquares’ method.

In this function, we will be using SetPIxels32 on ‘leftBrickTexture’ and colouring it using ‘colour0Arr’ which I have set to a grey colour. We will set it’s X and Y starting position to ‘0’ and ‘0’ and tell it cover the entire texture  using the textures width variable so we don’t have to hard code it. We do this using ‘leftBrickTexture.width’ and ‘leftBrickTexture.height’. Now we could continue like this but we will need to use this later for the right side of the brick without writing the same line again. Because we want our code to be as reusable as possible instead of specifying the texture, we could parse the ‘leftBrickTexture’ to our function and modify it based on that. We will call the parsed Texture2D ‘tex’. After doing this, parse ‘leftbrickTexture’ to the reference in the ‘Start’ function.

Code:


void Start()
{
   SetMainTextureSize();
   ConvertBricksToSquares(brickX, bricksY);
   CreateMortarColour(leftBrickTexture);
   ConvertColourToArray(blockWidth * blockHeight);
   CreatePattern();
}
...
void CreateMortarColour(Texture2D tex)
{
   tex.SetPixels32(0, 0, tex.width,
                         tex.height,
                         colour0Arr);
}


We have two more things to do in this step. If we run our code, we will get another ‘SetPixels32 failed’ error. This is because ‘colour0Arr’ doesn’t have enough elements to cover all the pixels of ‘leftBrickTexture’ in ‘CreateMortarColour’. This is quite an easy fix. In ‘CreateMortarColour’ just before we use ‘SetPixels’ we will reference ‘ConvertColourToArray’ and parse it ‘tex.width’ and ‘tex.height’ to get the area we need for the parsed texture. After we do this we need to apply the changes to the Texture2D variable. We do this by applying the changes using ‘tex.Apply()’ after we use ‘SetPixels32’. (See figure 1.1).

Figure 1.1

Code:


void Start()
{
   SetMainTextureSize();
   ConvertBricksToSquares(brickX, bricksY);
   CreateMortarColour(leftBrickTexture);
   ConvertColourToArray(blockWidth * blockHeight);
   CreatePattern();
}
...
void CreateMortarColour(Texture2D tex)
{
   ConvertColourToArray(tex.width * tex.height);
   tex.SetPixels32(0, 0, tex.width,
                         tex.height,
                         colour0Arr);
   tex.Apply();
}

Now if we want to see the results on a bigger scale we can temporally set this texture to our plain after applying the changes. We do this by using ‘GetComponent’ on the renderer and assigning this texture to the ‘mainTexture’ of the game object’s material. If you like you can copy and paste the line from the ‘CreatePattern’ function and change the assigned texture to ‘tex’ and comment out ‘CreatePattern’ for now in the ‘Start’ method. (See figure 1.2).

Code:


void Start()
{
   SetMainTextureSize();
   ConvertBricksToSquares(brickX, bricksY);
   CreateMortarColour(leftBrickTexture);
   ConvertColourToArray(blockWidth * blockHeight);
   //CreatePattern();
}
...
void CreateMortarColour(Texture2D tex)
{
   ConvertColourToArray(tex.width * tex.height);
   tex.SetPixels32(0, 0, tex.width,
                         tex.height,
                         colour0Arr);
   tex.Apply();
   GetComponent<Renderer>().material.mainTexture
                                          = tex;
}


Figure 1.2

When you have finished seeing the result comment out ‘the ‘GetComponent’ line in ‘CreateMortarColour’. In step three we will work out how dynamically change the brick size and calculate the variables.

Step 3: - Colouring in the brick


Now we have coloured in the mortar we need to colour the brick portion of the texture. As discussed in the previous steps we will fill in the brick colour by covering a smaller area of the texture, overlaying the mortar colour. The space not covered by the brick colour will leave the mortar colour visible. This sounds complicated but it is not.

First off, we need to create function which will fill in the brick colour, ‘CreateBrickColour’. Then just to get the function started we will use ‘SetPixels32’ on ‘leftBrickTexture’ setting ‘X’ and ‘Y’ to ‘0’. To get the effect described in the paragraph above we need a dynamic way to make it leave a little bit of the texture untouched. For the time being we will take away ‘1’ from the width and height variables (just to demonstrate the point, we will improve this in a bit). Then set the colour variable to ‘colour1Arr’, and apply the changes. (Make sure the ‘Colour1’ variable is set to a solid red. (See figure 1.3).

Code:


void Start()
{
   SetMainTextureSize();
   ConvertBricksToSquares(brickX, bricksY);
   CreateMortarColour(leftBrickTexture);
   CreateBrickColour();
   ConvertColourToArray(blockWidth * blockHeight);
   //CreatePattern();
}
...
void CreateBrickColour()
{
   leftBrickTexture.SetPixels32(0, 0,
                       leftBrickTexture.width - 1,
                       leftBrickTexture.height - 1,
                       colour1Arr);
   leftBrickTexture.Apply();
}


Figure 1.3


Step 4: - Calculating the covered area


Now this is ok, but what if we want to change the thickness of the mortar. We could change how much we take away from the texture width and height as it’s hard coded. This would be fiddly and we need to change it quickly and easily in the inspector, and as it’s procedural this would defeat the point. We should create a few variables which we can set as the area to cover and these variables will be calculated by another function. Create a new function called ‘CalculateBrickAndMortarSpacing’.

Next, we want to set the thickness of the mortar as an ‘int’, create two new public ‘int’s at the top called ‘mortarThicknessWidth’ and ‘mortarThicknessHeight’, set them to ‘1’ for now. Just under this we will create two private ‘int’s called ‘brickColourWidth’ and ‘brickColourHeight’ which are the width and height it will cover. (Why have two variables for mortar thickness? You don’t need to do this. I did it to make it more customizable in case a pattern needs more mortar on one axis without affecting everything else).

In the function, we will calculate the area using two equations, one for width and height. For now, we will assume that the area covered for the brick is the same on both sides (this will also make the equation easier for us as we won’t have to calculate it once). The first equation will work out the width to cover. We do this by assigning ‘brickColourWidth’ to ‘leftBrickTexture.width’ minus ‘mortarThicknessWidth’. Then below it we will do the same but with ‘brickColourHeight’, ‘leftBrickTexture.height’ and ‘mortarThicknessHeight’ but times two.

We multiply the mortar height by two because the value will subtract this from the top and bottom of the texture. If we don’t do this there will not be enough space for it and there needs to be at least equal space between the top and bottom.

Code:


public int mortarThicknessWidth = 1;
public int mortarThicknessHeight = 1;

private int brickColourWidth;
private int brickColourHeight;
...
void Start()
{
   SetMainTextureSize();
   ConvertBricksToSquares(brickX, bricksY);
   CreateMortarColour(leftBrickTexture);
   CalculateBrickAndMortarSpacing();
   CreateBrickColour();
   ConvertColourToArray(blockWidth * blockHeight);
   //CreatePattern();
}
...
void CalculateBrickAndMortarSpacing()
{
   brickColourWidth = leftBrickTexture.width -
                         mortarThicknessWidth;
   brickColourHeight = leftBrickTexture.height -
                    (mortarThicknessHeight * 2);
}


Great, now we need to add these variables into ‘CreateBrickColour’ replacing the old ones in ‘SetPixels32’. Then we need to change two little things, the starting positions for the ‘X’ and ‘Y’ axis. Set ‘X’ to ‘mortarThicknessWidth’ and ‘Y’ to ‘mortarThicknessHeight’. We already have these variables to hand so there’s no point in creating new ones for this purpose. It will position them away from the bottom left by the amount of the mortar thickness for us. See figure 1.4.

Code:


void CreateBrickColour()
{
   leftBrickTexture.SetPixels32(
                       mortarThicknessWidth,
                       mortarThicknessHeight,
                       brickColourWidth,
                       brickColourHeight,
                       colour1Arr);
   leftBrickTexture.Apply();
}


Figure 1.4





Great, part two is finished. We have laid down more foundations to the frame work.


We have learned how to:
  • Easily cover an entire textureusing SetPixels32
  • Easily layer colours for a pattern in a texture
  • Use simple mathematics with a texture's variables and custom variables
  • Dynamically cover part of a texture using said variables
  • Position the brick colour correctly using the mortar variable available


In part 3 we will learn how to create the right side of the brick using the skills we have, how to position the brick colour correctly and change the brick function to make it easier to use for both bricks.


2 comments:

  1. Part 1 was quite short because it involved setting things up. I would have preferred this one be a bit shorter but with the complexity of the subject it had to be this length. The first step is dedicated to explaining the theory and a summary of what is covered in the post. It explained a complex subject clearly and concisely. It has an illustration demonstrating what is meant. This is good because sometimes you need a visual representation to get the idea across. The post also highlights relevant parts of an images when it needs you to see something. E.g. when a variablehas changed in the inspector it needs to show you this in context. It shows the relevant component then it circles the changes variable or value. It also explains how the mortar colour is added to the image using a simple formula then the brick colour. I thought this was clever because I am sure there are more arduous routs for this.

    ReplyDelete
  2. I like how step 1 is dedicated to explaining the theory of what we cover in the entire post. It is like a summary and explains it in just enough detail to get a good idea of what will be covered and new ideas will be introduced. I will say, using the mortar as the background for the texture colour, then overlapping it with the brick colour is pretty smart. I can imagine there are more difficult ways to do it. Having adjustable mortar thickness variables is a nice touch, very customizable. Using the mortar size variable to decrease the area covered by the brick colour to show the mortar is a clever approach. I am glad that at the start the tutorial shows an image to illustrate the theory. This is a good enhancement. When the tutorial shows how a variable or value has changed (e.g. with the left brick texture) it shows the component in context but it circles what you are supposed to be looking it with a green box outline. This is a nice touch as it saves you wading through which variables to look at as there are a lot of them for this.

    ReplyDelete