Wednesday 1 February 2017

Procedural Generation Tutorial Brick Texture - Part 4 of 4

Procedural Generation Tutorial Brick Texture - Part 4 of 4



The end result of the full tutorial.


At the end of Part 4 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 3’ 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_51.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 4 and the completed project file of Part 4.


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:
  • Recode the brick function creating the right-side brick
  • Create a switch statement
  • Tidy up code by putting left and right brick in separate functions

Step 1:- Theory


This is the final chapter in this tutorial series for the basic brick texture pattern. We will be adding the brick pattern to the texture and using the for loop from the checkerboard pattern texture.

We will start with some theory. We have the code to add squares to the main texture and alternate between two values. We also have two mini textures created via code with adjustable texture sizes and we can tell the texture how many bricks we want on both the X and Y axis and how thick we want the mortar to be between the bricks. Now we need to add the bricks to the texture when the for loop is alternating between the values.

What we need to do is get the pixels from the miniature texture we need (left or right brick) and copy them to the appropriate square on the ‘mainTexture’. The way we will do this is by creating a function to do this process. In the function, we will use ‘GetPixels32’ from the texture we need, store those pixels in a colour array then copy those pixels to the new location and texture using ‘SetPixels32’. We will do this for both if statement condition in the for loop.

Step 2:- Demonstrating SetPixels32

Before we start, there are a few things we need to tweak from last time. In ‘CreateMortarColour’ we need to comment out the line where we set the texture to ‘leftBrickTexture’ or ‘rightBrickTexture’. We only added it to check the mini textures at the time, if it’s uncommented, comment it out. Another thing we need to do is uncomment ‘CreatePattern’ in the ‘Start’ method otherwise this will not work at all.

Code:

void Start()
{
   ...
   CreatePattern();
}

void CreateMortarColour(Texture2D tex)
{
   ...
   //GetComponent<Renderer>().material.
                           mainTexture = tex;
}

Now, in ‘CreatePattern’ write a reference to a new function called ‘DrawHalfBrick’ in the first if statement just above where we use ‘SetPixels32’ on ‘mainTexture’. Then under ‘RightBrickColour’ write a new function called ‘DrawHalfBrick’.

Code:

void DrawHalfBrick()
{
   
}
...
void CreatePattern()
{
   ...
   if (((i + j) % 2) == 1)
   {
      DrawHalfBrick();
      mainTexture.SetPixels32(i * blockWidth, j 
                      * blockHeight, blockWidth,
                      blockHeight, colour0Arr);
   }
   else
   {
      DrawHalfBrick();
      mainTexture.SetPixels32(i * blockWidth, j 
                      * blockHeight, blockWidth,
                      blockHeight, colour1Arr);
   }
   ...
}

Right next up we need to get the pixels we are copying from one of the miniature textures. We do this using a function called ‘GetPixels32’. ‘GetPixels32’ works by assigning pixels from a texture and assigns each pixel colour to an element in a colour32 array. It works in a similar way to ‘SetPixels32’ and they work with each other as they both start from the bottom left pixel to the top right one of the texture.

To demonstrate how this works, at the top create a public color32 array called ‘tempBrickPixels’, then in ‘DrawHalfBrick’ assign ‘leftBrickTexture.GetPixels32()’ to ‘tempBrickPixels’.

Code:

public Color32 [] tempBrickPixels;
...
void DrawHalfBrick()
{
   tempBrickPixels =
                leftBrickTexture.GetPixels32();
}

Let Unity compile, open the new array in the inspector and click ‘Play’. (See figure 1.0).

Figure 1.0

Note: You can do more with ‘GetPixels32’ such as telling it where to start on the X and Y axis and the width and height to cover. I left that out for now as we don’t need it.

Step 3:- Making the function more reusable

Now we have a simple understanding of how ‘GetPixels32’ works we can more easily use it with our code and make our function more reusable.

Where we have declared our other variables, delete where we have declared ‘tempBrickPixels’, then in ‘DrawHalfBrick’ declare it as a temporary ‘Color32’ array.

Code:

void DrawHalfBrick()
{
   Color32 [] tempBrickPixels =
             leftBrickTexture.GetPixels32();
}

