Wednesday 8 February 2017

Procedural Generation Tutorial Brick Noise Texture - Part 1

Procedural Generation Tutorial Brick Noise Texture - Part 1


Before you start you should know these things:
  • Create Texture variables
  • Manipulate textures using SetPixels

You should have gone through and completed all the ‘Checker Board Texture’ and 'Brick Pattern Texture' tutorials 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.

Brick Texture Tutorial http://joseph-easter.blogspot.co.uk/2017/01/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 1 and the completed project file of Part 1.

In this tutorial series, we will be adding to the brick pattern texture using code by adding noise to the texture image.


We will:
  • Explain the theory
  • Learn how to use 'Random.Value' to create a noise texture
  • Learn how to set a seed value so it's slightly configurable and stays on the same pattern
  • Understand the basics of Unity's random function
  • Combine two textures through code with Color32
  • How Color32 and Color differ and their specific use cases



Step 1: The theory


In the last project we created a simple brick pattern texture. This is OK but we could make it more interesting a variety of ways. Different brick colours, varying the pattern, type of brick, or adding noise to the texture. In this series we will concentrate on adding noise.


Noise is a type of interference (or a way of obscuring something), like when an FM radio is not tuned in correctly and there is additional sound not part of the broadcast. When we see a brick wall, normally they are not smooth, there are cracks and irregularities in the brick work thanks to the clay and contrite. We can use noise to recreate these irregularities as best we can. Noise can also be used to create textures such as corrosion, sand and dirt.

If we wanted to create a predictable shape or pattern we could use a Sine wave from a mathematical formula, but this will not do for natural textures.

Instead we could use ‘Random.value’. This is quite a simple approach and a rookie’s way of doing it, and there are drawbacks with it. However, it has a few uses and it will provide a little grounding for ‘Perlin’ noise later.

Step 2: How does 'Random.Value' work? And Setup

‘Random.value’ is one of Unity’s methods to create random values. This works by using a function to create a number by calculating a few of it’s previously calculated numbers to find out the new value. It does this is a manner in which a pattern can’t be seen easily.

To see this in action open your project, create a new script called ‘Noise’, duplicate the ‘BrickPattern’ game object (disable the old one), name the new one ‘BrickPatternNoise’ and attach the script to it, then open it in your code editor.

When your code editor has opened we will bootstrap some of the code from ‘CheckerBoardPatternTexture’ to speed things up. We will change ‘mainTexture’ to ‘noiseTexture’ so we don’t get confused when combining textures and we will change ‘mainTexWidth’ to ‘resolutionW’ and ‘mainTexHeight’ to ‘resolutionH’ for the same purpose. In the for loops for ‘CreateNoisePattern’ we will replace ‘i’ with ‘w’ and ‘j’ with ‘h’ and ‘squaresX’ with ‘resolutionW’ and ‘squaresY’ with ‘resolutionH’.

Code:


public Texture2D noiseTexture;

public int resolutionW = 16;
public int resolutionH = 16;

void Start()
{
   SetNoiseTextureSize();
   CreateNoisePattern();
}

void SetNoiseTextureSize()
{
   noiseTexture = new Texture2D(resolutionW
                   resolutionH);
   GetComponent<Renderer>().material.
                   mainTexture = noiseTexture;
   noiseTexture.wrapMode = 
                   TextureWrapMode;
   noiseTexture.filterMode = 
                   FilterMode.Point;
}

void CreateNoisePattern()
{
   for (int w = 0; w < resolutionW; w++)
   {
      for (int h = 0; h < resolutionH; h++)
      {
         if ()
         {
           mainTex.SetPixels32(i * blockWidth,
                j * blockHeight, blockWidth,
                blockHeight, colour0);
         }
         else
         {
           mainTex.SetPixels32(i * blockWidth,
                j * blockHeight, blockWidth,
                blockHeight, colour1);
         }
      }
   }
   noiseTexture.Apply();
}

