Wednesday 12 April 2017

Procedural Generation Tutorial Basic Cell Pattern Texture - Part 3

Procedural Generation Tutorial Basic Cell Pattern Texture - Part 3



The end result of the full tutorial.

At the end of Part 3 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 2:


http://joseph-easter.blogspot.com/2017/04/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 3 and the completed project file of Part 3.

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
  • Understand alpha composition
  • How to use alpha composition when combining two textures
  • Use 'Color32.Lerp' interpolation of achieve this effect
  • Normalise the alpha value correctly with 32 bit variables
  • Understand antialiasing a little bit to remove jaggys


Step 1: The theory

In this part, we will work on combining the background and top layer textures, and do it such a way that the background colour shows through the top layer’s transparent pixels.

There are multiple ways to do this using various alpha chancel algorithms. Although form my experience, this work best with Unity’s ‘Color’ type and not as well with ‘Color32’ (they do work but you must add more code).

We will do this by, creating two temporary ‘Color32’ arrays, one for the background texture and one for the top layer texture, and store the texture’s pixel colour information in them. After this we will create a temporary blank colour array called ‘combinedColours’ and set it to the same length and the other two arrays. The next step is to loop through this array using a ‘for’ loop. Inside the for loop we create a temporary ‘Color32’ and called it ‘main’ for the background texture. We will assign this a pixel from the background texture colour array and index it using the iterator in the ‘for’ loop. We will do the same for the top layer texture temporary colour array.

After doing this we will assign the current element from ‘combinedColours’ a new colour. We will use Color32.Lerp to interpolate between the current pixel for the background and top layer arrays. The value we will use for this is a normalized version of the alpha from the top layer because we want to decide how much of the background is visible based on the transparency of the top layer pixels.

Then after doing this we will create a new texture which we will use to place these new pixel colours. It will cleanly show the results of combining both textures using alpha compositing to perform a similar function to a ‘Multiply’ or ‘Overlay’ operation.

Step 2: Creating the Combined Texture function & The Combined Texture

In this step, we will need to do two things, create a new ‘Texture2D’ variable for the combined texture and modify the ‘AddNoiseToTexture’ method. You might be thinking. “Why not directly add the top layer to the background texture?”. You could do this if you wanted to, I prefer to keep everything as separate textures so I can concentrate on one thing at a time, and if something goes wrong with one texture, it’s isolated there.

First thing, create a new Texture2D variable called “combinedTexture’ if you have not already done so. Then in ‘Start’ uncomment the ‘AddNoiseToTexture’ reference. Then uncomment the entire function itself.


Code:



public Texture2D combinedTexture;
...
void Start ()
{
   SetMainTextureSize();
   CreateBackgroundColour(mainTexture);
   ConvertColourToArray(blockWidth * 
                        blockHeight);
   CreatePattern();
   AddNoiseToTexture();
}
...
void AddNoiseToTexture()
{
   ...
}

Then take the code out of the ‘if’ statement, and paste it outside of it. Then delete the ‘if’ statement. After this you should remove the reference to generate the noise texture when we access the function ‘GenerateNoiseTexture” form ‘noiseScr’ and remove the ‘tempNoiseTex’ variable we store it in. These are not needed any more and help keep our code tidy and concise. If this a little confusing look at the code below.

Code: 

void AddNoiseToTexture()
{
   Color32[] tempMainPixels = 
                   mainTexture.GetPixels32();
   Color32[] tempPixels = 
                   tempNoiseTex.GetPixels32();
   
   for (int i = 0; i < tempMainPixels.Length;
                                          i++)
   {
      tempMainPixels[i].r += tempPixels[i].r;
      tempMainPixels[i].g += tempPixels[i].g;
      tempMainPixels[i].b += tempPixels[i].b;
      tempMainPixels[i].a += tempPixels[i].a;
   }
   mainTexture.SetPixels32(0, 0, mainTexWidth,
              mainTexHeight, tempMainPixels);
   mainTexture.Apply();
}

After doing this we get an error, because ‘tempNoiseTex’ no longer exists. Well we are not generating a noise texture so this is fine. We need to get this to reference the ‘topLayerTexture’ so change ‘tempNoiseTex’ to ‘topLayerTexture’ and change the name of the variable to ‘tempTopLayerPixels’.

Code: 

void AddNoiseToTexture()
{
   Color32[] tempMainPixels = 
                   mainTexture.GetPixels32();
   Color32[] tempTopLayerPixels
               topLayerTexture.GetPixels32();
   
   for (int i = 0; i < tempMainPixels.Length;
                                          i++)
   {
      tempMainPixels[i].r += tempPixels[i].r;
      tempMainPixels[i].g += tempPixels[i].g;
      tempMainPixels[i].b += tempPixels[i].b;
      tempMainPixels[i].a += tempPixels[i].a;
   }
   mainTexture.SetPixels32(0, 0, mainTexWidth,
              mainTexHeight, tempMainPixels);
   mainTexture.Apply();
}

