GLSL noise

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Die noise-Funktionen der GLSL erzeugen ein stochastisches Rauschen, das dazu benutzt werden kann, die visuelle Vielfalt zu erhöhen. Die zurückgegebenen Werte sehen zufällig aus, sind allerdings nicht wirklich zufällig. Bei Rauschen ("noise") handelt es sich zudem nicht nur um einfache Zufallszahlen.

Besonderheiten

Die einzigen Treiber, die derzeit wirklich die noise-Funktionen anbieten, stammen von 3DLabs.

ATI-Treiber implementieren die noise-Funktionen gar nicht, wodurch der Shader im Softwaremodus läuft.

NVidia hat sie in OpenGL 2.0 zwar implementiert, jedoch geben sie nur 0.0 zurück.

Daher empfiehlt sich zurzeit, nicht nur aus Geschwindigkeitsgründen, statt der Verwendung dieser Funktionen, eher eine vorher erzeugte Noisetextur zu nutzen. Eine solche Textur könnte beispielsweise per Perlin Noise erzeugt werden. Für neuere Grafikkarten mit Shader Model 4.0 gibt es einen Workaround um Pseudozufallszahlen direkt im Shader zu erzeugen.

Syntax

float noise1 (genType x)
vec2 noise2 (genType x)
vec3 noise3 (genType x)
vec4 noise4 (genType x)

Die 4 Funktionen unterscheiden sich nur in der Dimension der Rückgabewerte.

Eigenschaften

Die von der GLSL-Spezifikation 1.20.8 geforderten Eigenschaften der noise-Funktionen sind:

  • Die Rückgabewerte liegen im Intervall [-1.0,1.0] und sind mindestens in [-0.6,0.6] annähernd normalverteilt.
  • Die Rückgabewerte haben insgesamt den Durchschnitt 0.0.
  • Sie sind reproduzierbar, indem ein bestimmter Eingabewert immer die gleichen Rückgabewerte erzeugt.
  • Sie sind statistisch invariant unter Rotation und Verschiebung (d. h. egal wie der Wertebereich rotiert oder verschoben wird, die Funktion hat immer das gleiche statistische Verhalten).
  • Normalerweise unterschiedliche Ergebnisse bei Verschiebung der Eingabewerte.
  • Die Ortsfrequenz befindet sich in einem eingeengten, konzentrierten Bereich mit der Mitte zwischen 0.5 und 1.0.
  • Die ersten Ableitungen der Funktionen sind überall stetig.

Workaround

Mit ShaderModel 4.0 kann man beliebig viele Pseudozufallszahlen direkt im Shader erzeugen. Man benötigt mindestens die Extensions GL_EXT_gpu_shader4 und GL_EXT_texture_integer. Diese stellen Integer-Arithmetik im Shader bereit und man kann sich ganz leicht einen lineareren Kongruenzgenerator bauen. Die Pseudozufallszahlen in C/C++/Delphi werden übrigens auf die gleiche Weise erzeugt. Im wesentlichen braucht man nur eine Multiplikation, eine Addition und einmal Modulo um von einer Zufallszahl zur nächsten zu kommen:

s := (a * s + c) mod m

Wählt man gute Werte für a, c und m erhält man mit diesen Operationen halbwegs gleich verteilte Pseudozufallszahlen. Die obigen Eigenschaften sind jedoch nicht erfüllt. Außerdem sind die so erzeugten Zahlen natürlich deterministisch. Verwendet man also den gleichen Ausgangswert s, erhält man immer die gleiche Zahlenfolge. Folglich benötigt man für jeden Vertex (bzw. Pixel) eine Textur mit zufälligen Ausgangswerten. Allerdings kann man sich aus diesem einen Wert beliebig viele weitere Zahlen erzeugen, also beispielsweise je eine für die XYZ-Achse.

Ich verwende hier einen Texturbuffer (GL_EXT_texture_buffer_object). In jedem Frame werden die vom Shader aktualisierten Seed-Werte via Transform-Feedback wieder in diese Textur geschrieben. Die CPU muss die Textur also nicht in jedem Frame neu initialisieren. Funktioniert so aber natürlich nur im Vertexshader bzw. Geometryshader. Bei dieser Methode wird die Extension GL_NV_transform_feedback oder GL_EXT_transform_feedback benötigt.

Mit klassischen Framebufferobjects kann man diese Idee wahrscheinlich ebenfalls umsetzen. Man sollte jedoch nicht auf die Idee kommen die Seedwerte als float zu speichern. Die Konvertierung in float sorgt dafür das die erzeugten Zufallszahlen sehr schnell degenerieren und nicht mehr gleich verteilt sind.

Gute Werte für a, c und m findet man z.B. bei Wikipedia. Man sollte darauf achten, dass der Zufallsgenerator, der die Textur für die Ausgangswerte initialisiert nicht den gleichen lineareren Kongruenzgenerator verwendet. Da die erzeugten Zahlen deterministisch sind, würde man sonst für alle Vertices/Pixel die gleichen, jeweils nur um eine Generation verschobenen, Zahlen erhalten.

Hier ein Beispiel für den Vertexshader:

#extension GL_EXT_gpu_shader4 : enable

uniform usamplerBuffer tboSeed;

varying unsigned int seed; // wird vom Transform-Feedback abgegriffen

float random() {
	seed = (seed * 1103515245u + 12345u);
	return float(seed) / 4294967296.0;
}

void main() {
	seed = texelFetchBuffer(tboSeed, gl_VertexID).x;

	// ...

	float randomNumber = random();

	// ...
}

Weblinks