Programmer Art

Published October 21, 2008
Advertisement
Making Programmer Art: Tiling Rough Stone Floors

Dungeon floors. You've seen 'em, I've seen 'em. We've all of us died on 'em at some point in our lives. Here is a fairly simple method I've come up with for creating a rough-stone paved floor texture that tiles, suitable for entry in the category of Programmer Art.

I start with a Voronoi cell diagram. A Voronoi diagram is an image that is partitioned into discrete regions or areas based on a collection of randomly generated seed points. The image space is populated with a number of these seed points, then each pixel of the image is given a color based on which point it is nearest to. So all pixels nearest a white point will be colored white. This fractures the image space into a number of polygonal shapes that form the basis of our tile floor. However, there is a condition: the cell diagram must tile with itself. In order to ensure this, we need to construct the Voronoi diagram with a specially constructed seed set. We populate an image area the size of our final texture with a set of seed points, then we copy this set 8 times, and "arrange" the sets in a 3x3 block pattern. Then we calculate the diagram for the central section of the 3x3. Since the neighboring areas have the same seed point pattern, the cells are constructed such that the final diagram tiles perfectly with itself.

A sample bit of Lua code to generate such a diagram (and the code I used to generate the diagram for this article) is:

valuearray=CArray2Dd()distarray=CArray2Dd()pointlist={}function populate_point_list(numpoints, width, height)  -- Each point is duplicated 8 times  local count  pointlist={}    for count=1,numpoints,1 do    local point={}    point.id = count    point.x = randRange(0,width-1)    point.y = randRange(0,height-1)        local x,y    for x=-1,1,1 do      for y=-1,1,1 do        local newpoint={}        newpoint.id=point.id        newpoint.x=point.x+x*width        newpoint.y=point.y+y*height                table.insert(pointlist, newpoint)      end    end  end  endfunction find_closest_point(x,y)  local i  local mindist=1000000  local id=0    for i=1,#pointlist,1 do    local p=pointlist    local dx,dy = p.x-x, p.y-y    local dist=math.sqrt(dx*dx + dy*dy)    if dist      mindist=dist      id=p.id    end  end    return mindist,idendfunction tiling_voronoi(numpoints, width, height)  populate_point_list(numpoints, width, height)  valuearray:init(width,height)  distarray:init(width,height)    local x,y  for x=0,width-1,1 do    for y=0,height-1,1 do      local dist,id = find_closest_point(x,y)      distarray:set(x,y,dist)      valuearray:set(x,y,id)    end  endend


In the above source, CArray2Dd() is a type that simply encapsulates a 2-dimensional array of double values. To generate a diagram, the function tiling_voronoi is called with the number of seed points desired, as well as the dimensions of the image. It first creates the list of seed points, and sets up the 3x3 array of duplicate sets. Then it initializes the arrays to hold distance values and cell id values (colors). Then it cycles through each pixel in the range, finds the closest point, and assigns an id based on the closest point. Find closest point functions by testing the distance to each point in the 3x3 block of point lists.

The above code was written for my own personal toolset, but can be adapted to other applications.

The result is something that looks like this:


If you load the above into an image editor and apply a 1/2 offset in each direction, you can see that it tiles with itself perfectly. And while it doesn't look much like a stone floor, it gives us a place to start. The cells will be the stones in our floor. We just need to delineate stones from mortar lines.