Now this is good but we need to do more to it. We want to be able to not only get the pixels from the ‘leftBrickTexture’ but also from the ‘rightBrickTexture’ when we need to. We could use an if or switch statement but if we add more types of bricks later our code will get very difficult to read. There is another way to do it though. We can parse the function a ‘Texture2D’ variable and use ‘GetPixels32’ in that instead, replacing ‘leftBrickTexture’. In the brackets of the function (parentheses) add a ‘Texture2D’ variable called ‘fromTexture’. Then replace ‘leftBrickTexture’ with ‘tempTexture’ but keep the ‘GetPixels32’ bit. After this parse ‘leftBrickTexture’ where we reference the function in ‘CreatePattern’ in the first if condition.

Code:

void DrawHalfBrick(Texture2D fromTexture)
{
   Color32 [] tempBrickPixels =
             fromTexture.GetPixels32();
}
...
void CreatePattern()
{
   ...
   if (((i + j) % 2) == 1)
   {
      DrawHalfBrick(leftBrickTexture);
      //mainTexture.SetPixels32(
                    i * blockWidth,
                    j * blockHeight,
                    blockWidth, blockHeight,
                    colour1Arr);
   }
   ...
}

Step 4:- Setting the pixels to the main texture

Now we have the pixels at hand we need to do another thing, we need to paste or set those new pixels on the main texture. This will be quite straight forward. We copy the code we commented out in the last step from the ‘CreatePattern’ function when we use ‘SetPixels32’ on ‘mainTexture’ and we paste it into ‘DrawBrick’ under the first line.

Code:

void DrawHalfBrick(Texture2D fromTexture)
{
   Color32 [] tempBrickPixels =
             fromTexture.GetPixels32();
   mainTexture.SetPixels32(i * blockWidth,
                  j * blockWidth, blockWidth,
                  blockHeight, colour1Arr);
}

If we were to save, and let the code interoperate and run we would get multiple errors. We need to change a few obvious things. We should change the function so that we can parse it a destination texture. You might be thinking because we only have one main texture currently why should we bother? Well in the future you may want to have multiple main textures for different purposes, and we want our code to be as generic as possible.

The next thing to do is replace ‘mainTexture’ with ‘toTexture’ then in the brackets just after ‘fromTexture’ add a comma and add another parse option, ‘Texture2D’ followed by ‘toTexture’. Then in ‘CreatePattern’ parse ‘mainTexture’ after the first parse parameter.

Code:

void DrawHalfBrick(Texture2D fromTexture,
                   Texture2D toTexture)
{
   Color32 [] tempBrickPixels =
             fromTexture.GetPixels32();
   toTexture.SetPixels32(i * blockWidth,
                  j * blockWidth, blockWidth,
                  blockHeight, colour1Arr);
}
...
void CreatePattern()
{
   ...
   if (((i + j)))
   {
      DrawHalfBrick(leftBrickTexture,
                    mainTexture);
      //mainTexture.SetPixels(i * blockWidth,
                              j * blockHeight,
                              blockWidth,
                              blockHeight,
                              colour0Arr);
   }
   ...
}


Next, we need to parse a few more variables to our function. We need to parse the ‘i’ and ‘j’ values that ‘SetPIxels32’ uses in this line. This is because ‘i’ and ‘j’ are out of context and variables that have no value in this function. In the for loop they are temporary variables that only exist in that function. This is a very simple fix, we add two more parse variable parameters to the function, ‘i’ and ‘j’ both as integers.

Code:

void DrawHalfBrick(Texture2D fromTexture,
                   Texture2D toTexture,
                   int i, int j)
{
   Color32 [] tempBrickPixels =
             fromTexture.GetPixels32();
   toTexture.SetPixels32(i * blockWidth,
                  j * blockWidth, blockWidth,
                  blockHeight, colour1Arr);
}
...
void CreatePattern()
{
   ...
   if (((i + j)))
   {
      DrawHalfBrick(leftBrickTexture,
                    mainTexture, i, j);
      //mainTexture.SetPixels(i * blockWidth,
                              j * blockHeight,
                              blockWidth,
                              blockHeight,
                              colour0Arr);
   }
   ...
}

We need to tweak one more thing in this line. At the end of ‘SetPixels32’ we state which colour array to colour the pixels with. Currently we are using ‘colourArr0’ for the ‘leftBrickTexture’. This is not useful to us here because we do not need this array, we want to colour it using the ‘leftBrickTexture’ which we have temporarily stored in ‘tempBrickPixels’. Replace ‘colourArr0’ with ‘tempBrickPixels’.

Code:

void DrawHalfBrick(Texture2D fromTexture,
                   Texture2D toTexture,
                   int i, int j)
{
   Color32 [] tempBrickPixels =
             fromTexture.GetPixels32();
   toTexture.SetPixels32(i * blockWidth,
                  j * blockWidth, blockWidth,
                  blockHeight, tempBrickPixels);
}

