Up to date

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

Screen-Reading-Shader

Einführung

Es ist oft erwünscht, einen Shader zu erstellen, der von demselben Bildschirm liest, auf den er schreibt. 3D-APIs wie OpenGL oder DirectX machen dies aufgrund interner Hardwarebeschränkungen sehr schwierig. GPUs sind extrem parallel, so dass das Lesen und Schreiben alle möglichen Cache- und Kohärenzprobleme verursacht. Infolgedessen wird dies nicht einmal von der modernsten Hardware richtig unterstützt.

Der Workaround besteht darin, eine Kopie des Bildschirms oder eines Teils des Bildschirms in einen Hintergrundpuffer zu erstellen und dann beim Zeichnen daraus zu lesen. Godot bietet einige Tools, die diesen Prozess vereinfachen.

Bildschirmtextur

Godot Shader-Sprache hat eine spezielle Textur, um auf den bereits gerenderten Inhalt des Bildschirms zuzugreifen. Sie wird durch die Angabe eines Hints bei der Deklaration einer sampler2D-Uniform verwendet: hint_screen_texture. Ein spezielles Built-in-Varying SCREEN_UV kann verwendet werden, um die UV relativ zum Bildschirm für das aktuelle Fragment zu erhalten. Das Ergebnis dieses canvas_item Fragment-Shaders ist ein unsichtbares Objekt, da er nur das zeigt, was dahinter liegt:

shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

void fragment() {
    COLOR = textureLod(screen_texture, SCREEN_UV, 0.0);
}

textureLod wird hier verwendet, da wir nur die untere Mipmap auslesen wollen. Wenn Sie stattdessen von einer unscharfen Version der Textur lesen wollen, können Sie das dritte Argument auf textureLod erhöhen und den Hint filter_nearest auf filter_nearest_mipmap ändern (oder jeden anderen Filter mit aktivierten Mipmaps). Wenn Sie einen Filter mit Mipmaps verwenden, wird Godot automatisch die unscharfe Textur für Sie berechnen.

Warnung

Wenn der Filtermodus nicht in einen Filtermodus geändert wird, der mipmap in seinem Namen enthält, hat textureLod mit einem LOD-Parameter größer als 0.0 das gleiche Aussehen wie mit dem LOD-Parameter 0.0.

Beispiel für eine Bildschirmtextur

Die Bildschirmtextur kann für viele Dinge verwendet werden. Es gibt eine spezielle Demo für Screen Space-Shader, die Sie herunterladen können, um sie anzuschauen und daraus zu lernen. Ein Beispiel ist ein einfacher Shader zur Anpassung von Helligkeit, Kontrast und Sättigung:

shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

uniform float brightness = 1.0;
uniform float contrast = 1.0;
uniform float saturation = 1.0;

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;
}

Hinter den Kulissen

Obwohl dies wie Magie erscheint, ist es das nicht. In 2D, wenn hint_screen_texture zum ersten Mal in einem Node gefunden wird, der gerade gezeichnet werden soll, kopiert Godot den gesamten Bildschirm in einen Hintergrund-Puffer. Nachfolgende Nodes, die ihn in Shadern verwenden, werden den Bildschirm nicht kopieren, da dies ineffizient ist. In 3D wird der Bildschirm nach dem Durchlauf der undurchsichtigen Geometrie, aber vor dem Durchlauf der transparenten Geometrie kopiert, so dass transparente Objekte nicht in der Bildschirmtextur erfasst werden.

Wenn sich Shader, die hint_screen_texture verwenden, in 2D überlappen, wird der zweite Shader nicht das Ergebnis des ersten verwenden, was zu unerwarteten visuellen Effekten führt:

../../_images/texscreen_demo1.png

Im obigen Bild verwendet die zweite Kugel (oben rechts) die gleiche Quelle für die Bildschirmtextur wie die erste darunter, so dass die erste Kugel "verschwindet" oder nicht sichtbar ist.

In 2D kann dies durch den Node BackBufferCopy korrigiert werden, der zwischen beiden Kugeln instanziiert werden kann. BackBufferCopy kann entweder durch Angabe eines Bildschirmbereichs oder des gesamten Bildschirms arbeiten:

../../_images/texscreen_bbc.png

Bei richtigem Kopieren des Hintergrundpuffers zeigen die beiden Kugeln ein korrektes Blending:

../../_images/texscreen_demo2.png

Warnung

In 3D werden Materialien, die hint_screen_texture verwenden, selbst als transparent angesehen und erscheinen nicht in der resultierenden Bildschirmtextur anderer Materialien. Wenn Sie eine Szene instanziieren wollen, die ein Material mit hint_screen_texture verwendet, müssen Sie einen BackBufferCopy-Node verwenden.

