Synthesis of textures and bump maps of tiled surfaces – Part I

Image converted using ifftoanyImage converted using ifftoanyImage converted using ifftoanyImage converted using ifftoany

Motivation

Tiled surfaces are a ubiquitous staple of modern architecture. In an interior space, floors and walls of rooms, from bathrooms to kitchens, and almost every space in between, can be covered with tiles. While, larger format stone tiles are used to clad buildings, delineate walkways and form patios.

The state of computer rendering of images, using global illumination or physically based rendering, has reached a point where it can fool the casual viewer into thinking that what she sees is a photograph. Details defining surfaces at various scales contribute a great deal toward creating this illusion. A simple item, such as a humble floor tile, can have a lot of detail both in its shape and in its color. This detail is generally defined in computer graphics using geometry and/or textures.

While the generation of textures to convey color variation is relatively easy, the geometric detail to capture the small nuances of tile shape can still be prohibitive to generate and render. The use of surface textures to represent color (be that diffuse or specular, depending on the tile and its surface finish) is part of most rendering engines. Small-scale surface geometric detail can be represented using so called bump maps (Blinn, 1978), where the local surface height of geometry is altered by the bump map for lighting/shading calculations only. This method does not alter the underlying geometry, which is evident from the silhouettes of bump-mapped objects; a bump-mapped sphere still casts a smooth circular/elliptical shadow. A variant of the method is to use a normal map, which encodes a perturbed normal at the lighting/shading calculation point, thus the local perturbation can be along any direction, as opposed to perpendicular to the surface, as the traditional bump mapping. A more advanced method, called displacement mapping, actually alters the local geometry in lighting/shading calculations.

Features

This multi-part article was born out of the need and curiosity to procedurally generate tiles for use in rendering. The method presented and its implementation is capable of generating texture and bump maps of arbitrary sizes. Both square and rectangular tiles, with or without offset, can be created. A grout line of arbitrary color can be specified between tiles as well. The tiles can have any base color, which can be altered from tile to tile. This variation can specified to be between the base color and two other colors (think of it as a darker and a lighter version of the base color, for example, but can be any other colors). In addition, a color variation can be specified within a tile itself. Similarly, a bump map can be generated to alter the base height of a tile, which can vary from tile to tile. A local, per tile, variation can be specified as well. Finally, a grunge or grime map can be overlain the tiles to represent oh-not-so-clean tiles or remains of a spill.

At the heart of the method is a noise function. This noise function represents a random, albeit smoothly varying function. The implementation uses a realization of the Perlin Noise function (Adrian’s Soapbox, 2014) that was modified by yours truly. Thus I cannot claim that I wrote the noise function parts of the code, however I have modified it to suit my needs. Therefore all the credits go to the original author and hereby I thank him for publicly making available such code and the explanation that went with it.

Although the procedural tile generator was meant to be used as a set of C++ classes, part of a larger program, a simple driver program was written for it. This small program allows input files to be read using a rudimentary file format giving access to the parameters of the tile generator. The format of the input file is rather freeform; the parameters can be specified in any order, though logical groupings of them are encouraged.

The source code of the tile generator can be downloaded from tileMaker_v1. It was written in C/C++ and there is a supplied project file for Xcode. All the example input files are contained in the zip as well. Enjoy!

Implementation and usage

This section is in a form of a tutorial and a detailed explanation of what each parameter of the tile generator does. Starting with a simple model, and subsequently building up, more complex tiles can be generated. However, perhaps the most fundamental parameter of the tile generator is the specification of where to save the files, thus the input file needs a description of this

 outputFileName /Users/amzs/Desktop/yellow_square_tile_plain01.tif

 The size of the textures to be generated (in pixels) can be specified as

textureWidth 2048
textureHeight 2048

Similarly, the physical tiled area is specified by

tiledAreaWidth 10.0
tiledAreaLength 10.0