Change the name of the method and the respective reference to it in ‘Start’ to ‘AddTexturesTogether’ if you have not already done so. This makes more sense now since this is the functions purpose.

Code: 

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

Step 3: Combining the textures together using Interpolation aka Alpha Composition theory

Currently with our combined texture all the pixels have full alpha values, meaning they are opaque with bit values of 255. The pixels have been drawn with a transparent colour. The image is also drawn into the ‘topLayerTexture’ and regardless of the pixel’s transparency the alpha values will add up increasing the alpha value making it less translucent. In other words, the background colour is enable to show through the translucent pixels of the ‘topLayerTexture’ pattern because the image pattern is being drawn later and is given priority and is on top.

We can use several different algorithms and formulas to fix this alpha composition problem. Although these mostly work with ‘Color’ and rely on multiplying the background colour and dividing the top layer alpha colour to interpolate the alpha values. This works well with ‘Color’ but ‘Colour32’ works a little differently using 32 bit values. If we were to add two colours together we couldn’t just add to at will, you need to add together the RGBA bit value separately. Using the formula this way would very complex. The effect we want to achieve wouldn’t be up to scratch without sever modification getting the edge colours to be the right colour and they would have a different alpha value. Plus, the code would not be that reusable. It would take too long and cause a headache.

Step 4: Putting theory into practice with Color32 Interpolation

There is another, simpler approach though. We can cycle through every pixel in the ‘combinedTexture’ with a ‘for’ loop. Then in the loop we create a new temporary ‘Color32’ variable and we assign it a new ‘Color32’. We will name this ‘tempCombinedColour’. This new Color32 will interpolate between the background colour elements and the ‘topLayer’ colour elements. For this we will use ‘Color32.Lerp’. With this we do not need to mess about with affecting the RGBA values individually as this is taken care of for us with ‘Color32.Lerp’.

For the ‘t’ used in the ‘Color32.Lerp’ function we will use the alpha value of the ‘topLayerTexture’ elements. In other words, when the alpha of ‘topLayerTexture’ element is low, the background texture will show through appropriately, and when the alpha is high the top layer will be more visible. Anything in between will be translucent to some degree.

Code: 

void AddNoiseToTexture()
{
   Color32[] tempMainPixels = 
                   mainTexture.GetPixels32();
   Color32[] tempTopLayerPixels = 
               topLayerTexture.GetPixels32();
   
   for (int i = 0; i < tempMainPixels.Length;
                                          i++)
   {
      Color32 tempCombinedColor = 
                   Color32.Lerp(
                   tempMainPixels[i],
                   tempTopLayerPixels[i],
                   tempTopLayerPixels[i].a);
   }
   mainTexture.SetPixels32(0, 0, mainTexWidth,
              mainTexHeight, tempMainPixels);
   mainTexture.Apply();
}


Image 1.1




As you can see, the image is still the same. That is because we forgot something. Alpha values use 32 bit values that go from 0 to 255. Interpolations function such as ‘Lerp’ use floating point values ranging between 0 to 1. Because anything larger than ‘0’ clearly exceeds this threshold everything is still opaque. We need to normalise the value to make it work with our function.

This is quite straight forward. We create a float called ‘normaliser’ in the ‘for’ loop above the ‘Color32’ interpolation. Then we assign it the values of a simple formula. This formula is like working out the percentage left of something. We want to know how much of the current alpha is of the maximum alpha. So if it’s one tenth of the maximum alpha available the values returned will be ‘0.1’. This fits in nicely with our interpolation method. We work this out by getting the alpha value of the current pixel element and dividing it by the maximum alpha value possible which is 255. Then we replace 'tempTopLayer[i].a' with 'normalizer'.

Code: 

void AddNoiseToTexture()
{
   Color32[] tempMainPixels = 
                   mainTexture.GetPixels32();
   Color32[] tempTopLayerPixels = 
               topLayerTexture.GetPixels32();
   
   for (int i = 0; i < tempMainPixels.Length;
                                          i++)
   {
      float normalizer = 
              tempTopLayerPixels[i].a / 255;
      Color32 tempCombinedColor = 
                   Color32.Lerp(
                   tempMainPixels[i],
                   tempTopLayerPixels[i],
                   normalizer);
   }
   mainTexture.SetPixels32(0, 0, mainTexWidth,
              mainTexHeight, tempMainPixels);
   mainTexture.Apply();
}

After doing this if you have not already done so, create a new Color32 array outside of the ‘for’ loop called ‘combinedTexturePixels’ and make it the same length and the other two arrays. Then below where we interpolate the colours in the ‘for’ loop, assign the current element of the ‘combinedTexturePixels’ array ‘tempCombinedColour’.