In 3D gibt es weniger Möglichkeiten, dieses Problem zu lösen, da die Bildschirmtextur nur einmal erfasst wird. Seien Sie vorsichtig, wenn Sie die Bildschirmtextur in 3D verwenden, da sie keine transparenten Objekte erfasst und möglicherweise einige undurchsichtige Objekte erfasst, die sich vor dem Objekt befinden, das die Bildschirmtextur verwendet.

Sie können die Hintergrund-Puffer-Logik in 3D reproduzieren, indem Sie einen Viewport mit einer Kamera an der gleichen Position wie Ihr Objekt erstellen und dann die Textur des Viewports anstelle der Bildschirmtextur verwenden.

Hintergrundpuffer-Logik

Zur Verdeutlichung: So funktioniert die Hintergrundpuffer-Kopierlogik in 2D in Godot:

  • Wenn ein Node hint_screen_texture verwendet, wird der gesamte Bildschirm in den Hintergrundpuffer kopiert, bevor dieser Knoten gezeichnet wird. Dies geschieht nur beim ersten Mal; nachfolgende Nodes lösen dies nicht aus.

  • Wenn ein BackBufferCopy-Node vor der Situation im obigen Punkt verarbeitet wurde (auch wenn hint_screen_texture nicht verwendet wurde), tritt das im obigen Punkt beschriebene Verhalten nicht auf. Mit anderen Worten, das automatische Kopieren des gesamten Bildschirms geschieht nur, wenn hint_screen_texture zum ersten Mal in einem Node verwendet wird und vorher kein BackBufferCopy-Node (nicht deaktiviert) in der Baumreihenfolge gefunden wurde.

  • BackBufferCopy kann entweder den gesamten Bildschirm oder einen Bereich kopieren. Wenn nur ein Bereich (nicht der gesamte Bildschirm) kopiert wird und Ihr Shader Pixel verwendet, die nicht in dem kopierten Bereich liegen, ist das Ergebnis des Lesevorgangs undefiniert (höchstwahrscheinlich Müll aus früheren Frames). Mit anderen Worten, es ist möglich, BackBufferCopy zu verwenden, um einen Bereich des Bildschirms zurückzukopieren und dann die Bildschirmtextur aus einem anderen Bereich zu lesen. Vermeiden Sie dieses Verhalten!

Tiefentextur

Für 3D-Shader ist es auch möglich, auf den Tiefenpuffer des Bildschirms zuzugreifen. Hierfür wird der Hint hint_depth_texture verwendet. Diese Textur ist nicht linear; sie muss mit Hilfe der inversen Projektionsmatrix konvertiert werden.

Mit dem folgenden Code wird die 3D-Position unter dem gezeichneten Pixel ermittelt:

uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;

void fragment() {
    float depth = textureLod(depth_texture, SCREEN_UV, 0.0).r;
    vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth, 1.0);
    vec3 pixel_position = upos.xyz / upos.w;
}

Normalen-Rauheits-Textur

Bemerkung

Normalen-Rauheits-Textur wird nur in der Forward+ Rendering-Methode unterstützt, nicht in Mobile oder Kompatibilität.

In ähnlicher Weise kann die Normalen-Rauheits-Textur verwendet werden, um die Normalen und die Rauheit von Objekten zu lesen, die im Tiefenvordurchlauf gerendert wurden. Die Normalen werden in den .xyz-Kanälen gespeichert (auf den Bereich 0-1 abgebildet), während die Rauheit im .w-Kanal gespeichert wird.

uniform sampler2D normal_roughness_texture : hint_normal_roughness_texture, repeat_disable, filter_nearest;

void fragment() {
    float screen_roughness = texture(normal_roughness_texture, SCREEN_UV).w;
    vec3 screen_normal = texture(normal_roughness_texture, SCREEN_UV).xyz;
    screen_normal = screen_normal * 2.0 - 1.0;

Neudefinition von Bildschirmtexturen

Die Bildschirmtextur-Hints (hint_screen_texture, hint_depth_texture und hint_normal_roughness_texture) können mit mehreren Uniforms verwendet werden. Zum Beispiel kann es sein, dass Sie die Textur mehrmals mit einem anderen Wiederholungsflag oder Filterflag lesen wollen.

Das folgende Beispiel zeigt einen Shader, der die Screen Space-Normale mit linearer Filterung, die Screen Space-Rauheit jedoch mit Nearest Neighbour-Filterung ausliest.

uniform sampler2D normal_roughness_texture : hint_normal_roughness_texture, repeat_disable, filter_nearest;
uniform sampler2D normal_roughness_texture2 : hint_normal_roughness_texture, repeat_enable, filter_linear;

void fragment() {
    float screen_roughness = texture(normal_roughness_texture, SCREEN_UV).w;
    vec3 screen_normal = texture(normal_roughness_texture2, SCREEN_UV).xyz;
    screen_normal = screen_normal * 2.0 - 1.0;