Textures for OpenSCAD with noisetool

“Coherent noise”… a bit like military intelligence?


Table of Contents:


Introduction

(Top)

Putting textures on objects in CAD is usually done using a heightmap, a two-dimensional array of numbers that is converted to a mesh by the CAD program. Most CAD programs of which I’m aware allow use of a photograph for the two-dimensional array, which opens up a rich source of textures. But photographs can be a bit problematic, as the tone range needs to be controlled to keep the texture from looking like the Rocky Mountains and the image may contain things that don’t “texture” too well.

OpenSCAD’s heightmap tool is surface(). OpenSCAD will read both PNG photos and text arrays of numbers for use in the surface() tool. It’s the latter that we’ll be using for the workflow in this post, using software to produce textures to use in OpenSCAD. First, a little background.

Most of the graphics you see in computer-generated movies uses noise-based textures. Now, noise itself is, by definition, random, which really doesn’t work well for the large variety of textures in nature, so some smart folk came up with math to put “pattern” (my term) into noise data. The whole concept is called “coherent noise”, and the primary researcher developing it is Ken Perlin. There’s a link to the Wikipedia page on Mr. Perlin and Perlin noise in the Resources section at the end of this post.

A fellow named Jason Bevins took Mr. Perlin’s coherent noise math and a few others’ derivative works and developed a C++ library called libnoise. One can write a C++ program around libnoise and generate all sorts of texture, sufficient flexibility to model a world. I took the libnoise library and wrote a program around it to let folk generate coherent noise streams without having to write C++ code, called noisetool. The rest of this post is about using noisetool to make textures for incorporation into CAD models.

noisetool

(Top)

noisetool is available from one of my Github repositories, link provided in the Resources section. As of this writing (4/10/2025) you can only clone the repository and compile the program; I’ll be posting a .zip file with a Windows executable when I get my cross-compiler fixed. Note that noisetool is a command-line program, meant to be run from a Windows CMD or Powershell window, or any of the terminal shell programs available in Linux distributions. It should compile okay on Macs, but I don’t have one to test it.

noisetool takes a definition file and uses it to generate coherent noise in various forms. The form we’ll be discussing here is one I wrote in addition to the already-available forms, “brick”. This form (or ‘builder’, in the definition file) lays the output of the noise defintion network onto a rectangular “brick” form, which has the feature to taper the edges to comply with various hewn stone patterns. A definition file for a simple texture generator would look like this:

Perlin:name=perlin
Output:module=perlin;builder=brick;destfile=brick.txt;destsize=8,4

This defines a Perlin noise module named perlin (noisetool is case-sensitive), then an Output that takes perlin’s noise stream maps it onto a brick form of size 8x4 inches and outputs the result to brick.txt in a format recognizableby OpenSCAD’s surface() tool. There are other parameters one can use to modify either/both the Perlin and Output modules, we’ll get to them below.

Jason Bevins organized libnoise as a collection of classes that are chained together to form noise streams. noisetool exposes that “network” organization as a set of nodes and a connector operator to define connections between the nodes. A more performant example illustrates the network organization to produce hewn stone textures:

RidgedMulti:name=ridged;freq=0.2;oct=3;lac=0.5
Perlin:name=base
Add:name=add
ScaleBias:name=scale;scale=0.3
Connect:source=ridged;sink=add;instance=0
Connect:source=base;sink=add;instance=1
Connect:source=add;sink=scale
Output:module=scale;builder=brick;bounds=30.0,30.0,5.0,5.0;destsize=23,13;edgegradientdepth=5;edgegradientelevation=0.6

This definition file has three parts: 1) node definitions, 2) connector definitions, and 3) the output definition. Essentially, Perlin and RidgedMulti noise generators are combined in the Add node, whose output is directed to a Scale node. The Output module (always required, at the end of the definition) takes the Scale module output to map to the builder and save it to a text file. Note there’s no destfile definition, more on that in a bit.

Definition files are a bit hard to read. Being they define a network, they make more sense depicted in a graphical fashion. To facilitate this, noisetool has a ‘digraph’ option that, instead of spitting out the noise stream, spits out a graph definition readable by the ‘dot’ program of graphviz. The following command line:

$ ./noisetool stone.txt digraph | dot -Tpng -o stone.png

produces a graph that looks like this:

stone.png

A few things about modules and parameters can now be explained:

The Output module deserves its own list. Parameter-by-parameter:

Note that there’s no destfile= parameter in the output node. The output node parameters can be specified on the command line, which allows a script to run it multiple times with different parameters. If a parameter is specified in both the definition file and the command line, the command line specification overrides the definition file’s specification. Any of the Output parameters can be specified this way.

So, this command, where stone.txt contains the network definition above, would generate a texture file for OpenSCAD:

$ ./noisetool stone.txt destfile=stonetexture.txt

Making Stones

I’ve found that separating the construction of stones from construction of the wall keeps the wall render from running to the end of time. So, what I do with the noise definition is to use it to assemble a collection of stone .stl files, named ‘1.stl’, ‘2.stl’, etc. I make a set of, say, 50 stones, then I select from the set to populate the wall script.

To generate the stones I use a bash script, which works with both Linux and msys2 (a Unix environment for Windows):

#!/bin/bash

for i in $(seq 1 30);
do
    bounds=$((1 + $RANDOM % 20)),$((1 + $RANDOM % 20)),15,15
    noisetool stone.txt destfile=texture.txt bounds=$bounds destsize=23,13 
    /c/Users/glenn/Documents/OpenSCAD-2025.02.11-x86-64/openscad.exe stone.scad -o $i.stl
done

The for loop runs 30 times, to produce 30 stone .stl files. The code in the loop is pretty straightforward, the bounds line constructs the bounds paramter for running noisetool, using the bash random generator. This guarantees random stones. The second line runs noisetool to make texture.txt. The third line runs openscad, using stone.scad and texture.txt to make a single stone and save it to $i.stl, where $i is incremented by the loop. Here’s the stone OpenSCAD script:

module stone() {
    translate([0,0,4]) surface("texture.txt");
    cube([22,12,4]);
}
	
stone();

Making the Wall

The wall is just an OpenSCAD script, using some of the same logic as my brick scripts to make staggered courses. The interesting part is in the stone() module, where the filename is constructed with the OpenSCAD rands() random number generator to randomly select a stone from 1-30:

module stone() {
	stonefile = str(round(rands(1,30,1)[0]),".stl");
	import(stonefile);
}

module stonepattern(width, height, brickwidth, brickheight, brickthickness, mortarwidth) {
	difference() {
		union()
		for (y = [0:1:height/(brickheight+mortarwidth)]) {
			for (x = [0:1:width/(brickwidth+mortarwidth)]) {
				t = y % 2 ? 0 : brickwidth/2;
				translate([(x*(brickwidth+mortarwidth))-t,y*(brickheight+mortarwidth),0]) 
					stone();
			}
		}
		translate([-brickwidth,-height/2,0]) cube([brickwidth,height*2,brickthickness*16]);
		translate([width,-height/2,0]) cube([brickwidth+0.1,height*2,brickthickness*16]);
	}
	translate([0,0,0]) cube([width, height, 2]);  //mortar sheet
}

scale(25.4)
	scale(1/87)
		stonepattern(8*12, 5*12+5, 23, 13, 1, .1);

Here’s the rendered wall:

stone_wall-small.png

Observations and Considerations

The above scripts make textures with 1" resolution. That might be a bit coarse for some printers; indeed, I found the above to print a little soft on the ridge crests. The resolution can be increased by making the destsize in the noisetool script bigger, say, 2x, then scaling down the texture with scale(0.5) when its loaded in the surface() module of stone.scad.

Random selection of stones in stone_wall.scad with a pool of 30 stones has a good probability of putting identical stones in close, noticeable proximity. This might be mitigated somewhat by generating a larger stone pool, but I think a better approach is to select stones sequentially, and maybe generating a pool that is as large as the quantity of a stone course in the wall.

I originally just used the RidgedMulti noise generator in the libnoise network, but this made discernable perfectly flat spots between the ridges. Combining RidgedMulti and Perlin took care of that. I didn’t mess much with either generators’ parameters; there may be improvements there not yet discovered.

Even if this whole workflow is unintelligible, one thing to take from it is that generating the stone set separately from the wall removes the texture generation burden from the wall, which would happen every time the display is rendered or the export file is saved. A very good way to make brick patterns feasible on less-than-powerful computers.

Yeah, a bit complicated.


Resources

(Top)

A .zip file with all the scripts and a set of 30 .stl files is here: stonewall.zip