We need to change a few more things, we will remove both if statements in the for loop and replace them with one ‘SetPixel’ statement. We will use set pixel on ‘noiseTexture’ to change each pixel one at a time, using ‘w’ and ‘h’ to set the pixel location. Then set the colour of the pixel to a random shade of gray using ‘Color.grey * Random.value’ (Random.value controls the intensity of the colour). (You might be wondering ‘why use grey and not white?’. I tried with white when combining textures and you can’t see the brick pattern, defeating the object).

Code:

...
void CreateNoisePattern()
{
   for (int w = 0; w < resolutionW; w++)
   {
      for (int h = 0; h < resolutionH; h++)
      {
         noiseTexture.SetPixel(w, h, 
                 Color.grey * Random.Value);
      }
   }
   noiseTexture.Apply();
}

Now, let this compile and disable the ‘CheckerBoardPatternTexture’ script for a moment, then run the code.




First renders of the random noise texture. From Left to right: resolution 16 render 1, 
resolution 16 render 2, resolution 256 render 1, resolution 256 render 2.

Can you see something in each render? The texture changes every time the texture is modified because it continues from the value the random generator left when finishing the last texture. This is ok in some cases depending on your needs and it looks very random. Although in a lot of cases you will need more control over the texture pattern and you may want to keep a particular noise texture. For this we need to add pseudorandom noise, where the results will stay the same based on a value of set of parameters.

As a quick fix we could use a seed value as a starting value we have control over before we do anything with the texture. We add ‘Random.seed’ before the for loop.

Code:

...
void CreateNoisePattern()
{
   Random.seed = 29;
   for (int w = 0; w < resolutionW; w++)
   {
      for (int h = 0; h < resolutionH; h++)
      {
         noiseTexture.SetPixel(w, h, 
                 Color.grey * Random.Value);
      }
   }
   noiseTexture.Apply();
}




From left to right: resolution 16, 32, 64, 128, 256.

Now the texture will stay the same unless we change the seed or the texture size, in which case we get a continuation of the same sequence. Or to put it another way, the value of the pixel and the pattern sequence will depend on the order of the pixels when they are added to the texture.

You may have noticed as the resolution changes the left most part of the first row is identical as the resolution increases. This in part is down to how ‘SetPixel’ works from the bottom left to top right. As the row on the bottom get’s longer from the resolution increasing the original pattern takes up less space and continues on the row. This appears to give a form of faux-variation.

Next we will work on combining the two textures though code.

Step 3: Combining the Textures - Theory

What we have is good, but we want to combine these textures together to give the brick pattern more texture and make it look a little more real. Having one or the other will not do the job. We could just use Unity’s new material shader by hand but since the textures are generated at run time this is not possible. We could add them via code at runtime to the shader variables but this could get complicated.

What we can do though is temporarily get and store the colour of each pixel into a colour32 array for each texture. Then we create a for loop which cycles through every element in the colour array. When we reference and element in each array using the iterator we will reference the ‘rgba’ values separately and add them together. E.g. ‘colour[i].r += colour1[i].r’. We do this because each colour stores it’s ‘rbga’ information as an array of bytes which can hold a maximum value of 255 bytes each. So, in the for loop we need to add the ‘r’ byte value of the noise texture to the brick texture colour array using an incremental operator ‘+=’. Then we do this for the remaining colour byte values ‘g’, ‘b’ and ‘a’.

Step 4: Combining the Textures - The Implementation

The first thing we need to do is create a function for this purpose called ‘AddNoiseToTexture’ and reference it in the start method. Then we need to do a few simple things before we anything else. We need to be able to turn off the noise if we don’t want to add it to our texture for whatever reason. We do this by creating a Boolean called ‘addNoise’ at the top, then we need a way of obtain the texture pixel information since it’s stored in a different script. We do this by creating a variable of type ‘Noise’ and call it ‘noiseScr’. Then in the inspector, drag the ‘Noise’ script into the variable.

After this the first thing we will do with our function is check that ‘addNoise’ is set to true and that ‘noiseScr’ is not null. If neither condition is met the function will fail and not run. The rest of the code in this function will be inside this if statement.

