Zum Inhalt springen

Rechnen mit WGSL

WGSL bietet verschiedene Rechenoperatoren und mathematische Funktionen, mit denen wir Zahlen, Vektoren und Matrizen verarbeiten können.

Viele dieser Operationen kennen wir bereits aus unseren Beleuchtungsberechnungen. In diesem Abschnitt schauen wir uns ihre Schreibweise und ihre Ergebnisse genauer an.

Die grundlegenden Rechenoperatoren sind uns aus JavaScript bekannt:

Operator Bedeutung Beispiel
+ Addition a + b
- Subtraktion a - b
* Multiplikation a * b
/ Division a / b
% Rest einer Division a % b

Wir können damit einzelne Zahlen berechnen:

WGSL
let a = 10.0;
let b = 4.0;
let sum = a + b; // 14.0
let difference = a - b; // 6.0
let product = a * b; // 40.0
let quotient = a / b; // 2.5

Der Operator - kann auch das Vorzeichen eines Wertes umkehren:

WGSL
let direction = vec3f(1.0, 0.0, 0.0);
let opposite = -direction;

opposite enthält vec3f(-1.0, 0.0, 0.0) und zeigt damit in die entgegengesetzte Richtung.

Die Rechenoperatoren funktionieren auch mit Vektoren. Bei der Addition und Subtraktion werden jeweils die entsprechenden Komponenten verrechnet.

WGSL
let a = vec3f(1.0, 2.0, 3.0);
let b = vec3f(4.0, 5.0, 6.0);
let sum = a + b;
let difference = a - b;

sum enthält vec3f(5.0, 7.0, 9.0), difference enthält vec3f(-3.0, -3.0, -3.0).

Beide Vektoren müssen dafür gleich viele Komponenten besitzen.

Multiplizieren wir einen Vektor mit einer einzelnen Zahl, wird jede Komponente mit dieser Zahl multipliziert:

WGSL
let color = vec3f(0.8, 0.4, 0.2);
let darkerColor = color * 0.5;

Das Ergebnis ist vec3f(0.4, 0.2, 0.1).

Auch zwei gleich große Vektoren können wir miteinander multiplizieren:

WGSL
let a = vec3f(1.0, 2.0, 3.0);
let b = vec3f(4.0, 5.0, 6.0);
let result = a * b;

Dabei werden die entsprechenden Komponenten miteinander multipliziert. result enthält deshalb vec3f(4.0, 10.0, 18.0).

Das ist eine komponentenweise Multiplikation. Sie unterscheidet sich von der Multiplikation von Matrizen, bei der andere Rechenregeln gelten.

Auch Matrizen können wir mit den Rechenoperatoren verarbeiten.

Bei der Addition und Subtraktion werden die entsprechenden Elemente zweier gleich großer Matrizen verrechnet.

Bei der Multiplikation können wir eine Matrix mit einer Zahl, einem Vektor oder einer weiteren Matrix multiplizieren.

Ein einfaches Beispiel ist die Multiplikation einer 2×2-Matrix mit einem Vektor:

WGSL
let matrix = mat2x2f(
vec2f(2.0, 0.0),
vec2f(0.0, 3.0)
);
let position = vec2f(4.0, 5.0);
let result = matrix * position;

Das Ergebnis ist vec2f(8.0, 15.0). Die Matrix hat die erste Komponente mit 2.0 und die zweite mit 3.0 skaliert.

Die Matrixmultiplikation ist also nicht einfach eine komponentenweise Multiplikation. Wie sie mathematisch funktioniert und wie wir damit Objekte transformieren, behandeln wir später ausführlich.

Neben den Rechenoperatoren stellt WGSL zahlreiche eingebaute Funktionen bereit.

Einige arbeiten mit einzelnen Zahlen, andere mit Vektoren. Wir können sie direkt verwenden, ohne sie selbst definieren zu müssen.

Mit dot() berechnen wir das Skalarprodukt zweier Vektoren. Das Ergebnis ist eine einzelne Zahl.

