Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Shader-Präprozessor
Warum einen Shader-Präprozessor verwenden?
In Programmiersprachen ermöglicht ein Präprozessor die Änderung des Codes, bevor der Compiler ihn liest. Anders als der Compiler kümmert sich der Präprozessor nicht darum, ob die Syntax des vorverarbeiteten Codes gültig ist. Der Präprozessor führt immer das aus, was die Direktiven ihm vorgeben. Eine Direktive ist eine Anweisung, die mit einem Rautezeichen (#
) beginnt. Sie ist kein Schlüsselwort der Shader-Sprache (wie if
oder for
), sondern eine spezielle Art von Token innerhalb der Sprache.
Ab Godot 4.0 können Sie einen Shader-Präprozessor in textbasierten Shadern verwenden. Die Syntax ähnelt dem, was die meisten GLSL-Shader-Compiler unterstützen (was wiederum dem C/C++-Präprozessor ähnelt).
Bemerkung
Der Shader-Präprozessor ist in visuellen Shadern nicht verfügbar. Wenn Sie Präprozessoranweisungen in einen visuellen Shader einfügen müssen, können Sie ihn in einen textbasierten Shader konvertieren, indem Sie die Option In Shader konvertieren im Ressourcen-Dropdown des VisualShader-Inspektors verwenden. Diese Konvertierung ist eine Einweg-Operation; Text-Shader können nicht zurück in visuelle Shader konvertiert werden.
Direktiven
Allgemeine Syntax
Präprozessor-Direktiven verwenden keine geschweiften Klammern(
{}
), können aber runde Klammern verwenden.Präprozessor-Direktiven enden nie mit Semikolons (mit Ausnahme von
#define
, wo dies erlaubt, aber potentiell gefährlich ist).Präprozessor-Direktiven können sich über mehrere Zeilen erstrecken, indem jede Zeile mit einem Backslash (
\
) abgeschlossen wird. Der erste Zeilenumbruch, der keinen Backslash enthält, beendet die Präprozessor-Direktive.
#define
Syntax: #define <bezeichner> [ersetzungscode]
.
Definiert den Bezeichner nach dieser Direktive als Makro und ersetzt alle aufeinanderfolgenden Vorkommen durch den im Shader angegebenen Ersetzungscode. Die Ersetzung erfolgt auf der Basis "ganzer Wörter", d.h. es wird keine Ersetzung vorgenommen, wenn der String Teil eines anderen Strings ist (ohne Leerzeichen oder Operatoren als Trennzeichen).
Defines mit Ersetzungen können auch ein oder mehrere Argumente haben, die dann beim Verweis auf das Define übergeben werden können (ähnlich wie bei einem Funktionsaufruf).
Wenn der Ersetzungscode nicht definiert ist, darf der Bezeichner nur mit #ifdef
oder #ifndef
-Direktiven verwendet werden.
Wenn das Verkettungs-Symbol (##
) im Ersetzungscode vorhanden ist, wird es beim Einfügen des Makros zusammen mit einem etwaigen umgebenden Leerzeichen entfernt und die umgebenden Wörter und Argumente zu einem neuen Token zusammengefügt.
uniform sampler2D material0;
#define SAMPLE(N) vec4 tex##N = texture(material##N, UV)
void fragment() {
SAMPLE(0);
ALBEDO = tex0.rgb;
}
Im Vergleich zu Konstanten (const CONSTANT = value;
) kann #define
an jeder beliebigen Stelle innerhalb des Shaders verwendet werden (auch in Uniform-Hints). #define
kann auch verwendet werden, um beliebigen Shadercode an jeder beliebigen Stelle einzufügen, während dies bei Konstanten nicht möglich ist.
shader_type spatial;
// Notice the lack of semicolon at the end of the line, as the replacement text
// shouldn't insert a semicolon on its own.
// If the directive ends with a semicolon, the semicolon is inserted in every usage
// of the directive, even when this causes a syntax error.
#define USE_MY_COLOR
#define MY_COLOR vec3(1, 0, 0)
// Replacement with arguments.
// All arguments are required (no default values can be provided).
#define BRIGHTEN_COLOR(r, g, b) vec3(r + 0.5, g + 0.5, b + 0.5)
// Multiline replacement using backslashes for continuation:
#define SAMPLE(param1, param2, param3, param4) long_function_call( \
param1, \
param2, \
param3, \
param4 \
)
void fragment() {
#ifdef USE_MY_COLOR
ALBEDO = MY_COLOR;
#endif
}
Die Definition eines #define
für einen Bezeichner, der bereits definiert ist, führt zu einem Fehler. Um dies zu verhindern, verwenden Sie #undef <bezeichner>
.
#undef
Syntax: #undef bezeichner
Die #undef
-Direktive kann verwendet werden, um eine zuvor definierte #define
-Direktive zu beenden:
#define MY_COLOR vec3(1, 0, 0)
vec3 get_red_color() {
return MY_COLOR;
}
#undef MY_COLOR
#define MY_COLOR vec3(0, 1, 0)
vec3 get_green_color() {
return MY_COLOR;
}
// Like in most preprocessors, undefining a define that was not previously defined is allowed
// (and won't print any warning or error).
#undef THIS_DOES_NOT_EXIST
Ohne #undef
im obigen Beispiel würde es einen Fehler bei der Neudefinition des Makros geben.
#if
Syntax: #if <bedingung>
Die #if
-Direktive prüft, ob die bedingung
erfüllt ist. Wenn sie einen Wert ungleich Null ergibt, wird der Codeblock verwendet, andernfalls wird er übersprungen.
Um korrekt ausgewertet zu werden, muss die Bedingung ein Ausdruck sein, der ein einfaches Float-, Integer- oder boolesches Ergebnis liefert. Es kann mehrere Bedingungsblöcke geben, die mit den Operatoren &&
(AND) oder ||
(OR) verbunden sind. Er kann durch einen #else
-Block fortgesetzt werden, muss aber mit der Anweisung #endif
abgeschlossen werden.
#define VAR 3
#define USE_LIGHT 0 // Evaluates to `false`.
#define USE_COLOR 1 // Evaluates to `true`.
#if VAR == 3 && (USE_LIGHT || USE_COLOR)
// Condition is `true`. Include this portion in the final shader.
#endif
Mit Hilfe der defined()
Präprozessorfunktion können Sie überprüfen, ob der übergebene Bezeichner durch das #define
definiert ist, das über dieser Direktive steht. Dies ist nützlich, um mehrere Shaderversionen in der gleichen Datei zu erstellen. Sie kann durch einen #else
Block fortgesetzt werden, muss aber mit der #endif
-Direktive beendet werden.
Das Ergebnis der Funktion defined()
kann mit dem vorangestellten Symbol !
(boolesches NOT) negiert werden. Dies kann verwendet werden, um zu prüfen, ob ein Define nicht gesetzt ist.
#define USE_LIGHT
#define USE_COLOR
// Correct syntax:
#if defined(USE_LIGHT) || defined(USE_COLOR) || !defined(USE_REFRACTION)
// Condition is `true`. Include this portion in the final shader.
#endif
Seien Sie vorsichtig, da defined()
nur einen einzigen Bezeichner in Klammern einschließen darf, niemals mehr:
// Incorrect syntax (parentheses are not placed where they should be):
#if defined(USE_LIGHT || USE_COLOR || !USE_REFRACTION)
// This will cause an error or not behave as expected.
#endif
Tipp
In the shader editor, preprocessor branches that evaluate to false
(and
are therefore excluded from the final compiled shader) will appear grayed
out. This does not apply to runtime if
statements.
#if-Präprozessor vs. if-Anweisung: Performance-Hinweise
The shading language supports runtime if
statements:
uniform bool USE_LIGHT = true;
if (USE_LIGHT) {
// This part is included in the compiled shader, and always run.
} else {
// This part is included in the compiled shader, but never run.
}
Wenn das Uniform nie geändert wird, verhält sich dies genauso wie die folgende Verwendung der #if
-Präprozessoranweisung:
#define USE_LIGHT
#if defined(USE_LIGHT)
// This part is included in the compiled shader, and always run.
#else
// This part is *not* included in the compiled shader (and therefore never run).
#endif
However, the #if
variant can be faster in certain scenarios. This is because
all runtime branches in a shader are still compiled and variables within
those branches may still take up register space, even if they are never run in
practice.
Moderne GPUs sind recht effektiv bei der Durchführung von "statischen" Verzweigungen. "Statische" Verzweigungen beziehen sich auf if
-Anweisungen, bei denen alle Pixel/Vertices bei einem bestimmten Shader-Aufruf das gleiche Ergebnis liefern. Allerdings können große Mengen von VGPRs (die durch zu viele Verzweigungen verursacht werden können) die Shader-Ausführung immer noch erheblich verlangsamen.
#elif
Die Anweisung #elif
steht für "else if" und prüft die Bedingung, die übergeben wird, wenn die obige #if
als false
ausgewertet wird. #elif
kann nur innerhalb eines #if
-Blocks verwendet werden. Es ist möglich, mehrere #elif
-Anweisungen nach einer #if
-Anweisung zu verwenden.
#define VAR 2
#if VAR == 0
// Not included.
#elif VAR == 1
// Not included.
#elif VAR == 2
// Condition is `true`. Include this portion in the final shader.
#else
// Not included.
#endif
Wie bei #if
kann die Präprozessorfunktion defined()
verwendet werden:
#define SHADOW_QUALITY_MEDIUM
#if defined(SHADOW_QUALITY_HIGH)
// High shadow quality.
#elif defined(SHADOW_QUALITY_MEDIUM)
// Medium shadow quality.
#else
// Low shadow quality.
#endif
#ifdef
Syntax: #ifdef <bezeichner>
Dies ist eine Kurzform für #if defined(...)
. Prüft, ob der übergebene Bezeichner durch ein #define
definiert ist, das über dieser Direktive steht. Dies ist nützlich, um mehrere Shaderversionen in der gleichen Datei zu erzeugen. Sie kann durch einen #else
Block fortgesetzt werden, muss aber mit der #endif
Direktive beendet werden.
#define USE_LIGHT
#ifdef USE_LIGHT
// USE_LIGHT is defined. Include this portion in the final shader.
#endif
Der Prozessor unterstützt nicht #elifdef
als Abkürzung für #elif defined(...)
. Verwenden Sie stattdessen die folgende Reihe von #ifdef
und #else
, wenn Sie mehr als zwei Verzweigungen benötigen:
#define SHADOW_QUALITY_MEDIUM
#ifdef SHADOW_QUALITY_HIGH
// High shadow quality.
#else
#ifdef SHADOW_QUALITY_MEDIUM
// Medium shadow quality.
#else
// Low shadow quality.
#endif // This ends `SHADOW_QUALITY_MEDIUM`'s branch.
#endif // This ends `SHADOW_QUALITY_HIGH`'s branch.
#ifndef
Syntax: #ifndef <bezeichner>
Dies ist eine Kurzform für #if !defined(...)
. Ähnlich wie #ifdef
, prüft aber, ob der übergebene Bezeichner nicht durch #define
vor dieser Direktive definiert ist.
Dies ist das genaue Gegenteil von #ifdef
; es wird immer in Situationen passen, in denen #ifdef
niemals passen würde, und andersherum.
#define USE_LIGHT
#ifndef USE_LIGHT
// Evaluates to `false`. This portion won't be included in the final shader.
#endif
#ifndef USE_COLOR
// Evaluates to `true`. This portion will be included in the final shader.
#endif
#else
Syntax: #else
Definiert den optionalen Block, der eingeschlossen wird, wenn die zuvor definierte #if
-, #elif
-, #ifdef
- oder #ifndef
-Direktive als false ausgewertet wird.
shader_type spatial;
#define MY_COLOR vec3(1.0, 0, 0)
void fragment() {
#ifdef MY_COLOR
ALBEDO = MY_COLOR;
#else
ALBEDO = vec3(0, 0, 1.0);
#endif
}
#endif
Syntax: #endif
Wird als Endelement für die #if
, #ifdef
, #ifndef
oder nachfolgende #else
-Direktiven verwendet.
#error
Syntax: #error <message>
The #error
directive forces the preprocessor to emit an error with optional message.
For example, it's useful when used within #if
block to provide a strict limitation of the
defined value.
#define MAX_LOD 3
#define LOD 4
#if LOD > MAX_LOD
#error LOD exceeds MAX_LOD
#endif
#include
Syntax: #include "pfad"
Die #include
-Direktive schließt den gesamten Inhalt einer Shader-Include-Datei in einen Shader ein. Der pfad
kann ein absoluter res://
Pfad oder relativ zur aktuellen Shaderdatei sein. Relative Pfade sind nur in Shadern erlaubt, die in .gdshader
oder .gdshaderinc
Dateien gespeichert sind, während absolute Pfade in Shadern verwendet werden können, die in eine Szene/Ressourcendatei eingebaut sind.
Sie können neue Shader-Includes erstellen, indem Sie die Menüoption Datei > Shader-Include erstellen des Shader-Editors verwenden oder indem Sie eine neue ShaderInclude-Ressource im FileSystem-Dock erstellen.
Shader-Includes können von jedem Shader oder einem anderen Shader-Include aus an jeder beliebigen Stelle der Datei eingefügt werden.
Wenn Sie Shader-Includes in den globalen Bereich eines Shaders aufnehmen, wird empfohlen, dies nach der ersten shader_type
-Anweisung zu tun.
Sie können Shader-Includes auch innerhalb eines Funktionsrumpfs einbinden. Bitte beachten Sie, dass der Shader-Editor wahrscheinlich Fehler für den Code Ihres Shader-Includes melden wird, da er außerhalb des Kontextes, für den er geschrieben wurde, nicht gültig sein könnte. Sie können diese Fehler entweder ignorieren (der Shader wird trotzdem kompiliert), oder Sie können das Include in einen #ifdef
-Block einbinden, der auf eine Definition des Shaders prüft.
#include
ist nützlich, um Bibliotheken von Hilfsfunktionen (oder Makros) zu erstellen und Code-Duplikation zu reduzieren. Bei der Verwendung von #include
ist auf Namenskollisionen zu achten, da das Umdefinieren von Funktionen oder Makros nicht erlaubt ist.
#include
unterliegt mehreren Einschränkungen:
Nur Shader-Include-Ressourcen (mit der Endung
.gdshaderinc
) können eingebunden werden..gdshader
-Dateien können nicht von einem anderen Shader eingebunden werden, aber eine.gdshaderinc
-Datei kann andere.gdshaderinc
-Dateien einbinden.Zyklische Abhängigkeiten sind nicht erlaubt und führen zu einem Fehler.
Um eine unendliche Rekursion zu vermeiden, ist die Inklusionstiefe auf 25 Schritte begrenzt.
Beispiel für eine Shader-Include-Datei:
// fancy_color.gdshaderinc
// While technically allowed, there is usually no `shader_type` declaration in include files.
vec3 get_fancy_color() {
return vec3(0.3, 0.6, 0.9);
}
Beispiel für einen Basisshader (unter Verwendung der oben erstellten Include-Datei):
// material.gdshader
shader_type spatial;
#include "res://fancy_color.gdshaderinc"
void fragment() {
// No error, as we've included a definition for `get_fancy_color()` via the shader include.
COLOR = get_fancy_color();
}
#pragma
Syntax: #pragma wert
Die #pragma
-Direktive liefert zusätzliche Informationen an den Präprozessor oder Compiler.
Derzeit kann sie nur einen Wert haben: disable_preprocessor
. Wenn Sie den Präprozessor nicht benötigen, verwenden Sie diese Direktive, um die Shader-Kompilierung zu beschleunigen, indem Sie den Präprozessorschritt weglassen.
#pragma disable_preprocessor
#if USE_LIGHT
// This causes a shader compilation error, as the `#if USE_LIGHT` and `#endif`
// are included as-is in the final shader code.
#endif
Built-in defines
Current renderer
Since Godot 4.4, you can check which renderer is currently used with the built-in
defines CURRENT_RENDERER
, RENDERER_COMPATIBILITY
, RENDERER_MOBILE
,
and RENDERER_FORWARD_PLUS
:
CURRENT_RENDERER
is set to either0
,1
, or2
depending on the current renderer.RENDERER_COMPATIBILITY
is always0
.RENDERER_MOBILE
is always1
.RENDERER_FORWARD_PLUS
is always2
.
As an example, this shader sets ALBEDO
to a different color in each renderer:
shader_type spatial;
void fragment() {
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
ALBEDO = vec3(0.0, 0.0, 1.0);
#elif CURRENT_RENDERER == RENDERER_MOBILE
ALBEDO = vec3(1.0, 0.0, 0.0);
#else // CURRENT_RENDERER == RENDERER_FORWARD_PLUS
ALBEDO = vec3(0.0, 1.0, 0.0);
#endif
}