Wednesday 5 April 2017

Procedural Generation Tutorial Basic Cell Pattern Texture - Part 2

Procedural Generation Tutorial Basic Cell Pattern Texture - Part 2


The end result of the full tutorial.

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


Before you start you should know these things:
  • Create Texture variables
  • Manipulate textures using SetPixels
  • Create a basic texture pattern
  • What noise is and hot to use it with a texture pattern

You should have gone through these tutorials before going further:

  • Checker Board Texture
  • Brick Pattern Texture
  • Brick Pattern Noise Texture


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.

Part 1:



http://joseph-easter.blogspot.com/2017/03/procedural-generation-tutorial-basic.html
Checker Board Pattern Texture http://joseph-easter.blogspot.nl/2016/12/procedural-generation-tutorial-checker.html

Brick Texture Tutorial http://joseph-easter.blogspot.co.uk/2017/01/procedural-generation-tutorial-brick.html

Brick Noise Texture Tutorial http://joseph-easter.blogspot.nl/2017/02/procedural-generation-tutorial-brick_8.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 adding to the brick pattern texture using code by adding noise to the texture image.


In this tutorial series, we will be creating a very simple pattern using a cellular texture using code. This tutorial will use and build upon what we have learnt from the previous texture tutorials, by creating two textures, one for the background, and one for the foreground, dividing the foreground into cells and adding an image into those cells then combining those textures. Later we will add variation to it by deciding whether to show a cell based on it noise value in the texture space, then using that noise to position it with in the cell itself using bombing.

We will:
  • Explain the theory
  • Divide a texture into cells
  • Create a tiled pattern using the cells
  • Make a texture have transparency
  • Adjust an images position in a texture's cell

Step 1: The theory

In this part, we will be creating the pattern of the texture, specifically a repeating tiled texture which we can use later to modify to make more interesting and less predictable patterns.

We will do this by, creating another texture variable with the same resolution of the background texture and divide it into cells of equal width and height. The we will store the image we want to use in a ‘Texture2D’ variable, get its pixel information and store it in a ‘Color32’ array. We will sort through the cells of the texture using a for loop to get their ‘X’ and ‘Y’ position. Then we will set the pixels of the cell in the top layer texture to those of the image we just obtained the pixel information we got.


Step 2: Creating the Pattern Texture and Dividing it into Cells

In this step, we will create the pattern for our texture. To do this we need to create a separate texture variable called ‘topLayerTexture’ of type ‘Texture2D’ and set its resolution to the same as ‘mainTexture’. We do this in the ‘SetMainTextureSize’ method, under where we set the resolution of ‘mainTexture’. We set it here using the same variables because it’s automatic and we will not need to adjust the resolution every time. While we are here we will rename ‘brickTexWidth’ and ‘brickTexHeight’ to ‘bW’ and ‘bH’. Then in the reference to ‘SetBlockSize’ change the variable we parse it to ‘bW’ and ‘bH’. This will automatically divide the texture up into cell spaces dependent on how many squares on the X and Y axis we need. It will also tell us how many pixels wide and high each cell is (assuming every cell has equal dimensions, which they will).


Code:


public Texture2D mainTexture;
public Texture2D combinedTexture;
public Texture2D topLayerTexture;
...
void SetMainTextureSize ()
{
   mainTexture = new Texture2D(mainTexWidth,
                               mainTexHeight);
   topLayerTexture = new Texture2D(
                              mainTexWidth,
                              mainTexHeight);
   int bW = mainTexWidth / squaresX;
   int bH = mainTexHeight / squaresY;
   SetBlockSize(bW, bH);
}

Step 3: Creating the Tilled Pattern

When we create our cellular pattern, it will be similar in structure to the ‘CreatePattern’ function from the Checkerboard and brick texture projects. In fact, because we won’t use as alternating pattern it will be more straight forward (unless you need that for your project of course). We will use this method and tweak it somewhat to save us some work since we have already done this in a previous project.

Uncomment the reference to ‘CreatePattern’ in the ‘Start’ method. Then in the code uncomment the function itself.



Code:



void Start ()
{
   SetMainTextureSize();
   CreateBackgroundColour(mainTexture);
   ConvertColourToArray(blockWidth * 
                        blockHeight);
   CreatePattern();
   //AddTexturesTogether();
}
...
void CreatePattern()
{
   ...
}

Now we will create the pattern and set it to ‘topLayerTexture’. We do not need to touch the nested ‘for’ loops since they are already doing their job cycling through the cells of the texture variable on the X and Y axis. What you need What we need to do here is copy and paste the line where we set the pixels to the ‘mainTexture’ and place it above and outside the ‘if else’ statement but still in the ‘for’ loop. After this delete the ‘if else’ statement (or comment out) the ‘if else’ statement inside the ‘for’ loop because we are not alternating between cells, like a checker board pattern (unless this is the effect you are after).

Then with the line of code you just copied, change ‘mainTexture’ to ‘topLayerTexture’ and change ‘colourArr0’ to ‘colourArr1’. After this change all the lines in this function where ‘mainTexture’ is referenced and change them to ‘topLayerTexture’ since this the texture we are modifying. We set our ‘topLayerTexture’ to the ‘mainTexture’ of the renderer for debugging purposes and more easily spot any problems.