To do this, we load the image up into the GIMP. (Or Photoshop, but since I don't have Photoshop, the GIMP it is). Using the Fuzzy Select tool, we select each region in turn. HINT: If one of the regions is pure black (RGB (0,0,0)) select it first; since the mortar lines will be solid black, if you wait to do the solid black cell, you will end up selecting all of the lines when you select the black cell. Select a region using Fuzzy Select with no feathering or antialiasing of the region, then flood-fill the cell with pure black. Then go to Select->Shrink to shrink the selection by some arbitrary (say, 5) number of pixels. Uncheck the Shrink From Edge box, so that the selection will not "pull away" from the edges when shrunk. This creates a smaller region within the cell; floodfill this region with pure white. Repeat for all of the cells, and the result should be an image of white cells delineated by sharp black borders, like this:



We can see our floor starting to take shape. But, of course, it's too sharp and regular. First of all, let's apply a Gaussian blur to fuzzy it up a bit. Go to Filters->Blur->Tileable Blur and select an arbitrarily large (I used 26) blur radius. It needs to be a Tileable Blur, rather than a normal Gaussian Blur, since we need to ensure that the edges will still tile. After the filter runs, you should see something like this:



That looks pretty good. We can apply some tweaks to the brightness and contrast of the image, to sort of play with the contrast of the regions:



That looks better, but those stones are just way too smooth. So let's apply some turbulence to noisify it a little.

We'll start with some fBm fractal noise. I've discussed using fractal noise turbulence as a tool quite a bit in previous journal entries, so I won't go into too much detail here. Just some things to remember: The fractal noise sources you use to perturb the pixels must be tiling. I typically sample 2 fBm fractal modules into separate buffers, using a seamless sampling algorithm to ensure the buffers tile, then use those buffers with a scaling factor to perturb or offset the pixels in the X and Y directions. This turbulence has the effect of roughing the edges and making it seem more like natural stonework:



There, that looks pretty good. We have rough stones, rough mortar lines between them. Now we need to get down to constructing the actual texture.

First, we need to select the colormaps we will use for the stone and the mortar surfaces. Where you get these sources matters little, although you must ensure that the textures you use tile with themselves. I typically manufacture texture sources from digital photographs. I'll photograph a surface (stone, dirt, asphalt, you name it; I take my camera with me everywhere I go) then later I will crop and scale and filter and edit pieces to construct seamless tiling textures. You can also synthesize textures using any of a bajillion procedural texture synthesis routines. At any rate, the textures I selected are these, which were edited from two of my stock of library photos. (Both taken in Flagstaff, at the Sunset Crater Volcano)



The image on the left will be our stones, the one on the right will be our mortar. Starting with the mortar image, I create another Layer and copy the stone image into this layer. Then I right click the new layer in the Layers box, and Add Layer Mask. I copy the perturbed rock tile image we just created into this layer mask so that it acts as a blend agent between the mortar (where the tile mask is black) and rock (where the tile mask is white):



This outlines our mortar areas from the stone areas.

Now all we need to do is apply a bump-map for shading and we are done. However, I hate GIMPS built-in bump map generator. Typically, I construct my own bump maps. I treat the tile image as a heightmap, construct an array of vertex normals based on the height data, and calculate the bump map by performing a dot product between the normal at each pixel and an arbitrary light direction vector. Simple and effective:



Once I've generated the bump map, I load it into the GIMP, copy it, and paste it on top of the color map. Then I change the layer settings of the pasted map to Multiply. This has the effect of scaling the colormap pixel based on the shading information in the bump map. And voila:



There you have it: a tiling stone floor that looks at least halfway decent, that any programmer can make.
Previous Entry Back to the islands
Next Entry Back to the floor
0 likes 8 comments

Comments

Twisol
That looks pretty cool!
October 22, 2008 01:13 AM
swiftcoder
Seems to me the mortar should probably be inset (not outset), but anyway, nice effect.
October 24, 2008 03:39 PM
JTippetts
Quote: Original post by swiftcoder
Seems to me the mortar should probably be inset (not outset), but anyway, nice effect.


It's an optical illusion, kind of like those photos of volcanic craters taken from the air that make the crater look inverted, like a dome hill, rather than inset. I'm working on that problem as we speak...
October 24, 2008 06:00 PM
MGB
MGB
Nice :) Hmm... perhaps you can shed some light on a (related) question that's been bugging me for a while.
Namely, I've been trying to create a realistic normal map for an ocean shader. I read (in 'Texturing & Modelling - A procedural approach' p140) about a 'fractal version of F1' - a cellular based noise like you used here (aka Worley), yet try as I might, I can't get anything resembling the 'crumpled tinfoil' effect this is supposed to give. Your work here is the closest I've seen to that area, so wondered if you'd tried anything similar?
October 29, 2008 03:37 PM
JTippetts
Quote: Original post by Aph3x
Nice :) Hmm... perhaps you can shed some light on a (related) question that's been bugging me for a while.
Namely, I've been trying to create a realistic normal map for an ocean shader. I read (in 'Texturing & Modelling - A procedural approach' p140) about a 'fractal version of F1' - a cellular based noise like you used here (aka Worley), yet try as I might, I can't get anything resembling the 'crumpled tinfoil' effect this is supposed to give. Your work here is the closest I've seen to that area, so wondered if you'd tried anything similar?



Here's a quick couple tries, using F1 with an fBm fractal and with a ridged multifractal (both described in that book):



I'd imagine the fBm one would be adequate, but I really haven't done much with water surfaces so I couldn't say.
October 29, 2008 04:16 PM
MGB
MGB
Hmm I was summing octaves of cell noise (and getting something approaching the top-right image, but messier.) Did you just add cell noise + perlin and invert for that top-right one?
October 31, 2008 08:36 AM
MGB
MGB
Hmm I was summing octaves of cell noise (this is what I assumed he meant by 'fractal version') and getting something approaching the top-right image, only messier. Did you just add cell noise + perlin/fbm and invert for that top-right one?
October 31, 2008 08:37 AM
JTippetts
Quote: Original post by Aph3x
Hmm I was summing octaves of cell noise (this is what I assumed he meant by 'fractal version') and getting something approaching the top-right image, only messier. Did you just add cell noise + perlin/fbm and invert for that top-right one?


The top-left is octaves of cell noise combined as an fBm fractal, the top right is octaves of ridged multifractal cell noise. I wrote my fractals such that I can plug in any type of basis function (cell noise, Perlin's gradient noise, simplex noise, generated regular patterns, greyscale image buffers, etc...) for the octaves from which to build the fractal.
October 31, 2008 09:06 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement