Holz, Fliesen, Marmor, Mauerwerk: Eine Textur muss kein Foto sein. Man kann sie auch ausrechnen, für jeden Punkt einer Fläche aus seiner Stelle, mit Zufall, der immer gleich bleibt, mit Rauschen, mit Zellen und mit Formen. Hier steht, wie das geht, wie eine Textur nahtlos wird, wie sie sich gar nicht mehr wiederholt und warum feine Linien in der Ferne flimmern. Jedes Beispiel läuft auf dieser Seite, in GLSL, und lässt sich ändern.
Eine prozedurale Textur ist kein gespeichertes Bild, sondern ein Rezept: eine Funktion, die für jede Stelle einer Fläche ausrechnet, welche Farbe sie hat. Das hat Vorteile:
Es gibt zwei Wege. Entweder malt ein Programm das Rezept einmal in ein Bild, etwa mit einem canvas im Browser, und das Bild kommt wie ein Foto auf die Fläche, Kachel für Kachel. Oder der Fragment-Shader rechnet das Rezept beim Zeichnen aus, für jeden Pixel, jedes Mal. Die Beispiele hier gehen den zweiten Weg. In jedem steht eine Funktion color(p): p ist die Stelle auf der Fläche in Metern, das Ergebnis ihre Farbe, Rot, Grün und Blau von 0 bis 1.
Ein Shader kennt keinen Zufall, und er soll auch keinen haben: Würde eine Fliese bei jedem Bild ihre Farbe wechseln, flackerte die Wand. Gebraucht wird eine Zahl, die zufällig aussieht, aber für dieselbe Stelle immer dieselbe ist. Das macht eine Hash-Funktion: Sie rührt die Bits einer Zahl so durcheinander, dass zwei benachbarte Eingaben ganz verschiedene Ergebnisse geben.
float hash(vec2 p) { vec3 q = fract(vec3(p.xyx) * 0.1031); q += dot(q, q.yzx + 33.33); return fract((q.x + q.y) * q.z); }
hash gibt eine Zahl zwischen 0 und 1. Diese stammt von Dave Hoskins, »Hash without Sine«: Viele Beispiele im Netz nehmen fract(sin(…) * 43758.5), doch das gibt auf manchen Grafikkarten Muster statt Zufall. Gibt man ihr die Nummer eines Felds, floor(p * 4.0), bekommt jedes Feld von 25 cm seinen eigenen Ton:
Ändere die 4.0: Wie groß werden die Felder? Mit return vec3(t, 0.5, 1.0 - t); sind sie bunt.
floor(p * 10.0) ist die Nummer einer Fliese von 10 cm, fract(p * 10.0) die Stelle in ihr, von 0 bis 1. Nahe 0 oder 1 liegt der Rand: dort die Fuge. Und jede Fliese bekommt aus ihrer Nummer einen etwas anderen Ton:
Wie versetzt man jede zweite Reihe um eine halbe Fliese, wie bei Mauersteinen? Tipp: Schieb p.x um 5 cm, wenn mod(floor(p.y * 10.0), 2.0) 1 ist, bevor du die Nummer der Fliese ausrechnest. Die Lösung steht im nächsten Abschnitt.
Eine Mauer aus Ziegeln ist eine Fliesenwand mit zwei Unterschieden. Die Steine sind länger als hoch, hier 24 cm × 7,1 cm mit 1 cm Fuge. Und jede zweite Reihe ist um einen halben Stein verschoben, damit keine Fuge durch die ganze Wand läuft (Läuferverband). Dafür kommt zuerst die Nummer der Reihe, dann die Stelle in ihr, schon verschoben:
Die Zeile mit shade ist ein kleiner Trick: Der Mörtel liegt etwas tiefer als die Steine, also fällt von oben Schatten in die Fuge. Ist ihre obere Hälfte dunkler als die untere, sieht die Wand gleich weniger flach aus, obwohl sie es ist.
Ersetze 0.5 * mod(row, 2.0) durch 0.25 * row: Jede Reihe rutscht um einen Viertelstein weiter, und die Fugen laufen schräg über die Wand. Und was wird aus der Mauer, wenn du shade überall auf 0.95 setzt?
mod(id.x + id.y, 2.0) ist für die Felder eines Schachbretts abwechselnd 0 und 1: Geht man ein Feld nach rechts oder nach oben, ändert sich die Summe um eins. Damit lässt sich mehr machen als Schwarz und Weiß. Teppichfliesen im Büro liegen oft so, dass jede quer zu ihren Nachbarn liegt, damit man die Nähte nicht sieht. Ihre Rippen laufen in der einen Fliese entlang x, in der nächsten entlang y:
Dasselbe Muster ohne Rippen, nur mit zwei Farben, ist ein Schachbrett. Wie man es zeichnet, ohne dass es in der Ferne flimmert, steht unten bei »Flimmern«.
Nicht jedes Muster ist ein Raster aus Rechtecken. Beim Fischgrätparkett liegen kurze Stäbe abwechselnd längs und quer: ein Längsstab und an seinem rechten Ende ein Querstab nach unten, das ist ein Paar. Das nächste Paar liegt eine Stabbreite schräg darüber, so entsteht eine Treppe, und viele Treppen nebeneinander füllen die Fläche. Die Paare liegen also auf einem schiefen Gitter mit den Schritten (1, 1) und (n, -n), in Stabbreiten, wenn ein Stab n-mal so lang wie breit ist.
Für einen Punkt rechnet man aus, in welcher Masche dieses Gitters er liegt (die beiden Schritte als Koordinaten, abgerundet), und prüft dann diese Masche und ihre Nachbarn: Liegt der Punkt, von ihrem Paar aus gesehen, im Längs- oder im Querstab? Ein Paar ragt weit über seine Masche hinaus, deshalb geht die Suche ein paar Maschen zurück. Der Stab, in dem der Punkt liegt, gibt den Ton (hash) und wie weit es bis zur nächsten Fuge ist:
Ändert man N auf 4, werden die Stäbe kürzer; die Suche muss dann weniger weit zurück, schadet aber nicht. Ohne die Drehung (mat2) läuft das Zickzack schräg.
Zufall pro Feld gibt Kanten. Für Wolken, Flecken und Stein braucht es Zufall ohne Kanten: Rauschen (englisch Noise). Auf den Ecken eines Gitters liegt je eine Zufallszahl aus hash, und dazwischen wird weich gemischt:
float noise(vec2 p) { vec2 i = floor(p), f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f); // weich: am Rand flach, wie smoothstep return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x), mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y); }
Ken Perlin (geboren 1954), Informatiker in New York. Nach den Computerbildern für den Film Tron (1982) störte ihn, wie glatt und sauber Oberflächen aus dem Computer aussahen, und er erfand das Rauschen, das seinen Namen trägt. Dafür bekam er 1997 einen Oscar für technische Leistungen.
Wolken, Feuer und ganze Landschaften in Spielen wie Minecraft entstehen aus Perlin-Rauschen und seinen Verwandten.
Foto: Ambuj Saxena, 2007, CC BY 2.5, zugeschnitten (Wikimedia Commons)
Weil mix mit u statt mit f mischt, gibt es an den Gitterlinien keinen Knick. Diese Art heißt Value Noise; Ken Perlin rechnete 1985 statt Zahlen Richtungen auf die Ecken, das gibt das bekanntere Perlin-Rauschen, ähnlich, nur gleichmäßiger.
Turbulenz. Dasselbe Rauschen in mehreren Größen, jede doppelt so fein und halb so stark wie die vorige, zusammengezählt, sieht aus wie die Natur: groß und klein zugleich. Man sagt auch fraktales Rauschen.
float turbulence(vec2 p) { float sum = 0.0, size = 1.0; for (int i = 0; i < 4; i++) { sum += noise(p * size) / size; size *= 2.0; } return sum / 1.875; // 1 + 1/2 + 1/4 + 1/8: wieder 0 bis 1 }
Marmor, wie Ken Perlin ihn 1985 in »An Image Synthesizer« beschrieb: Ein Sinus quer über die Fläche gibt gerade Streifen; eine Ader ist dort, wo er nahe null ist. Zählt man die Turbulenz dazu, verbiegen sich die Streifen zu Adern.
Was ändert t * 6.0, wenn du 6 durch 15 ersetzt? Und was 0.12?
Holz: Bretter, 12,5 cm breit, jedes in seinem Ton aus hash, und eine Maserung aus einem Sinus quer über das Brett, mit Rauschen gebogen:
Terrazzo ist ein Boden aus gebrochenen Steinchen in Zement. Die Steinchen sind eckig wie Scherben und liegen dicht an dicht. Dafür liegt in jedem Feld eines Gitters ein Punkt an einer zufälligen Stelle, und jeder Punkt der Fläche gehört zu dem Punkt, der ihm am nächsten liegt. So zerfällt die Fläche in Zellen mit geraden Kanten, ein Voronoi-Diagramm. Gesucht wird nur in den neun Feldern um die Stelle herum, denn weiter weg liegt kein Punkt, der näher sein könnte.
Merkt man sich den Abstand zum nächsten Punkt (f1) und zum zweitnächsten (f2), weiß man auch, wo eine Kante liegt: Dort sind beide fast gleich weit weg. Wo f2 - f1 klein ist, liegt Zement, sonst ein Steinchen, und jedes bekommt aus der Nummer seines Felds eine Farbe:
Steven Worley hat diese Art Rauschen 1996 beschrieben; es heißt nach ihm Worley Noise oder zelluläres Rauschen. Mit return vec3(f1); sieht man die Abstände selbst: In jeder Zelle wird es zur Mitte hin dunkel, wie bei Zellen unter dem Mikroskop, Schuppen oder Kieseln. Mit return vec3(f2 - f1); bleiben nur die Kanten.
A Cellular Texture Basis Function, Steven Worley, SIGGRAPH 1996. Auf vier Seiten das zelluläre Rauschen als Ergänzung zu Perlins Rauschen, mit Bildern von Fliesen, Haut, zerknülltem Papier, Eis und Gestein: bei der ACM.
Ändere 0.2 in step(0.2, f2 - f1): Bei 0.05 liegen die Steinchen dicht an dicht, bei 0.5 schwimmen sie im Zement. Was passiert, wenn du in at statt vec2(hash(c), hash(c + 17.0)) nur vec2(0.5) schreibst?
Löcher, Pfeile, ein Etikett: Auch Formen sind Rechnungen. Eine Form ist die Menge der Stellen, für die eine Bedingung gilt. Ein Kreis ist alles, was näher als sein Radius an seiner Mitte liegt, also length(p - mitte) < r. Ein Rechteck ist alles, was in x zwischen zwei Werten liegt und in y auch. Zwei Formen zugleich (und) ist ihr Produkt, eine von beiden (oder) ihr Maximum. Ein Dreieck ist ein Band, das nach oben schmaler wird: seine halbe Breite hängt von y ab.
Damit die Ränder nicht treppig werden, ist jede Bedingung einen Pixel breit weich, wie bei den Fugen unten unter »Flimmern«. Hier Löcher auf einem Raster, wie die Anker in einer Wand aus Beton, und ein Pfeil:
Was eine Form hat, kann sie aus hash bekommen: Ein Etikett, das auf jeder Kiste woanders klebt und anders groß ist, ist ein Rechteck, dessen Ecken aus der Nummer der Kiste kommen. Für viele Formen gibt es fertige Abstandsfunktionen (Signed Distance Functions), die für jede Stelle sagen, wie weit sie vom Rand der Form entfernt ist, außen positiv, innen negativ.
Mach aus 0.7 in der Spitze 1.5: Wie ändert sich der Pfeil? Und wie bekommt er einen zweiten, etwas weiter rechts? Tipp: max aus zwei Pfeilen.
Malt man eine Textur in ein Bild, sagen wir ein Quadrat von einem Meter, und legt es auf einer Wand nebeneinander, muss es am rechten Rand genau so weitergehen, wie es links anfängt, und unten wie oben. Sonst sieht man an jeder Wiederholung eine Naht. Ein Bild mit dieser Eigenschaft heißt nahtlos (englisch seamless) oder kachelbar.
Für Muster ist das leicht: Streifen und Rauten müssen nur glatt in die Breite des Bilds aufgehen. Für Rauschen nimmt man ein Gitter, das sich selbst wiederholt: Die Ecke hinter dem rechten Rand bekommt dieselbe Zufallszahl wie die am linken. Dafür genügt mod auf der Nummer der Ecke. Hier mit 4 Gitterfeldern je Kachel, und die roten Linien zeigen, wo eine Kachel endet:
Lösche mod(…, n) an einer Stelle, oder ersetze das zweite 4.0 durch 3.0: Wo bricht das Muster an den roten Linien ab?
Nahtlos ist nicht genug. Auf einer großen Wand sieht man ein Muster, das sich jeden Meter wiederholt, auch ohne Naht: derselbe Fleck, immer wieder, in einer Reihe. Ein Shader braucht das Bild gar nicht. Er rechnet das Rezept aus der Stelle in der Welt aus, und keine Stelle kommt zweimal vor. Jedes Brett hat eine eigene Nummer, also einen eigenen Ton; das Rauschen läuft über die ganze Wand, und Flecken liegen, wo sie wollen. Das kostet nicht mehr: Die Rechnung ist für jeden Pixel dieselbe, wie groß die Fläche auch ist.
Rechnet man in drei Dimensionen, mit vec3 statt vec2, laufen die Adern eines Marmors sogar über die Ecke von der Wand auf den Boden weiter, wie bei einem echten Block aus Stein.
Feine Linien machen Ärger, wenn die Fläche weit weg ist. Ein Pixel misst dann mehr als eine Fuge breit ist, und ob er die Fuge trifft oder nicht, entscheidet ein winziger Unterschied: Beim Bewegen springen die Fugen hin und her, die Fläche flimmert, und es entstehen Muster, die es gar nicht gibt (Moiré). Schieb den Regler unter dem ersten Bild nach rechts:
Die Lösung ist fwidth: Es sagt, wie viel sich ein Wert von einem Pixel zum nächsten ändert, hier also, wie viel Fläche ein Pixel zeigt. Ist er breiter als die Fuge, zeichnet man sie nicht ganz oder gar nicht, sondern so stark, wie sie den Pixel füllt. Aus der Nähe bleibt sie scharf, in der Ferne wird sie zu einem gleichmäßigen, blassen Grau:
Ein Schachbrett hat dasselbe Problem an jeder Kante zwischen Schwarz und Weiß. sin(q * 3.14159) wechselt an jeder Kante das Vorzeichen; smoothstep macht daraus 0 oder 1, über einen Pixel weich. Weit weg, wo ein Pixel viele Felder zeigt, wird daraus ein gleichmäßiges Grau statt eines flimmernden Musters:
Dasselbe gilt für die Maserung im Holz, die Adern im Marmor und die Steinchen im Terrazzo: Werden sie feiner als ein Pixel, lässt man sie verblassen, statt sie flimmern zu lassen, und zeigt ihre mittlere Farbe.
fwidth in der Referenz der Khronos Group