WGSL
let a = vec3f(1.0, 0.0, 0.0);
let b = vec3f(0.0, 1.0, 0.0);
let result = dot(a, b);

Das Ergebnis ist 0.0, weil die beiden Vektoren senkrecht aufeinanderstehen.

Bei zwei Vektoren gleicher Richtung mit der Länge 1.0 ergibt das Skalarprodukt dagegen 1.0.

Wir haben dot() bereits im Phong-Shader verwendet, um zu berechnen, wie stark eine Oberfläche vom Licht getroffen wird:

WGSL
let diffuseFactor = max(dot(normal, lightDirection), 0.0);

Die Funktion cross() berechnet das Kreuzprodukt zweier vec3-Vektoren.

WGSL
let a = vec3f(1.0, 0.0, 0.0);
let b = vec3f(0.0, 1.0, 0.0);
let result = cross(a, b);

Das Ergebnis ist vec3f(0.0, 0.0, 1.0). Der neue Vektor steht senkrecht auf den beiden Ausgangsvektoren.

Das Kreuzprodukt ist beispielsweise nützlich, um aus zwei Richtungen eine dazu senkrechte Richtung zu berechnen.

Mit normalize() berechnen wir einen Vektor, der dieselbe Richtung wie der ursprüngliche Vektor besitzt, aber die Länge 1.0 hat.

WGSL
let direction = vec3f(3.0, 0.0, 0.0);
let normalizedDirection = normalize(direction);

normalizedDirection enthält vec3f(1.0, 0.0, 0.0).

Solche normierten Vektoren verwenden wir häufig bei Beleuchtungsberechnungen, damit die Länge eines Richtungsvektors das Ergebnis nicht verfälscht.

Die Funktion reflect() berechnet die Spiegelungsrichtung eines Vektors an einer Oberfläche.

WGSL
let incoming = vec3f(0.0, 0.0, -1.0);
let normal = vec3f(0.0, 0.0, 1.0);
let reflected = reflect(incoming, normal);

Das Ergebnis ist vec3f(0.0, 0.0, 1.0).

reflect() kennen wir ebenfalls aus unserem Phong-Shader. Dort verwenden wir die Spiegelungsrichtung, um den Glanzlichtanteil zu berechnen.

WGSL bietet viele weitere mathematische Funktionen. Einige davon werden wir regelmäßig benötigen:

Funktion Bedeutung
abs(x) Absolutwert
min(a, b) Kleinerer Wert
max(a, b) Größerer Wert
clamp(x, min, max) Wert auf einen Bereich begrenzen
pow(x, y) Potenz berechnen
sqrt(x) Quadratwurzel berechnen
length(v) Länge eines Vektors
distance(a, b) Abstand zwischen zwei Vektoren
sin(x) Sinus
cos(x) Kosinus

Viele dieser Funktionen können sowohl mit einzelnen Zahlen als auch mit Vektoren arbeiten.

Beispielsweise begrenzt clamp() einen Wert auf einen bestimmten Bereich:

WGSL
let brightness = clamp(1.4, 0.0, 1.0);

Da 1.4 oberhalb der angegebenen Obergrenze liegt, ergibt sich 1.0.

Auch pow() haben wir bereits verwendet:

WGSL
let specular = pow(specularFactor, uniforms.shininess);

Mit dieser Potenzberechnung bestimmen wir im Phong-Reflexionsmodell, wie stark sich das Glanzlicht auf einen kleinen Bereich konzentriert.

WGSL bietet Rechenoperatoren für Zahlen, Vektoren und Matrizen. Bei Vektoren werden viele Operationen komponentenweise ausgeführt, während die Matrixmultiplikation eigenen Regeln folgt.

Zusätzlich stehen eingebaute mathematische Funktionen zur Verfügung. Besonders häufig verwenden wir in der Grafikprogrammierung dot(), cross(), normalize() und reflect().

Diese Funktionen begegnen uns bei Beleuchtungsberechnungen, Transformationen und vielen weiteren Aufgaben immer wieder.