Code: 

void AddNoiseToTexture()
{
   Color32[] tempMainPixels = 
                   mainTexture.GetPixels32();
   Color32[] tempTopLayerPixels = 
               topLayerTexture.GetPixels32();
   Color32[] combinedTexturePixels = 
          new Color32[tempMainPixels.Length];
   
   for (int i = 0; i < tempMainPixels.Length;
                                          i++)
   {
      float normalizer = 
              tempTopLayerPixels[i].a / 255;
      Color32 tempCombinedColor = 
                   Color32.Lerp(
                   tempMainPixels[i],
                   tempTopLayerPixels[i],
                   normalizer);
      combinedTexturePixels[i] =
                   tempCombinedColor;
   }
   mainTexture.SetPixels32(0, 0, mainTexWidth,
              mainTexHeight, tempMainPixels);
   mainTexture.Apply();
}

While we are at it change all the remaining references from ‘mainTexture’ to ‘combinedTexture’ in the function and assign ‘combinedTexture’ as our ‘mainTexture’ for the material in the function so we can more easily see it. Also in ‘SetPixels32’ change ‘tempMainPixels’ to ‘combinedTexturePixels’.

Code: 

void AddNoiseToTexture()
{
   Color32[] tempMainPixels = 
                   mainTexture.GetPixels32();
   Color32[] tempTopLayerPixels = 
               topLayerTexture.GetPixels32();
   Color32[] combinedTexturePixels = 
          new Color32[tempMainPixels.Length];
   
   for (int i = 0; i < tempMainPixels.Length;
                                          i++)
   {
      float normalizer = 
              tempTopLayerPixels[i].a / 255;
      Color32 tempCombinedColor = 
                   Color32.Lerp(
                   tempMainPixels[i],
                   tempTopLayerPixels[i],
                   normalizer);
      combinedTexturePixels[i] =
                   tempCombinedColor;
   }
   combinedTexturePixels.SetPixels32(0, 0, 
              mainTexWidth,
              mainTexHeight, 
              combinedTexturePixels);
   combinedTexturePixels.Apply();
   GetComponent<Renderer>().material.
        mainTexture = combinedTexturePixels;
}

This is good, but if we run our code we will hit an error ‘UnassignedReferenceException’. We forgot to create the create the texture the variable is meant to hold. This is a very simple fix, we will set it to the same resolution as the other textures in ‘SetMainTextureSize’.

Code: 

...
void SetMainTextureize()
{
   mainTexture = new Texture2D(
                             mainTexWidth,
                             mainTexHeight);
   topLayerTexture = new Texture2D(
                             mainTexWidth,
                             mainTexHeight);
   combinedTexture = new Texture2D(
                             mainTexWidth,
                             mainTexHeight);
   ...
}



When we run our code, we get this.




Image 1.2


This is OK we have translucency, but we have a few jaggeys around the edges (the staircase effect pixels create when not anti-aliased). This is because the alpha of the current element is a byte implicitly converted to an integer matching ‘255’. Then both integers are implicitly converted to float values. This means the values are converted into flat values such as 1, 2 and 3, and there is a more noticeable jump between low and high alpha values. If all values were ‘float’ values, we would not notice this since the jump between the values would be so small it would not be noticeable.

In ‘Step 4’ we will convert everything in this line to float type variables and explicitly convert the byte value to a float value.

Step 5: Refining the jaggys with psudo-antialaising

We will explicitly cast ‘tempTopLayerPixels’ as a ‘float’ by putting the word ‘float’ in front of it (or pre-fixing it) and butting the work into brackets ‘(‘ and ‘)’. Then we will suffix 255 with an ‘f’ telling the compiler we it’s a ‘float’.

Code: 

void AddNoiseToTexture()
{
   ...
   for (int i = 0; i < tempMainPixels.Length;
                                          i++)
   {
      float normalizer = 
        (float)tempTopLayerPixels[i].a / 255f;
   ...
   }
   ...
}

Image 1.3: No jaggys.

See, now we have no jaggys and our texture looks clean. We have successfully combined two textures and can show a pattern with transparency.


Image 1.4





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

We have learnt how to:
  • Understand alpha composition
  • How to use alpha composition when combining two textures
  • Use 'Color32.Lerp' interpolation of achieve this effect
  • Normalise the alpha value correctly with 32 bit variables
  • Understand antialiasing a little bit to remove jaggys

In Part 4 we will use our 'Noise' script to get a noise value form our quad's position in world space. We will use this value to decide weather we show the image in the cell or not based on the cell's position coordinates in the texture. This will add some variety to our texture and make the pattern less predictable.




Go to Part 4, 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