GLSL noise: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (Miniformatierung und ergänzung)
(Workaround: Pseudozufallszahlen mit Shader Model 4.0)
Zeile 8: Zeile 8:
 
'''NVidia''' hat sie in OpenGL 2.0 zwar implementiert, jedoch geben sie nur 0.0 zurück.
 
'''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 [[Techniken_und_Algorithmen#Prozeduale_Grafiken|Noisetextur]] zu nutzen. Eine solche Textur könnte beispielsweise per [[Perlin Noise]] erzeugt werden.
+
Daher empfiehlt sich zurzeit, nicht nur aus Geschwindigkeitsgründen, statt der Verwendung dieser Funktionen, eher eine vorher erzeugte [[Techniken_und_Algorithmen#Prozeduale_Grafiken|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|Workaround]] um Pseudozufallszahlen direkt im Shader zu erzeugen.
  
 
== Syntax ==
 
== Syntax ==
Zeile 27: Zeile 27:
 
* Die Ortsfrequenz befindet sich in einem eingeengten, konzentrierten Bereich mit der Mitte zwischen 0.5 und 1.0.
 
* 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.
 
* 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 [http://en.wikipedia.org/wiki/Linear_congruential_generator 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|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 [[Tutorial_Framebufferobject|Framebufferobjects]] kann man sich aber sicher auch was basteln.
 +
 +
[http://en.wikipedia.org/wiki/Linear_congruential_generator#LCGs_in_common_use 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 ==
 
== Weblinks ==

Version vom 1. Februar 2009, 12:21 Uhr

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 sich aber sicher auch was basteln.

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