Wednesday 15 February 2017

Procedural Generation Tutorial Brick Noise Texture - Part 2

Procedural Generation Tutorial Brick Noise Texture - Part 2


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.

http://joseph-easter.blogspot.com/2017/01/procedural-generation-tutorial-brick_26.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.


We will:
  • Explain the theory
  • Declare local coordinates of a quad
  • How to use use Lerp between two Vector3's to get one Vector3 point
  • How to get a point between upper and lower side of a quad
  • Find a point between the left or sight side of a quad
  • Convert local coordinate space to world space

In Part one we created a way to generate noise using one of Unity’s built in functions and it created a random noise pattern every time. We said next time we will change it so we get psudo-random noise in case we need to replicate a pattern, based on the object’s location. In this part we will not see the results of our code until part 3 because if I were to put in how to get the objects location and turn it into a value and how to use the psudo-random noise, part 2 would have been par too long.

Step 1: The theory

Last time we have a little control over the output of the noise using a ‘seed’ as our initial value. In order to get more control over the noise we will have the objects position in world space generate a value which will then be send to an algorithm and produce a result.

The way to make this work will be to map out the 3D object we will be using by visualising it’s texture coordinates in local space e.g. say where the upper and lower left points are and the the upper and lower right points of the object are, then locating and storing them as private vector3 variables. After this we will create two more vector3 variables one for the left side and the right side of the quad. These will interpolate the values between the top and bottom of the left and right sides. After this we will create another vector3 variable which will interpolate between these two variables, combining it all into one neat value. We do this because it will multiply the pixel noise based on where it is in the texture.

After we do this we will use the four original points (to map out the objects UV texture coordinates) and find out what their values are in world space (not local space) by converting the code a little.

After this we will be able to use the value to create a multiplier for the psudo-random noise.

Step 2: Defining local coordinates

At the moment we are using our local UV texture coordinates and the ‘Random.value’ and ‘seed’ values to produce mostly random noise and a different pattern each time.

In order to create psudo-random noise we need to define local coordinates of the object in local space. We do this by defining four variables and storing the location of the four points of the quad in those variables. Because we are using local space, it has its own origin which it’s centered on. These coordinates are (-0.5, 0.5.0), (0.5, -0.5.0), (-0.5, -0.5.0), (0.5, 0.5.0). We will call these ‘pointLL’ in which the last two letters represent the upper or lower left or right side. E.g. the second the last letter will be ‘L’ for lower or ‘U’ for upper and the last letter will be ‘L’ for left or ‘R’ for right.

Also to keep things tidy we will put these variables in a function called ‘WorldCoordinates’ and reference it in the for loop and comment out the ‘Random.seed’ line.


Code:


Vector3 pointLL;
Vector3 pointUL;
Vector3 pointLR;
Vector3 pointUR;
...
void CreateNoisePattern()
{
   //Random.seed = 29;
   WorldCoordinates();
   for (int w = 0; w < resolutionW; w++)
   {
      for (int h = 0; h < resolutionH; h++)
      {
         noiseTexture.SetPixel(w, h, 
                   Color.grey * Random.Value);
      }
   }
   noiseTexture.Apply();
}
...
void WorldCoordinates()
{
   pointLL = new Vector3(-0.5f, -0.5f);
   pointUL = new Vector3( 0.5f, -0.5f);
   pointLR = new Vector3(-0.5f,  0.5f);
   pointLR = new Vector3( 0.5f,  0.5f);
}

Also to keep things tidy we will put these variables in a function called ‘WorldCoordinates’ and reference it in the for loop and comment out the ‘Random.seed’ line.

The next step here is, inside the for loops we need to interpolate (find a value between) the lower and upper left corners of the object based on the ‘h’ iterator in the loop. Same thing goes for the corners on the right hand side. These will give us any point on the left or right side in the local object coordinates we may need. For this we will create two new variables called ‘pointLeftSide’ and ‘pointRightSide’.

The next step here is to find a value between these two new point varaibles based on the iterator ‘w’ in the loop for the ‘W’ axis. We now have a bilinear way of finding the final point in the local space. We can use this to convert it into a noise value for a specific pixel. We will put these into a separate function called ‘LeftAndRightSides’.

Code:


Vector3 leftSide;
Vector3 rightSide;
..void CreateNoisePattern()
{
   //Random.seed = 29;
   float normalizer = 1f / resolutionW;
   WorldCoordinates();
   for (int w = 0; w < resolutionW; w++)
   {
      LeftAndRightSides(pointLL, pointLR,
                        pointUL, pointUR, 
                        w, normalizer);
      for (int h = 0; h < resolutionH; h++)
      {
         noiseTexture.SetPixel(w, h, 
                   Color.grey * Random.Value);
      }
   }
   noiseTexture.Apply();
}
...
void LeftAndRightSides(Vector3 pLL, Vector3 pUL, Vector3 pLR, Vector3 pUR, int w, float n)
{
   leftSide = Vector3.Lerp(pointLL, pointUL,
                          (w + 0.5f) * n);
   rightSide = Vector3.Lerp(pointLR, pointUR,
                          (w + 0.5f) * n);
}

After this we need to add a ‘normalizer’ in the form of a ‘float’ just before the for loops. This will basically choose the intensity of the noise colour (grey or what ever colour you choose) by dividing ‘1f’ by the resolution of the texture. This is because colour variables have multiple channels that use values varying from 0 to 1. The normalizer variable, alters the values to fit into the current context it’s needed for. We also parse these variables to the function so it can deal with the calculations.

In a new method ‘CalculatePoint’ when we interpolate between the two side vectors we use the ‘w’ iterator plus half the width of the quad and multiply it by the normalizer. We do this to bring the colour into the value range we need it for due to the colour channels and how they operate.

The last thing we need to do is interpolate the left and right sides into one point. This will change the value depending on the location of the point on both the with and height. We will interpolate by using the ‘h’ iterator plus ‘0.5’ (the height of the object from the centre) and multiplying it by the normalizer again. We will parse ‘h’ to the function we well.

Code:


Vector3 point;
..void CreateNoisePattern()
{
   //Random.seed = 29;
   float normalizer = 1f / resolutionW;
   WorldCoordinates();
   for (int w = 0; w < resolutionW; w++)
   {
      LeftAndRightSides(pointLL, pointLR,
                        pointUL, pointUR, 
                        w, normalizer);
      for (int h = 0; h < resolutionH; h++)
      {
         CalculatePoint(leftSide, rightSide,
                        h, normalizer);
         noiseTexture.SetPixel(w, h, 
                   Color.grey * Random.Value);
      }
   }
   noiseTexture.Apply();
}
...
void CalculatePoint(Vector3 lSide, Vector3 rSide, int h, float n)
{
   point = Vector3.Lerp(lSide, rSide,
                          (w + 0.5f) * n);
}

Great, we now have the local coordinates set up. All that’s left to do is convert this to world space to get our desired values.

Step 3: Converting local coordinates to world space

This last step is really quick and easy, we just have to tweak a new existing lines of code and we are done.

Rather that generating a value from local object space we need to get the object’s location using it’s four points located in world space. This is really straight forward. When we set the points’ vector3 positions we tell the compiler to convert the value to world space (where ever this is in world space). To do this just when we declare the local coordinates, we envelope them into a ‘transform.TransformPoint’ function which takes care of this for us.

Code:

void WorldCoordinates()
{
   pointLL = transform.TransformPoint(new 
                       Vector3(-0.5f, -0.5f));
   pointUL = transform.TransformPoint(new 
                       Vector3( 0.5f, -0.5f));
   pointLR = transform.TransformPoint(new 
                       Vector3(-0.5f,  0.5f));
   pointLR = transform.TransformPoint(new 
                       Vector3( 0.5f,  0.5f));
}

Now we have our four points as world space locations and can use them to generate a psudo-random value.

Great, part two is finished. We have laid down the groundwork for psudo-random patterns and we can do all sorts of cool things with it.

We have learnt how to:
  • Declare local coordinates of the four points of the quad
  • Interpolate between two vector3 variables to get one vector3
  • Find a point between upper & lower left to get a point on the left side (same for right side)
  • Find a point between the left and right side to get a point in the middle of the quad
  • Convert the local point coordinates (or UV coordinates) to world space coordinates
Click here for Part 3.

No comments:

Post a Comment