Code:

public bool addNoise;
public Noise noiseScr;
...
void Start()
{
   ...
   AddNoiseToTexture();
}

void AddNoiseToTexture()
{
   if (addNoise && noiseScr != null)
   {
      
   }
}

Next we need to get the texture and temporarily store in the function so we can use it. Inside the statement create a new ‘Texture2D’ called ‘tempNoiseTex’. Then assign it the noise texture from ‘noiseScr’ using ‘noiseScr.noiseTexture’.

Code:

void AddNoiseToTexture()
{
   if (addNoise && noiseScr != null)
   {
      Texture2D tempNoiseTex = 
                      noiseScr.noiseTexture;
   }
}

Next we need to get the pixels from both ‘mainTexture’ and ‘tempNoiseTex’ and store them somewhere so we can cycle through their elements. Create two ‘Color32’ arrays, one called ‘tempMainPixels’ and the other ‘tempPixels’. Assign ‘tempMainPixels’ the result of ‘mainTexture.GetPixels32’ and ‘tempPixels’ the result of ‘tempNoise.GetPixels32’.

Code:

void AddNoiseToTexture()
{
   if (addNoise && noiseScr != null)
   {
      Texture2D tempNoiseTex = 
                      noiseScr.noiseTexture;
      Color32 [] tempMainPixels = 
                  mainTexture.GetPixels32();
      Color32 [] tempPixels = 
                tempNoiseTexture.GetPixels32();
   }
}

This is good, we have the basic things we need. We need to just cycle through all the elements using a for loop. For the end condition of the for loop we will use ‘tempMainPixels.Length’ because we we to cycle through all of the main texture pixels so we cover everything. This is because on the off change the noise texture isn’t a big enough, it won’t stop short on the main texture.

Code:

void AddNoiseToTexture()
{
   if (addNoise && noiseScr != null)
   {
      Texture2D tempNoiseTex = 
                      noiseScr.noiseTexture;
      Color32 [] tempMainPixels = 
                  mainTexture.GetPixels32();
      Color32 [] tempPixels = 
                tempNoiseTexture.GetPixels32();
      for (int i = 0; i < tempMainPixels.Length; i++)
      {
         
      }
   }
}

Next we need to add the ‘rbga’ values of ‘tempPixels’ to ‘tempMainPixels’ to get a combined colour (note, not replace the colour). You might be asking ‘why not just ass the colours together with out all this?”. Well you can do this with ‘Color’ but ‘Colour32’ doesn’t work that way, it’s values work slightly differently. I’m using ‘Color32’ here because we are manipulating a lot of data and ‘Color32’ is a bit quicker and more efficient than ‘Color’. We will add the ‘rgba’ values of ‘tempPixels’ to ‘tempMainPixels’ using the incremental operator.

Code:

void AddNoiseToTexture()
{
   if (addNoise && noiseScr != null)
   {
      Texture2D tempNoiseTex = 
                      noiseScr.noiseTexture;
      Color32 [] tempMainPixels = 
                  mainTexture.GetPixels32();
      Color32 [] tempPixels = 
                tempNoiseTexture.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;
      }
   }
}


Now we need to set those pixels in place on the ‘mainTexture’ using ‘SetPixels32’ and applying the ‘tempMainPixels’ array to them. Because we need to cover the whole texture this is quite simple, we give it a starting position of ‘0’ and ‘0’ and parse it the ‘mainTexWidth’ and ‘mainTexHeight’ for the area to cover. This is handy because if the texture changes size we don’t need to change the function. Then we apply the changes to the texture.

Code:

void AddNoiseToTexture()
{
   if (addNoise && noiseScr != null)
   {
      Texture2D tempNoiseTex = 
                      noiseScr.noiseTexture;
      Color32 [] tempMainPixels = 
                  mainTexture.GetPixels32();
      Color32 [] tempPixels = 
                tempNoiseTexture.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();
   }
}