Code:

void CreatePattern()
{
   for (int i = 0; i < squaresX; i++)
   {
      for (int j = 0; j < squaresY; j++)
      {
         topLayerTexture.SetPixels32(
                     i * blockWidth,
                     j * blockHeight,
                     blockWidth, blockHeight,
                     colour1);
      }
   }
   topLayerTexture.Apply();
   GetComponent<Renderer>().material.
             mainTexture = topLayerTexture;
   topLayerTexture.wrapMode = 
             TextureWrapMode.Clamp;
   topLayerTexture.filterMode = 

             FilterMode.Point;
}

When we run our code, this happens. (See image 1.1).


Image 1.1




This is because ‘colourArr1’ is set to the same colour as ‘colour1’ in ‘ConvertColourToArray’. ‘Colour1’ has not been set to anything yet and its default is completely black. Now this is not very interesting. We want to replace this with an image of our own.

Step 4: Setting the Tilled Pattern

We already have an image to use, it is in the ‘Art’ folder and called 'compact-disk (1)'. Select the image and make sure ‘Alpha is tranparency’ is set to true. At the top of our code, create a new public Texture2D variable called ‘imageTexture’. Then drag the PNG file into the variable in the inspector.

Code:


public Texture2D mainTexture;
public Texture2D combinedTexture;
public Texture2D topLayerTexture;
public Texture2D imageTexture;

Before we can set the pixels to anything we need to get the pixels of the texture, and store the colour information into a ‘Color32’ array. We create a new colour array called ‘imagePixels’ in the ‘CreatePattern’ method and we assign it the ‘imageTexture’ colour information for every pixel using ‘GetPixels32’. ‘GetPixels32’ resizes the array for us. After this in the ‘for’ loop where we set the pixels in ‘topLayerTexture’ change the colour array from ‘colourArr1’ to ‘imagePixels’.

Code:

void CreatePattern()
{
   Color32[] imagePixels = 
                 imageTexture.GetPixels32();
   for (int i = 0; i < squaresX; i++)
   {
      for (int j = 0; j < squaresY; j++)
      {
         topLayerTexture.SetPixels32(
                     i * blockWidth,
                     j * blockHeight,
                     blockWidth, blockHeight,
                     imagePixels);
      }
   }
   topLayerTexture.Apply();
   GetComponent<Renderer>().material.
            mainTexture = topLayerTexture;
   topLayerTexture.wrapMode = 
            TextureWrapMode.Clamp;
   topLayerTexture.filterMode = 

            FilterMode.Point;
}


This is good but we need to do one more thing. If we run our code we get an error telling us ‘SetPixels32 failed’ because the colour array we are using is not the right size to cover the area we want. In our case, we do not have enough texture space pixels but this can also apply to an area that is too large.

There is a simple fix for this, instead of telling it set an area as wide and high as the cell, tell is to cover an area that is the size of the ‘imageTexture’ itself which we want to set to the larger texture.

This is easy, we replace ‘blockWidth’ and ‘blockHeight’ where we tell it to set the pixel area with ‘imageTexture.width’ and ‘imageTexture.height’. We have just accessed the textures width and height properties here, information embedded in the date type, so we don’t have to change our code every time we use an image with different dimensions.

Code:

void CreatePattern()
{
   Color32[] imagePixels = 
                 imageTexture.GetPixels32();
   for (int i = 0; i < squaresX; i++)
   {
      for (int j = 0; j < squaresY; j++)
      {
         topLayerTexture.SetPixels32(
                     i * blockWidth,
                     j * blockHeight,
                     imageTexture.width, 
                     imageTexture.height,
                     imagePixels);
      }
   }
   ...
}


Image 1.2: Resolution 256, squaresX: 4, squaresY: 4.

We have one more problem to sort out now, the transparency.

Step 5: Fixing the Transparency

Now, even though the image file has the alpha channels set as transparency, the same is not the same for the ‘Texture2D’ we created. By default, the colour is set to white or gray, with an alpha of ‘255’. When you add two colour values together, their RGBA values (specifically Alpha) are added together. With ‘SetPixels32’ only the Alpha values are added together. Therefore, if you have an image with an alpha of 128, and you write an image to it with an alpha value of 127, the total alpha value will equal 255.

To fix this, just after we set the texture’s resolution we need to set its pixels to a colour with an alpha property of ‘zero’.

Rename ‘colour1’ to ‘transparentColour’. It can be set to any colour you like if the alpha value is set to ‘0’. Do the same for ‘colour1Arr’ rename it to ‘transparentColourArr’. Then in ‘ConvertColourToArray’ change all instances of ‘colour1Arr’ to ‘transparentColour’.

Code:

...
public Color32 transparentColour;
...
public Color32[] transparentColourArr;
...
void ConvertColourToArray(int arrSize)
{
   colour0Arr = new Color32[arrSize];
   transparentColour Arr = 
                     new Color32[arrSize];
   
   for (int i = 0; i < arrSize; i++)
   {
      colour0Arr[i] = colour0;
      transparentColourArr[i] = 
                     transparentColour;
   }
}