Now if we save, and run our code we get this. (See figure 1.1).

Figure 1.1: I set the texture size to 16 by 16, blockWidth 
and blockHeight to 4 and bricksX to 2, and bricksY to 4.

The last thing is to do this for the right half of the brick. In ‘CreatePattern’ in the else case comment out the ‘SetPixels32’ line, then parse ‘rightBrickTexture’ to ‘DrawHalfBrick’ and the other relevant variables.

Code:

void CreatePattern()
{
   ...
   if (((i + j)))
   {
      DrawHalfBrick(leftBrickTexture,
                    mainTexture, i, j);
      //mainTexture.SetPixels(i * blockWidth,
                              j * blockHeight,
                              blockWidth,
                              blockHeight,
                              colour0Arr);
   }
   else
   {
      DrawHalfBrick(rightBrickTexture,
                    mainTexture, i, j);
      //mainTexture.SetPixels(i * blockWidth,
                              j * blockHeight,
                              blockWidth,
                              blockHeight,
                              colour1Arr);
   }
   ...
}

Now enter ‘Play’ mode. (See figure 1.2).


Figure 1.2


This is good, but if we change any of the variables, such as the texture or number of bricks the texture will generate undesirable results. We will fix these problems in ‘Step 5’.

Step 5:- Fixing a few bugs

Our first bug appears when we change the texture to anything but 16 by 16 or the number of bricks is anything but 2 by 4. The error is related to ‘SetPixels32’ and is being called multiple times because there are not enough pixels either in the colour array or enough pixels in the texture to colour in the first place. (See figure 1.2.5).


Figure 1.2.5


This is because the ‘blockWidth’ and ‘blockHeight’ variables are staying constant regardless of texture size. Because a function (ConvertColourToArray) and several variables, specifically in ‘SetPixels (I * blockWidth, j * blockHeight)’ do not adjust and give the wrong value. Therefore, ‘SetPixels32’ is not being given a high enough value, even when multiplied.

This is quite an easy thing to fix. We need to create another function called ‘SetBlockSize’ and reference it at the bottom of ‘SetMainTextureSize’. It will take two integers ‘w’ and ‘h’. When referencing the function in ‘setMainTextureSize’ parse it ‘brickTexWidth’ and ‘brickTexHeight’ as because these are the width and height of the ‘leftBrickTexture’ which is what we want the ‘blockWidth’ and ‘blockHeight’ variables to be.

Then in ‘SetBlockSize’ set ‘blockWidth’ to ‘x’ and ‘blockHeight’ to ‘y’. (See figure 1.3).

Code:

void SetMainTextureSize()
{
   ...
   SetBlockSize(brickTexWidth,
                brickTexHeight);
}

void SetBlockSize(int w, int h)
{
   blockWidth = w;
   blockHeight = h;
}

Then we need to do one last thing. This code nearly works but the blockWidth and blockHeight value are still wrong. If you do some simple maths using a calculator the value of the block variables only equals the texture width divided by four, we need a smaller value than that and the maths is wrong. This is because thanks to the codes execution order and how fast the system runs, ‘ConvertBricksToSquares’ isn’t returning its values quickly enough even though it’s a simple multiplication. The best thing to do here is comment out the function in the ‘Start’ method, then copy and paste it into ‘SetMainTextureSize’ before the variable will be used. This will work because ‘SetMainTexture’ will pause until ‘SetBlockSize’ has done its job.

Code:

void Start()
{
   ...
   //ConvertBricksToSquares(bricksX, bricksY);
   ...
}...
void SetMainTextureSize()
{
   mainTexture = new Texture2D(mainTexWidth,
                               mainTexHeight);
   ConvertBricksToSquares(bricksX, brickY);
   ...
}


Figure 1.4: (L) tex resolution 128 x 128, bricksX 8, bricksY 16, mortar thickness width and height 2. (R) tex resolution 64 x 64, bricksX 2, bricksY 4, mortar thickness width and height 1.





Great, part four is finished and the tutorial is complete.

We have learnt how to:
  • Write and use a switch statement
  • Perform a task based on a given switch statement case

I hope you enjoyed this tutorial and much as I enjoyed making it. If you like it and want more follow my blog and leave a comment below saying what you liked. If you didn’t like it or think it’s needs improving leave a comment below saying what and why.

Keep an eye on my blog as I have more coming up soon, including a follow up to this tutorial involving noise textures.

No comments:

Post a Comment