Note that the above two parameters can be non-integer, since they represent some real-world dimensions. Although no units (meters, feet) are specified, as long as the real-world coordinates are consistent, things will work out. Thus, the sizes of the tiles themselves can be specified as

tileWidth 1.0
tileLength 1.0

Again, these are given in real-world units. Use the same number for square tiles, of some other combination for rectangular tiles. Finally, the thickness of the grout line can be given in real-world coordinates as

groutWidth 0.02

Note, that the grout thickness specified will be used for both the horizontal and vertical grout lines.

In summary, the figure below illustrates all these parameters on a sketch.

Figure2

Figure 1 Tile and texture space

So far, using the above specifications alone, only square or rectangular tiles can be generated, but the tiles have to be aligned along both axis. By defining a parameter, tiles can be offset along the horizontal direction. If you want tiles to be offset along the vertical direction, well, just rotate the resulting texture maps by 90 degrees. Thus,

useTileXOffset
tileXOffset 1.01

The tileXOffset flag turns on the offset generation of tiles for every even row, so the offset starts with the second row of tiles and it is done for every other row thereafter. This is shown in the figure below

 Figure3

Figure 2 Tile offset

Color variation from tile to tile

So, let’s get started with something simple. A square, yellowish tile with some color variation was used to fill a room. The tiled area was 10.0 by 10.0 units, and the tiles were 1.0 by 1.0. The final rendering, done in Maya using Metal Ray, using an area light source in the ceiling, is as follows

Image converted using ifftoany

Figure 3 Plain yellow tiles

Note the subtle color variation from tile to tile. The colors are uniform within each tile. This image was rendered using the following diffuse texture map and a bump map.

figure4

Figure 4 Texture and bumps maps for Figure 3

So how does one achieve the color (and greyscale) variation in the texture maps? The texture generator employs something called a ‘color descriptor’. Given a location within a texture map (or in tile space, see Figure 1) a query is made what the color should be. The color descriptor for the tiles represents this at both global (or per tile) and local (within a tile) space. So lets see what parameters a color descriptor can have. So, for the case of the plain yellow tiles, the color descriptor was

tileColorGlobalColorType variable
tileColorGlobalColorNoiseType perlin
tileColorGlobalColor 1.0 0.94509803921569 0.57254901960784 1.0
tileColorGlobalColorLowSide 1.0 0.92549019607843 0.42745098039216 1.0
tileColorGlobalColorHighSide 1.0 0.96470588235294 0.56078431372549 1.0
tileColorGlobalNoiseXMin 0.0
tileColorGlobalNoiseYMin 0.0
tileColorGlobalNoiseXMax 10.0
tileColorGlobalNoiseYMax 10.0
tileColorGlobalOctaves 1
tileColorGlobalPersistence 1.0

The parameter tileColorGlobalColorType specifies if the color of a tile is solid or variable. A solid means that there is no color variation between tiles, every tile is the same color. A variable means that colors can change from tile to tile. Thus, the parameter tileColorGlobalColorNoiseType specifies how the color changes. It can take on values such as random, perlin, cosineModulated or wood. The random does what it’s name says; the color randomly varies from tile to tile, more on this later. The perlin one describes the variation of color sampled from a Perlin noise while cosineModulated and wood are variants of the Perlin noise. Examples of each will be given later. So what exactly is meant by that the color varies from tile to tile governed by either a random or procedural manner. The color of a tile is specified by an RGBA parameter (tileColorGlobalColor). In addition, two more colors can be specified (tileColorGlobalColorLowSide and tileColorGlobalColorHighSide). These two colors denote a range in which the color can vary. This is best illustrated as follows

Figure5

Figure 5 Color interpolation using three colors

