Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Shader Style-Guide

Dieser Style Guide listet Konventionen zum Schreiben eleganter Shader auf. Er soll zu sauberem, lesbarem Code ermutigen und Konsistenz in Projekten, Diskussionen und Anleitungen fördern. Dies wird hoffentlich auch zur Entwicklung automatisierter Formatierungswerkzeuge beitragen.

Da die Godot-Shader-Sprache den Sprachen im C-Stil und GLSL sehr nahe steht, ist diese Anleitung von der Godot-eigenen GLSL-Formatierung inspiriert. Ein Beispiel für eine GLSL-Datei finden Sie im Quellcode von Godot hier.

Style Guides sind nicht als dogmatisch einzuhaltende Regelwerke zu verstehen. Einige der folgenden Richtlinien sind möglicherweise nicht stets anwendbar. In diesem Fall sollte nach bestem Wissen und Gewissen gehandelt und ggf. Erfahrungswerte bei anderen Entwicklern eingeholt werden.

Im Allgemeinen ist es wichtiger, Ihren Code in Ihren Projekten und in Ihrem Team konsistent zu halten, als dieser Anleitung blind zu folgen.

Bemerkung

Der in Godot integrierte Shader-Editor verwendet standardmäßig viele dieser Konventionen. Nutzen Sie ihn.

Hier ist ein vollständiges Shader-Beispiel, das auf diesen Richtlinien basiert:

shader_type canvas_item;
// Screen-space shader to adjust a 2D scene's brightness, contrast
// and saturation. Taken from
// https://github.com/godotengine/godot-demo-projects/blob/master/2d/screen_space_shaders/shaders/BCS.gdshader

uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
uniform float brightness = 0.8;
uniform float contrast = 1.5;
uniform float saturation = 1.8;

void fragment() {
    vec3 c = textureLod(screen_texture, SCREEN_UV, 0.0).rgb;

    c.rgb = mix(vec3(0.0), c.rgb, brightness);
    c.rgb = mix(vec3(0.5), c.rgb, contrast);
    c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation);

    COLOR.rgb = c;
}

Formatierung

Encoding und Sonderzeichen

  • Verwenden Sie Line Feed (LF) für Zeilenumbrüche, nicht CRLF oder CR. (Default im Editor)

  • Verwenden Sie ein Line Feed am Ende jeder Datei. (Default im Editor)

  • Verwenden Sie die UTF-8-Encoding ohne ein byte order mark. (Default im Editor)

  • Verwenden Sie Tabs anstelle von Leerzeichen für Einrückungen. (Default im Editor)

Einrückung

Jede Einrückungsstufe sollte um eins größer sein als die des umgebenden Blocks.

Gut:

void fragment() {
    COLOR = vec3(1.0, 1.0, 1.0);
}

Schlecht:

void fragment() {
        COLOR = vec3(1.0, 1.0, 1.0);
}

Verwenden Sie 2 Einrückungsebenen, um Fortsetzungszeilen von normalen Codeblöcken zu unterscheiden.

Gut:

vec2 st = vec2(
        atan(NORMAL.x, NORMAL.z),
        acos(NORMAL.y));

Schlecht:

vec2 st = vec2(
    atan(NORMAL.x, NORMAL.z),
    acos(NORMAL.y));

Zeilenumbrüche und Leerzeilen

Als allgemeine Einrückungsregel gilt der "1TBS-Style", der empfiehlt, die zu einer Steueranweisung gehörende geschweifte Klammer auf derselben Zeile zu platzieren. Verwenden Sie für Anweisungen immer geschweifte Klammern, auch wenn sie sich nur über eine Zeile erstrecken. Dies erleichtert das Refactoring und vermeidet Fehler beim Hinzufügen weiterer Zeilen zu einer if-Anweisung oder ähnlichem.

Gut:

void fragment() {
    if (true) {
        // ...
    }
}

Schlecht:

void fragment()
{
    if (true)
        // ...
}

Leerzeilen

Vor und nach einer Funktion bzw. Klassendefinition sollte eine Leerzeile stehen (und nur eine):

void do_something() {
    // ...
}

void fragment() {
    // ...
}

Verwenden Sie eine (und nur eine) Leerzeile innerhalb von Funktionen, um logische Abschnitte zu trennen.

Zeilenlänge

Halten Sie einzelne Codezeilen unter 100 Zeichen.

Falls möglich, versuchen Sie Zeilen unter 80 Zeichen zu halten. Dies hilft beim Lesen des Codes auf kleinen Displays und mit zwei nebeneinander geöffneten Shadern in einem externen Texteditor. Zum Beispiel bei der Betrachtung einer Unterschieds-Revision.

Eine Anweisung pro Zeile

Kombinieren Sie niemals mehrere Anweisungen in einer einzigen Zeile.

Gut:

void fragment() {
    ALBEDO = vec3(1.0);
    EMISSION = vec3(1.0);
}

Schlecht:

void fragment() {
    ALBEDO = vec3(1.0); EMISSION = vec3(1.0);
}

Die einzige Ausnahme dieser Regel ist der tenäre Operator:

void fragment() {
     bool should_be_white = true;
     ALBEDO = should_be_white ? vec3(1.0) : vec3(0.0);
 }

