Normal Map: Tangentenraum, RGB-Kanäle und der Unterschied zwischen OpenGL und DirectX
Eine Normal Map speichert pro Pixel eine Oberflächenrichtung im Tangentenraum über die drei Farbkanäle. Es gibt zwei Konventionen, die sich nur im Grün-Kanal unterscheiden: OpenGL (Y+, u. a. Blender, Godot, Unity, GIANTS Editor) und DirectX (Y-, standardmäßig bei Unreal Engine).
- Kurz gesagt
- Rot und Blau sind bei OpenGL und DirectX identisch, nur Grün ist umgekehrt
- Unsere Texturen
- _nor_gl = OpenGL-Konvention, PNG 16 Bit
- Umrechnen
- Grünkanal invertieren, z. B. mit einem kurzen Python-Skript
- GIANTS Editor / LS25
- arbeitet mit OpenGL – unsere _nor_gl-Dateien passen unverändert
Was eine Normal Map speichert
Eine Normal Map ist keine Farbtextur, auch wenn sie wie eine aussieht. Jeder Pixel speichert stattdessen einen Vektor, der angibt, in welche Richtung die Oberfläche an genau dieser Stelle zeigt. Weil ein Vektor im Raum aus drei Werten besteht, passt er exakt in die drei Kanäle Rot, Grün und Blau: Rot codiert die X-Richtung, Grün die Y-Richtung, Blau die Z-Richtung. Eine Engine liest diese Werte aus und verbiegt für die Beleuchtungsrechnung die Oberflächennormale, ohne dass am Mesh selbst auch nur ein Vertex bewegt wird. Nieten, Fugen oder Holzmaserung wirken dadurch dreidimensional, obwohl die Geometrie darunter flach bleibt.
Fast alle Normal Maps, die man kauft, herunterlädt oder selbst bakt, liegen im sogenannten Tangentenraum vor. Das ist ein lokales Koordinatensystem, das sich mit der Oberfläche mitdreht. Der Vorteil: Eine Tangentenraum-Normal-Map funktioniert auch dann noch, wenn das Objekt gedreht, skaliert oder animiert wird, weil sich die gespeicherten Richtungen immer auf die lokale Fläche beziehen und nicht auf eine feste Weltachse. Die Blender-Dokumentation nennt das den Hauptgrund, warum sich Tangentenraum als Standard durchgesetzt hat: Objektraum-Normal-Maps kleben zwar ebenfalls an der Oberfläche, reagieren aber nicht gut auf verformte Meshes, und Weltraum-Normal-Maps brechen schon bei einer einfachen Drehung des Objekts.
KI-generiertWeil ein Richtungsvektor nur Werte zwischen -1 und +1 annehmen kann, ein Bildkanal aber nur 0 bis 255 (oder bei 16 Bit 0 bis 65535) darstellt, wird der Vektor beim Speichern auf diesen Bereich gestreckt. Ein neutraler, nach außen zeigender Punkt landet dabei ungefähr in der Mitte, was der typischen blau-violetten Grundfarbe einer unbearbeiteten Normal Map ihre Farbe gibt.
Damit eine gebackene Normal Map später überhaupt richtig aussieht, müssen zwei Programme an derselben Stelle dieselbe Vorstellung von "oben" und "unten" haben: die Software, die die Karte erzeugt (Baking-Tool oder Textur-Generator), und die Software, die sie am Ende darstellt (Spiel-Engine, Renderer oder 3D-Programm). Genau diese Übereinkunft nennt man Konvention, und genau daran hakt es in der Praxis am häufigsten.
Normal Map, Bump Map und Displacement kurz erklärt
Die drei Kartentypen werden gerne verwechselt, lösen aber unterschiedliche Probleme:
Eine Bump Map ist ein einfaches Graustufenbild. Die Engine leitet daraus nur eine ungefähre Helligkeitsänderung ab, ohne echte Richtungsinformation zu haben. Das reicht für sehr feine Unruhe in der Oberfläche, wirkt bei größeren Details aber schnell flach und falsch beleuchtet.
Eine Normal Map speichert dagegen die tatsächliche Richtung der Mikro-Oberfläche. Licht reagiert dadurch deutlich glaubwürdiger auf Kanten und Vertiefungen. Die Silhouette des Objekts bleibt aber in beiden Fällen unverändert, weil keine Geometrie bewegt wird.
Eine Displacement Map geht einen Schritt weiter und verschiebt tatsächlich die Position von Vertices, meist auf Basis eines Höhenwerts. Dafür braucht das Mesh genug Unterteilung, sonst wirkt das Ergebnis grob oder eckig. Weil das Thema eigene Fallstricke hat, etwa bei der Bittiefe oder an UV-Nähten, behandeln wir Displacement Maps ausführlich in einer eigenen Anleitung.
OpenGL vs. DirectX: der Unterschied liegt im Grün-Kanal
Hier beginnt die Verwirrung, die den meisten Leuten beim Modding oder bei 3D-Projekten irgendwann begegnet: Es gibt nicht die eine Normal Map, sondern zwei Varianten, die auf den ersten Blick identisch aussehen. Der Unterschied geht auf zwei grafische Programmierschnittstellen zurück, OpenGL und DirectX, die die Y-Achse historisch in entgegengesetzte Richtungen definiert haben.
Die Blender-Dokumentation beschreibt es direkt am Normal-Map-Node: Blender verwendet standardmäßig die OpenGL-Konvention. Godot verlangt in der offiziellen Dokumentation ausdrücklich Normal Maps mit X+, Y+ und Z+, also ebenfalls OpenGL, und bietet für DirectX-Texturen eine Import-Option namens "Normal Map Invert Y" an, die genau diese eine Umrechnung übernimmt.
Praktisch bedeutet das: Rot und Blau sind in beiden Konventionen identisch. Nur Grün ist gespiegelt. Ein Pixel, der in der OpenGL-Version hell-grün ist (die Oberfläche zeigt "nach oben"), ist in der DirectX-Version derselben Textur dunkel-grün (die Oberfläche zeigt "nach unten") – bei exakt gleichem Rot- und Blauwert.
Woran man die Konvention erkennt
Ohne Metadaten in der Datei selbst hilft nur ein Blick auf das Ergebnis. Am zuverlässigsten ist der Test direkt an einem beleuchteten Modell: Kommt das Licht sichtbar von oben und ein erhabenes Detail, etwa eine Niete oder eine Kante, wirkt trotzdem wie eine Vertiefung, stimmt die Grün-Konvention der Textur nicht mit der Erwartung der Software überein. Sieht dieselbe Stelle nach dem Invertieren des Grün-Kanals plötzlich richtig erhaben aus, war genau das der Fehler.
Ein zweiter, schnellerer Hinweis: Wenn zwei sonst identische Programme dieselbe unveränderte Normal-Map-Datei unterschiedlich beleuchtet darstellen, ist so gut wie immer die Grün-Konvention die Ursache, nicht etwa ein Import- oder Farbraumfehler.
KI-generiertWie man umrechnet: Grünkanal invertieren
Die Umrechnung selbst ist simpel, weil nur ein einziger Kanal betroffen ist. Für 8-Bit-Bilder gilt: neuer Grünwert = 255 minus alter Grünwert. Bei 16-Bit-Dateien, wie unseren eigenen _nor_gl-Texturen, ersetzt man die 255 durch 65535. Rot und Blau bleiben in beiden Fällen unangetastet.
In Photoshop oder GIMP geht das über das Kanäle-Panel: den Grün-Kanal isolieren, invertieren, zurück zur RGB-Ansicht wechseln, speichern. Wer den Schritt automatisieren will, kommt mit wenigen Zeilen Python und der Bibliothek Pillow aus:
from PIL import Image
def invertiere_gruenkanal(eingabe_pfad, ausgabe_pfad):
bild = Image.open(eingabe_pfad)
kanaele = bild.split()
if len(kanaele) < 3:
raise ValueError("Datei hat keine drei Farbkanaele")
rot, gruen, blau = kanaele[0], kanaele[1], kanaele[2]
gruen_gespiegelt = gruen.point(lambda wert: 255 - wert)
ergebnis = Image.merge("RGB", (rot, gruen_gespiegelt, blau))
ergebnis.save(ausgabe_pfad)
invertiere_gruenkanal("normal_opengl.png", "normal_directx.png")
Für eine 16-Bit-PNG ersetzt man in der letzten Zeile der Funktion die 255 durch 65535 und öffnet das Bild im entsprechenden Modus. Wichtig ist nur, nicht versehentlich zweimal zu invertieren, etwa einmal in der Software selbst und einmal in der Datei – dann ist man wieder beim Ausgangszustand, sucht den Fehler aber an der falschen Stelle.
Welche Software nutzt welche Konvention?
Wer nur mit einem einzigen Programm arbeitet, muss sich um die Konvention meistens nicht kümmern, weil Baking und Darstellung dort ohnehin zusammenpassen. Das Problem entsteht beim Wechsel: Eine Normal Map, die in Blender gebacken wurde, in einer Unreal-Engine-Anwendung verwendet, oder ein Textur-Set, das ursprünglich für Unity gedacht war, in Godot eingesetzt. Genau in diesen Fällen lohnt sich der Blick in die folgende Übersicht, bevor man die Ursache für falsch aussehende Details an ganz anderer Stelle sucht. Die Tabelle fasst zusammen, was sich in den offiziellen Dokumentationen findet. Wo die Doku keine eindeutige Aussage trifft, steht das auch so da, statt eine Konvention zu vermuten.
| Software | Konvention | Beleg |
|---|---|---|
| GIANTS Editor (Landwirtschafts-Simulator 25) | OpenGL (Y+) | Eigene Erfahrung aus unserem Textur- und Modell-Workflow |
| Blender | OpenGL (Y+), Standardeinstellung | Blender Manual, Normal Map Node |
| Godot | OpenGL (Y+) verpflichtend; DirectX-Maps per Import-Option "Normal Map Invert Y" konvertierbar | Godot-Dokumentation, Importing Images |
| Unity | OpenGL (Y+); zusätzlich eine "Flip Green Channel"-Option beim Textur-Import für fremde DirectX-Maps | Unity Manual, Introduction to normal maps (bump mapping): "Unity uses Y+ normal maps, sometimes known as OpenGL format." |
| Unreal Engine | DirectX (Y-) als Standardkonvention laut Epic Games; eine "Flip Green Channel"-Texturoption rechnet fremde OpenGL-Maps um | Epic Games, Fab-Dokumentation "Setting up Assets for Fab in Launcher": "You must provide normal maps in tangent space, in the DirectX convention by default […]" |
| 3ds Max | Abhängig vom verwendeten Renderer, keine feste Software-weite Konvention dokumentiert | Laut Doku nicht eindeutig |
| Maya | Abhängig vom verwendeten Renderer, keine feste Software-weite Konvention dokumentiert | Laut Doku nicht eindeutig |
| Cinema 4D | Abhängig vom verwendeten Renderer, keine feste Software-weite Konvention dokumentiert | Laut Doku nicht eindeutig |
| SketchUp | Normal-Map-Unterstützung existiert erst seit den PBR-Materialien in aktuellen Versionen, eine dokumentierte Grün-Konvention haben wir nicht gefunden | Laut Doku nicht eindeutig |
Für 3ds Max, Maya und Cinema 4D gilt: Die Anwendung selbst schreibt keine Konvention fest vor, das entscheidet der eingesetzte Renderer (zum Beispiel Arnold, Corona oder Redshift) über eine eigene Option zum Invertieren des Grün-Kanals. Wie man das in der Praxis prüft und einstellt, zeigen wir in den jeweiligen Anleitungen. Für Blender gehen wir in einer eigenen Normal-Map-Anleitung für Blender zusätzlich auf das Backen der Karten selbst ein, einschließlich der Einstellungen, die beim Export in andere Programme wichtig werden.
GIANTS Editor, Landwirtschafts-Simulator 25 und unsere Dateien
Für unsere eigenen Produkte ist die Sache eindeutig: Der GIANTS Editor arbeitet mit der OpenGL-Konvention, und genau deshalb tragen unsere Normal Maps in den Texturpaketen die Endung _nor_gl. Sie lassen sich unverändert in den GIANTS Editor laden, ohne dass irgendein Kanal invertiert werden muss. Das gilt sowohl für die reinen Texturpakete (PNG, 16 Bit) als auch für die Normal Maps in unseren 3D-Modell-Paketen, die für den GIANTS-Import bereits als _normal.dds im BC5-Format und linearem Farbraum vorliegen.
KI-generiertFür den Weg von PNG zu DDS nutzen wir das GIANTS Texture Tool, das dem GIANTS Editor beiliegt. Für Normal Maps ist dabei das Format BC5 vorgesehen, weil es speziell für zwei-kanalige Richtungsdaten wie Rot und Grün optimiert ist und weniger Kompressionsartefakte erzeugt als generische Formate. Der lineare statt sRGB-Farbraum ist wichtig, weil Normal Maps keine Farbinformation im klassischen Sinn enthalten, sondern reine Richtungswerte, die sonst durch eine Gammakorrektur verfälscht würden.
Wer dieselben Texturen dagegen außerhalb des LS25-Modding-Umfelds einsetzt, zum Beispiel in einer Unreal-Engine-Anwendung, muss den Grün-Kanal einmal wie oben beschrieben invertieren. Für Blender, Godot oder andere OpenGL-basierte Werkzeuge ist keine Anpassung nötig.
Warum PNG und nicht JPG
Unsere Normal Maps liefern wir konsequent als PNG mit 16 Bit pro Kanal, nicht als JPG. Der Grund liegt in der Art, wie JPG Daten komprimiert: Das Format spart vor allem in Bereichen, die für das menschliche Auge weniger auffallen, etwa in feinen Farbverläufen, und rundet dafür Werte. Bei einer normalen Fototextur fällt das kaum auf. Bei einer Normal Map steckt die eigentliche Information aber genau in diesen feinen Abstufungen der einzelnen Kanäle. Rundet die Kompression sie, entstehen sichtbare Stufen oder ein leichtes Flackern der Beleuchtung bei Kamerabewegung, weil benachbarte Pixel plötzlich leicht unterschiedliche Richtungen speichern, obwohl die Oberfläche in Wirklichkeit glatt verläuft.
16 statt 8 Bit pro Kanal hilft aus demselben Grund: Ein 8-Bit-Kanal kennt nur 256 mögliche Abstufungen, ein 16-Bit-Kanal dagegen 65.536. Bei sanften, großflächigen Verläufen, wie sie normale Wand- oder Bodenstrukturen mit sich bringen, reduziert das sichtbares Banding deutlich, besonders bevor die Textur noch einmal für die Ziel-Engine komprimiert wird.
Häufige Fehler
| Problem | Ursache | Lösung |
|---|---|---|
| Details wirken wie Dellen statt wie Erhebungen | Grün-Kanal-Konvention passt nicht zur Ziel-Software (OpenGL-Map in einer DirectX-Umgebung oder umgekehrt) | Grünkanal invertieren, z. B. mit dem Kanäle-Panel in Photoshop/GIMP oder dem Python-Skript oben |
| Beleuchtung wirkt nach dem Import rau oder falsch eingefärbt | Normal Map wurde mit sRGB-Farbraum statt linear/"Non-Color" importiert | Import-Farbraum in der jeweiligen Software auf linear bzw. "Non-Color" stellen |
| An UV-Nähten sind sichtbare Streifen oder harte Kanten zu sehen | Starke verlustbehaftete Kompression (z. B. JPG) oder fehlendes Normal-Map-taugliches GPU-Format | Verlustfreies Format wie PNG verwenden und beim Export in Engines ein für Normal Maps vorgesehenes Kompressionsformat wählen |
| Dasselbe Modell sieht in zwei Programmen unterschiedlich beleuchtet aus | Beide Programme erwarten unterschiedliche Grün-Konventionen, die Datei liegt aber nur in einer Variante vor | Für jede Zielsoftware die passende Variante ablegen, etwa OpenGL für Blender/Godot/GIANTS Editor und die invertierte Version für Unreal Engine |
Häufige Fragen
Was ist der Unterschied zwischen einer Normal Map und einer Bump Map?
Eine Bump Map ist ein Graustufenbild, das nur die Helligkeit einer Oberfläche für die Beleuchtungsberechnung verändert. Eine Normal Map speichert für jeden Pixel eine echte Richtung im Raum über drei Farbkanäle und liefert dadurch genauere Lichtreflexionen, ohne dass sich die Silhouette des Objekts ändert.
Woran erkenne ich, ob eine Normal Map OpenGL oder DirectX ist?
Am einfachsten testet man es am fertigen Modell: Wirken erhabene Details wie Dellen oder umgekehrt, stimmt die Konvention nicht mit der Software überein. Rot und Blau sind in beiden Varianten identisch, nur der Grün-Kanal ist gespiegelt.
Wie invertiere ich den Grün-Kanal einer Normal Map?
In Bildbearbeitungsprogrammen wählt man den Grün-Kanal im Kanäle-Panel aus und kehrt seine Werte um. Für automatisierte Verarbeitung reicht ein kurzes Python-Skript mit Pillow, das jeden Grünwert durch 255 minus dem Ausgangswert ersetzt.
Welche Konvention nutzt der GIANTS Editor für den Landwirtschafts-Simulator 25?
Der GIANTS Editor arbeitet mit der OpenGL-Konvention. Unsere Normal Maps mit der Endung _nor_gl liegen bereits in diesem Format vor und lassen sich ohne Anpassung importieren.
Muss ich Normal Maps für Unreal Engine anpassen?
In der Regel ja. Unreal Engine erwartet meist die DirectX-Konvention, unsere Texturpakete liefern die OpenGL-Variante. Vor dem Import sollte der Grün-Kanal einmal invertiert und das Ergebnis am Modell geprüft werden.