We need to do one more thing before we finish. We need to set the pixels of the texture to ‘transparentColour’ just after we create it and before we do anything else to it. This will let the transparency of 'compact-disk (1)' show through. Also, when we combine the textures it will allow the blue from the ‘mainTexture’ show through, just a heads up.

To set the pixels for the ‘topLayerTexture’ we need to create another function that is more general purpose but it similar to ‘CreateBackgroudColour’. We will do this by copy and pasting the function for now and commenting out the line that sets the parsed texture as the ‘mainTexture’ for the shader and changing the colour array from ‘colour0Arr’ to ‘transparantColourArr’. We will call the function ‘CreateForegroundColour’. Then reference this just after ‘CreateBackgroundColour’ in the start method, parsing in ‘topLayerImage’.

Code:

void Start ()
{
   SetMainTextureSize();
   CreateBackgroundColour(mainTexture);
   CreateBackgroundColour(topLayerTexture);
   ConvertColourToArray(blockWidth * 
                        blockHeight);
   CreatePattern();
   //AddTexturesTogether();
}
...
void CreateForegroundColour(Texture2D tex)
{
   ConvertColourToArray(tex.width * 
                        tex.height);
   tex.SetPixels32(0, 0, tex.width, 
                   tex.height, 
                   transparentColourArr);
   tex.Apply();
   GetComponent<Renderer>().material.
                           mainTexture = tex;
}


Enter Play Mode and see the result.


Image 1.3: Resolution 128. The result is the same because
 we need to do more to it in Part 3. It sets up the
 ground work for it though.

This has happened because we have the image covering the entire cell not just the centre, with the outer frame not being left for the background colour. There is a simple fix for this, double the texture’s resolution.

Image 1.3: Resolution 256. 
Hexadecimal value for colour used: 41CEFD00

Now this is OK but the texture is in the bottom left and is one block colour. For now, we will finish off Part 2 by adjusting the position of the image within the cell since this will make the pattern marginally prettier. It’s also quite quick to do and Part 2 is getting long.

Step 6: Fixing the position within the Cell

We need to understand how ‘SetPixels32’ works. It works by drawing a texture pixel by pixel. It starts at the bottom left and goes right until it reaches the end of the row, then it goes one column up and starts again until it’s finished. So, bottom left to top right. If we were to tell it to start in the middle of the cell, the image would be placed in the top right of it. This is not what we want.

To fix this we need to get the cells width and height, and then divide it. If we divide its width and height by two, this would be in the middle of the cell (as described in the last paragraph) and the image would be in the top right because it would start in the middle of the cell. What we need to do is go a step further and place in between the bottom left corner and the middle of the cell. The easiest way to do this is by dividing the width and height of the cell by four. We will do this in our ‘CreatePattern’ method at the top, our side of the ‘for’ loops. We will store these values in a temporary ‘int’ type called ‘middleOfCellWidth’ and ‘middleOfCellHeight’.

Code:

void CreatePattern()
{
   Color32[] imagePixels = 
                 imageTexture.GetPixels32();
   int middleOfCellWidth = blockWidth / 4;
   int middleOfCellHeight = blockHieght / 4;
   for (int i = 0; i < squaresX; i++)
   {
      for (int j = 0; j < squaresY; j++)
      {
         topLayerTexture.SetPixels32(
                     i * blockWidth,
                     j * blockHeight,
                     imageTexture.width
                     imageTexture.height,
                     imagePixels);
      }
   }
   ...
}

Now we just need to add these variables to the ‘X’ and ‘Y’ position when we use ‘SetPixels32’ of ‘i’ and ‘j’.

Code:

void CreatePattern()
{
   Color32[] imagePixels = 
                 imageTexture.GetPixels32();
   int middleOfCellWidth = blockWidth / 4;
   int middleOfCellHeight = blockHieght / 4;
   for (int i = 0; i < squaresX; i++)
   {
      for (int j = 0; j < squaresY; j++)
      {
         topLayerTexture.SetPixels32(
         i * blockWidth middleOfCellWidth,
         j * blockHeight middleOfCellHeight,
                     imageTexture.width
                     imageTexture.height,
                     imagePixels);
      }
   }
   ...
}

Image 1.5

Now the image is positioned correctly, in the centre of the cell, with the background framing it. Great stuff guys.

Great, part two is finished. We have laid down more foundations.

We have learnt how to:

  • Divide a texture into cells
  • Create a tiled pattern using the cells
  • Make a texture have transparency
  • Adjust an images position in a texture's cell

In Part 3 we will sort of the transparency of the top layer, letting the background colour show through it. We will do this by understanding alpha composition, and interpolating between the two colours using alpha channel values.




Go to Part 3, click here.

If you enjoyed this tutorial and would like me to add some extra content to it, like and share this tutorial on here and social media and leave a comment below. If you didn’t like this tutorial please leave a comment below saying why.

No comments:

Post a Comment