Next we need to comment out a line of code in ‘SetNoiseTextureSize’ of the Noise script where we assign the noise texture to the material. This is because it set’s this texture after the brick texture and we will wonder why our code isn’t working.

Code:

void SetNoiseTextureSize()
{
   ...
   //GetComponent<Renderer>().material.
                   mainTexture = noiseTexture;
   ...
}

Now when we let this compile enable the ‘CheckerBoardPatternTexture’ script in the inspector and set ‘addNoise’ to true.

When we run the code we get this error.




We will fix this in step 5.

Step 5: Fixing Errors

The error in step 4 is happening because thanks to the order of operations, the ‘CheckerBoardPatternTexture’ is running first then ‘Noise’ starts running shortly after it. Because ‘AddNoiseToTexture’ is running first just before ‘Noise’ has only just started, it’s asking for the noise texture before it has been created.

In order to fix this, we need to create the noise texture on demand. Just under the start method create a public function called ‘GenerateNoiseTexture’ then in that function reference ‘SetNoiseTextureSize’ and ‘CreateNoisePattern’. Then in ‘CheckerBoardPatternTexture’ at the top of the if statement, reference the function through ‘noiseScr’. After this comment out everything in the start method then let the code compile and press play.




Left to right: resolution, 64, 128, 256.

Step 6: A few refinements

What we have is good and it does what we want it to do. However, we could make this even better and more robust before we leave for part 2.

First off both textures have resolutions that are independent of each other. If we enlarge one texture we need to manually do it for the other, otherwise we will get problems down the line.

In order to prevent this, we need to set the resolution of the noise texture when we create it using ‘GenerateNoiseTexture’ in the ‘AddNoiseToTexture’ method and parse it the width and height of the mainTexture.

In the 'Noise' script we need to add two parable int variables to the ‘GenerateNoiseTexture’ function, one for the width and height ‘w’ and ‘h’. These variables are not useful on their own, we need to parse them to the ‘SetNoiseTextureSize’ as this is creating the actual noise texture. In the parse options of ‘SetNoiseTexture’ add the same two int variables we added to ‘GenerateNoiseTexture’. We are using the same variable names to keep things clear. Then after this in the reference to ‘SetNoiseTextureSize’ in ‘GenerateNoiseTexture’ parse ‘w’ and ‘h’ to ‘SetNoiseTextureSize’.

Code:

/*void Start ()
{

   SetNoiseTextureSize();
   CreateNoisePattern();
}*/

public void GenerateNoiseTexture(int w, int h)
{
   SetNoiseTextureSize(w, h);
   CreateNoisePattern();
}

void SetNoiseTextureSize(int w, int h)
{
   ...
}

In the ‘CheckerBoardPatternTexture’ script in the ‘AddNoiseToTexture’ function where we reference ‘GenerateNoiseTexture’ from ‘noiseScr’ parse ‘GenerateNoiseTexture’ the variables for the main texture ‘mainTexWidth’ and ‘mainTextureHeight’.

Code:

void AddNoiseToTexture ()
{
   if (addNoise && noiseScr != null)
   {
      noiseScr.GenerateNoiseTexture(
                  mainTexWidth, mainTexHeight);
      ...
   }
}

Now we need to do two more things, comment out the ‘Start’ function and in ‘SetNoiseTextureSize’ assign ‘resolutionW’ to ‘w’ and ‘resolutionH’ to ‘h’. Let the code compile, press play and see the results.

Code:

void SetNoiseTextureSize (int w, int h)
{
   resolutionW = w;
   resolutionH = h;
   ...
}



Great, part one is finished. We have laid down the foundations and we can do more fun things with it.

We have learnt how to:
  • Make a simple noise texture that changes every time using Random.value
  • Set a seed value for the noise
  • The basics of Unity’s random function works
  • Combine two textures
  • What are the differences between Color32 and Color and their uses

In Part 2 we will learn how to make psudo-noise which is more predictable and based on the location of the object using its vertices and make a 1D noise texture.

Click here for Part 2.

No comments:

Post a Comment