The noise function (random, perlin, etc.) returns a value in the range of 0.0 to 1.0. Thus the final color is linearly interpolated from the given colors depending the noise value. Finally, parameters tileColorGlobalNoiseXMin, tileColorGlobalNoiseYMin, tileColorGlobalNoiseXMax, tileColorGlobalNoiseYMax define the region where the noise function is sampled from. Although, for this example a square region from zero to ten was specified, very interesting effects can be achieved if the region is not a square. By specifying a rectangular area, nice anisotropic, e.g. elongated, color distributions can be achieved, as we will see later. After that, the last two parameters, tileColorGlobalOctaves and tileColorGlobalPersistence are specific to the Perlin noise function. The number of octaves denotes the summation of noise at various scales, while the second parameter affects how the amplitude of noise falls with successive octaves. Generally, the noise value is obtained in the color descriptor as

noiseValue=noise.OctavePerlin(noiseX,noiseY,0.0,octaves,persistence)                 (1)

 Where the noiseX and noiseY are the x and y coordinates in the noise space, the third parameter is zero (we are only using 2D noise), whereas the octaves and persistence are defined in the reference (Adrian’s Soapbox, 2014). The table below gives a quick summary of how these parameters can affect the visual appearance of the noise function. However, this is just a brief summary. If interested in more detail, please see this reference (Adrian’s Soapbox, 2014).

Table 1 Perlin noise – parameters and their effect

xMin 0.0
xMax 1.0
yMin 0.0
yMax 1.0
octaves 1
persistence 1.0
T1-11xMin 0.0
xMax 2.0
yMin 0.0
yMax 2.0
octaves 1
persistence 1.0
T1-12
xMin 0.0
xMax 5.0
yMin 0.0
yMax 5.0
octaves 1
persistence 1.0
T1-21xMin 0.0
xMax 10.0
yMin 0.0
yMax 10.0
octaves 1
persistence 1.0
T1-22
xMin 0.0
xMax 50.0
yMin 0.0
yMax 50.0
octaves 1
persistence 1.0
T1-31xMin 0.0
xMax 100.0
yMin 0.0
yMax 100.0
octaves 1
persistence 1.0
T1-32
Anisotropy
xMin 0.0
xMax 5.0
yMin 0.0
yMax 1.0
octaves 1
persistence 1.0
T1-51xMin 0.0
xMax 50.0
yMin 0.0
yMax 1.0
octaves 1
persistence 1.0
T1-52
Octaves
xMin 0.0
xMax 1.0
yMin 0.0
yMax 1.0
octaves 2
persistence 1.0
T1-71xMin 0.0
xMax 1.0
yMin 0.0
yMax 1.0
octaves 4
persistence 1.0
T1-72
xMin 0.0
xMax 1.0
yMin 0.0
yMax 1.0
octaves= 8
persistence 1.0
T1-81xMin 0.0
xMax 1.0
yMin 0.0
yMax 1.0
octaves 16
persistence 1.0
T1-82
Persistence
xMin 0.0
xMax 4.0
yMin 0.0
yMax 4.0
octaves 2
persistence 0.25
T1-101xMin 0.0
xMax 1.0
yMin 0.0
yMax 1.0
octaves 16
persistence 0.5
T1-102

Lastly, the grout color is prescribed in a similar manner

groutColorColorType variable
groutColorColorNoiseType random
groutColorColor 0.5 0.5 0.5 1.0
groutColorColorLowSide 0.4 0.4 0.4 1.0
groutColorColorHighSide 0.6 0.6 0.6 1.0

This time, the grout is of a greyish color, with a color variation from a darker to a lighter tone.

Finally, the description of the tile generator input is concluded with a few more parameters:

randomTileOrientation false
randomTileNoiseStartPoint true
generateBumpMap true
blurFinalImage false

The randomTileOrientation parameter specifies if we want the orientation of each tile (e.g. the direction of sampling the noise space) from tile to tile to be consistent, or random. Similarly, the randomTileNoiseStartPoint parameter specifies if the starting point within the specified sampling region is consecutive as the tiles are generated or random. These last two boolean parameters help to achieve a more natural appearance, since tiles can be laid in forming a pattern or at random.