Leerzeichen bei Kommentaren

Reguläre Kommentare sollten mit einem Leerzeichen beginnen, nicht aber auskommentierter Code. Dies hilft, Kommentare von inaktivem Code zu unterscheiden.

Gut:

// This is a comment.
//return;

Schlecht:

//This is a comment.
// return;

Verwenden Sie keine mehrzeilige Kommentarsyntax, wenn Ihr Kommentar in eine einzelne Zeile passt:

/* This is another comment. */

Bemerkung

Drücken Sie im Shader-Editor Strg + K um den ausgewählten Code zu einem Kommentar zu machen (oder den Kommentar zu entfernen). Diese Funktion fügt am Anfang der ausgewählten Zeilen // hinzu oder entfernt sie.

Leerzeichen

Setzen Sie immer ein Leerzeichen, um Operatoren herum und nach Kommas. Vermeiden Sie auch zusätzliche Leerzeichen in Funktionsaufrufen.

Gut:

COLOR.r = 5.0;
COLOR.r = COLOR.g + 0.1;
COLOR.b = some_function(1.0, 2.0);

Schlecht:

COLOR.r=5.0;
COLOR.r = COLOR.g+0.1;
COLOR.b = some_function (1.0,2.0);

Verwenden Sie keine Leerzeichen, um Ausdrücke vertikal auszurichten:

ALBEDO.r   = 1.0;
EMISSION.r = 1.0;

Float-Zahlen

Geben Sie immer mindestens eine Ziffer für den Vorkommateil und den Nachkommateil an. Dies erleichtert die Unterscheidung zwischen Float-Zahlen und Integer-Zahlen, sowie die Unterscheidung von Zahlen größer als 1 von Zahlen kleiner als 1.

Gut:

void fragment() {
    ALBEDO.rgb = vec3(5.0, 0.1, 0.2);
}

Schlecht:

void fragment() {
    ALBEDO.rgb = vec3(5., .1, .2);
}

Zugriff auf Vektorelemente

Verwenden Sie r, g, b und a beim Zugriff auf die Member eines Vektors, wenn dieser eine Farbe enthält. Wenn der Vektor etwas anderes als eine Farbe enthält, verwenden Sie x, y, z und w. Dadurch können diejenigen, die Ihren Code lesen, besser verstehen, was die zugrunde liegenden Daten darstellen.

Gut:

COLOR.rgb = vec3(5.0, 0.1, 0.2);

Schlecht:

COLOR.xyz = vec3(5.0, 0.1, 0.2);

Namenskonventionen

Diese Namenskonventionen folgen dem Godot Engine-Stil. Bei Nichtbeachtung würde Ihr Code mit den Built-in-Namenskonventionen kollidieren, was zu inkonsistentem Code führt.

Funktionen und Variablen

Verwenden Sie snake_case, um Funktionen und Variablen zu benennen:

void some_function() {
     float some_variable = 0.5;
}

Konstanten

Schreiben Sie Konstanten mit CONSTANT_CASE, d.h. in Großbuchstaben mit einem Unterstrich (_) zur Trennung der Wörter:

const float GOLDEN_RATIO = 1.618;

Präprozessor-Direktiven

Shader-Präprozessor-Direktiven sollten in CONSTANT__CASE geschrieben werden. Direktiven sollten ohne Einrückung vor ihnen geschrieben werden, auch wenn sie in einer Funktion verschachtelt sind.

Um den natürlichen Fluss der Einrückung zu erhalten, wenn Shader-Fehler auf der Konsole ausgegeben werden, sollte keine zusätzliche Einrückung innerhalb von #if, #ifdef oder #ifndef Blöcken hinzugefügt werden:

Gut:

#define HEIGHTMAP_ENABLED

void fragment() {
    vec2 position = vec2(1.0, 2.0);

#ifdef HEIGHTMAP_ENABLED
    sample_heightmap(position);
#endif
}

Schlecht:

#define heightmap_enabled

void fragment() {
    vec2 position = vec2(1.0, 2.0);

    #ifdef heightmap_enabled
        sample_heightmap(position);
    #endif
}

Code-Reihenfolge

Wir empfehlen, Shader-Code auf diese Weise aufzubauen:

01. shader type declaration
02. render mode declaration
03. // docstring

04. uniforms
05. constants
06. varyings

07. other functions
08. vertex() function
09. fragment() function
10. light() function

Wir haben die Reihenfolge optimiert, um das Lesen des Codes von oben nach unten zu vereinfachen, Entwicklern das erste Lesen des Codes zu erleichtern und Fehler im Zusammenhang mit der Reihenfolge der Variablendeklarationen zu vermeiden.

Diese Code-Reihenfolge folgt zwei Faustregeln:

  1. Zuerst Metadaten und Propertys, gefolgt von Methoden.

  2. "Public" steht vor "private". Im Kontext einer Shader-Sprache bezieht sich "public" auf das, was vom Benutzer leicht angepasst werden kann (Uniforms).

Lokale Variablen

Deklarieren Sie lokale Variablen so nah wie möglich am Ort ihrer ersten Verwendung. Dies erleichtert das Lesen des Codes, ohne zu viel scrollen zu müssen, um herauszufinden, wo die Variable deklariert wurde.