Procedural Generation Tutorial Basic Cell Pattern Texture - Part 5
The end result of the full tutorial.
At the end of Part 5 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
A Quick Announcement:
Hi every one, a quick announcement. Starting next week on (enter date) I will be taking the next two weeks off, meaning there will be no new posts on (add date and add date). After this I will be back and these posts will resume as normal starting (enter date). I need some time to catch up on a few projects which need attention and I will be writing up a new tutorial on something new. I hope I haven't disappointed any one and I'll see you back in a fortnight with a new tutorial.
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 4:
http://joseph-easter.blogspot.com/2017/04/procedural-generation-tutorial-basic_26.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 5 and the completed project file of Part 5.
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
- Use and enum to quickly switch settings in the inspector
- Use a switch statement to switch between patterns
- Interpolate images position in a cell basic on noise
- Combine multiple pattern types
Step 1: The theory
The second thing we will do is, using our noise values when we show a cell of the image from the ‘topLayerTexture’ we will find a way to vary the position of it with its cell. We will use interpolation to help us achieve this effect on the ‘x’ and ‘y’ axis while keeping the image in the confines of the cell.
In the final case of the switch statement we will add code from the both existing cases.
Step 2: Pattern Type Settings in the Inspector
Before we do anything, we will set up our project so the developer and designer can quickly change the pattern type in the inspector at will. We will create a public enumeration variable called ‘PatternType’ in ‘BasicCellPatternCreator’ with three values, ‘showCell’, ‘cellPosition’ and ‘both’.
Code:
public enum PatternType {showCell,
cellPosition, both}
public PatternType patternType;
cellPosition, both}
public PatternType patternType;
Now in ‘CreatePattern’ we will create a switch statement that will use this enum. We will initially create one case for ‘showCell’ and place our existing ‘SetPixels32’ lines in there. The case will be ‘PatternType.showCell’.
Step 3: Interpolating the image's x position in the cell
To position the image within each cell, we will use interpolation or the 'Lerp' function of the ‘Vector3’ class and use the ‘i’ and ‘j’ iterators from last time for the ‘t’ value. This is quite straightforward. We will create two local integers, one for the ‘x’ and ‘y’ axis. With both variables, we will interpolate between to values, using the ‘Mathf.Lerp’ method. For the ‘x’ axis we will interpolate between the left side of the cell which is ‘i’ times ‘blockWidth’, and the right side of the cell which is ‘i’ times ‘blockWidth’ plus ‘middleOfCellWidth’ times two. We do this because ‘SetPixels32’ starts at the bottom left pixel, that’s why we make the centre of the cell the right most side. We use ‘noiseVal’ as our ‘t’ value for the interpolation. This is good, now replace ‘i * blockWidth + middleOfCellWidth’ with ‘x’. We will do this for ‘y’ in a moment, I wanted to keep this explanation to one axis to make things more straightforward. Another note we cast the function as an integer because this is the type we are assigning it to, but we cast the other variables ‘Mathf.Lerp’ because this function works best with ‘float’ point variables.
Code:
void CreatePattern()
{
...
for (int j = 0; j < squaresY; j++)
{
noiseScr.CalculatePoint(
noiseScr.leftSide,
noiseScr.rightSide,
j, normalizer);
float noiseVal = noiseDimention(
noiseScr.point,
noiseScr.patternAlternationSpeed);
float val = noiseVal * 10f;
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
}
...
}
for (int j = 0; j < squaresY; j++)
{
noiseScr.CalculatePoint(
noiseScr.leftSide,
noiseScr.rightSide,
j, normalizer);
float noiseVal = noiseDimention(
noiseScr.point,
noiseScr.patternAlternationSpeed);
float val = noiseVal * 10f;
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
}
...
}
}
Image 1.0
Step 3: Interpolating the image's x position in the cell
Code:
void CreatePattern()
{
...
for (int j = 0; j < squaresY; j++)
{
...
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
case PatternType.cellPosition:
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
topLayerTexture.SetPixels(
x, j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
break;
}
...
}
{
...
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
case PatternType.cellPosition:
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
topLayerTexture.SetPixels(
x, j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
break;
}
...
}
}
Image 1.0
Note: This is handy as the image does not accidentally go outside the cell itself. (In some engines, if an image did this, the offending parts of the image would be clipped off and not rendered).
Step 4: Interpolating the image's y position in the cell
The principal here is the same as in ‘Step 3’, but on the ‘y’ axis. Create a local variable ‘y’ and use ‘Mathf.Lerp’ again but instead of casting ‘i’ cast ‘j’ as this is the axis we are dealing with (plus it will give a more varied result as these iterators are often on different values). Also change the variables ‘blockWidth’ to ‘blockHeight’ and ‘middleOfCellWidth’ to ‘middleOfCellHeight’. Everything else can stay the same.
Then change ‘j * blockHeight + middleOfCellHeight’ to ‘y’.
Code:
void CreatePattern()
{
...
for (int j = 0; j < squaresY; j++)
{
...
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
case PatternType.cellPosition:
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
int y = (int)Mathf.Lerp(
(float)j * blockHeight,
(float)j * (float)blockHeight +
((float)middleOfCellHeight * 2f),
val);
topLayerTexture.SetPixels(
x, y * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
break;
}
...
}
{
...
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
case PatternType.cellPosition:
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
int y = (int)Mathf.Lerp(
(float)j * blockHeight,
(float)j * (float)blockHeight +
((float)middleOfCellHeight * 2f),
val);
topLayerTexture.SetPixels(
x, y * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
break;
}
...
}
}
Image 1.2
Step 5: Combining these two settings
Code:
void CreatePattern()
{
...
for (int j = 0; j < squaresY; j++)
{
...
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
case PatternType.cellPosition:
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
int y = (int)Mathf.Lerp(
(float)j * blockHeight,
(float)j * (float)blockHeight +
((float)middleOfCellHeight * 2f),
val);
topLayerTexture.SetPixels(
x, y * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
break;
case PatternType.both:
if (val >= noiseTolerance)
{
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
int y = (int)Mathf.Lerp(
(float)j * blockHeight,
(float)j * (float)blockHeight +
((float)middleOfCellHeight * 2f),
val);
topLayerTexture.SetPixels(x, y,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
}
...
}
{
...
switch(patternType)
{
case PatternType.showCell:
if (val >= noiseTolerance)
{
topLayerTexture.SetPixels(
i * blockWidth +
middleOfCellWidth,
j * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
case PatternType.cellPosition:
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
int y = (int)Mathf.Lerp(
(float)j * blockHeight,
(float)j * (float)blockHeight +
((float)middleOfCellHeight * 2f),
val);
topLayerTexture.SetPixels(
x, y * blockHeight +
middleOfCellHeight,
imageTexture.width,
imageTexture.height,
imagePixels);
break;
case PatternType.both:
if (val >= noiseTolerance)
{
int x = (int)Mathf.Lerp(
(float)i * blockWidth,
(float)i * (float)blockWidth +
((float)middleOfCellWidth * 2f),
val);
int y = (int)Mathf.Lerp(
(float)j * blockHeight,
(float)j * (float)blockHeight +
((float)middleOfCellHeight * 2f),
val);
topLayerTexture.SetPixels(x, y,
imageTexture.width,
imageTexture.height,
imagePixels);
}
break;
}
...
}
}
Image 1.3
Great, part five is finished. Our basic cell pattern is complete for now. We have three types of irregular pattern one randomly show some cells, one altering the position of an image with in those cells and another combining these two effects. With procedural generation one can do quite a lot with these simple tools to make interesting effects. Often combining them in interesting ways can achieve surprising results.
We have learnt how to:
- Use and enum to quickly switch settings in the inspector
- Use a switch statement to switch between patterns
- Interpolate images position in a cell basic on noise
- Combine multiple pattern types