The closing boolean parameter blurFinalImage serves to add a little bit of blur to the final texture image, if needed, to bring together sharp contrasting regions.

Local color variation within each tile

Next we can add local color variation to the tile, so the color not only varies from tile to tile, but also varies within each tile, based on a local noise function, we can get the following

Image converted using ifftoany

Figure 6 Local color variation within a tile

In comparison to Figure 3, the changes are subtle, but they are there, adding that little extra nuance to the visual appearance of the tiles. The texture and bump maps used were as follows

figure7Figure 7 Texture and bumps maps used in Figure 6

Also note that a small twist was added to the bump maps; a slowly varying noise was used to add some unevenness within the tiles altering their height not only per tile, but within a tile as well. The per-tile color variation was achieved using a local color descriptor, tileColorLocalColor. This color descriptor has exactly the same set of specifiable parameters as its global cousin. The full input file used to generate Figure 7 is as follows

outputFileName /Users/amzs/Desktop/yellow_square_tile_localnoise01.tif
textureWidth 2048
textureHeight 2048
tiledAreaWidth 10.0
tiledAreaLength 10.0
tileWidth 1.0
tileLength 1.0
groutWidth 0.02
tileColorGlobalColorType variable
tileColorGlobalColorNoiseType perlin
tileColorGlobalColor 1.0 0.94509803921569 0.57254901960784 1.0
tileColorGlobalColorLowSide 1.0 0.92549019607843 0.42745098039216 1.0
tileColorGlobalColorHighSide 1.0 0.96470588235294 0.56078431372549 1.0
tileColorGlobalNoiseXMin 0.0
tileColorGlobalNoiseYMin 0.0
tileColorGlobalNoiseXMax 10.0
tileColorGlobalNoiseYMax 10.0
groutColorColorType variable
groutColorColorNoiseType random
groutColorColor 0.5 0.5 0.5 1.0
groutColorColorLowSide 0.4 0.4 0.4 1.0
groutColorColorHighSide 0.6 0.6 0.6 1.0
tileColorLocalColorType variable
tileColorLocalColorNoiseType perlin
tileColorLocalColor 1.0 0.94509803921569 0.57254901960784 1.0
tileColorLocalColorLowSide 1.0 0.92549019607843 0.42745098039216 1.0
tileColorLocalColorHighSide 1.0 0.96470588235294 0.56078431372549 1.0
tileColorLocalNoiseXMin 0.0
tileColorLocalNoiseYMin 0.0
tileColorLocalNoiseXMax 50.0
tileColorLocalNoiseYMax 50.0
tileColorLocalOctaves 8
tileColorLocalPersistence 0.25
tileBumpNoiseAlternateColorType variable
tileBumpNoiseAlternateColorNoiseType perlin
tileBumpNoiseAlternateColor 1.0 1.0 1.0 1.0
tileBumpNoiseAlternateColorLowSide 1.0 1.0 1.0 1.0
tileBumpNoiseAlternateColorHighSide 1.0 1.0 1.0 1.0
tileBumpNoiseAlternateNoiseXMin 0.0
tileBumpNoiseAlternateNoiseYMin 0.0
tileBumpNoiseAlternateNoiseXMax 2.0
tileBumpNoiseAlternateNoiseYMax 2.0
tileBumpNoiseAlternateOctaves 2
tileBumpNoiseAlternatePersistence 0.25
randomTileOrientation false
randomTileNoiseStartPoint true    
generateBumpMap true
tileBumpNoiseAlternate true
blurFinalImage false

If the number of octaves is increased, a more fine detailed noise variation is achieved within each tile, as Figure 8 illustrates.

yellow_square_tile_localnoise02Figure 8 The number of octaves increased to 8, resulting in a finer noise

This concludes the first part of this article. In Part II, further variations on the usage of noise in tile texture making will be discussed.

Leave a Reply