Bildschirmlesender-Shader

Einführung

Sehr oft ist es 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 Hardwareeinschränkungen sehr schwierig. GPUs sind extrem parallel, sodass das Lesen und Schreiben alle möglichen Cache- und damit verbundene Probleme verursacht. Infolgedessen unterstützt dies nicht einmal die modernste Hardware ordnungsgemäß.

Die Problemumgehung besteht darin, eine Kopie des Bildschirms oder eines Teils des Bildschirms in einen Rückpuffer zu erstellen und dann beim Zeichnen daraus zu lesen. Godot bietet einige Tools, die diesen Prozess vereinfachen!

SCREEN_TEXTURE eingebaute Textur

Godot Shader-Sprache has a special texture, SCREEN_TEXTURE (and DEPTH_TEXTURE for depth, in the case of 3D). It takes as argument the UV of the screen and returns a vec3 RGB with the color. A special built-in varying: SCREEN_UV can be used to obtain the UV for the current fragment. As a result, this simple canvas_item fragment shader:

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

führt zu einem unsichtbaren Objekt, da es nur zeigt was dahinter steckt.

Der Grund, warum texturLod verwendet werden muss liegt darin, dass Godot beim Zurückkopieren eines Teils des Bildschirms auch eine effiziente, trennbare Gaußsche Unschärfe für seine MipMaps ausführt.

Dies ermöglicht nicht nur das Lesen vom Bildschirm, sondern auch das kostenlose Lesen davon mit unterschiedlichen Unschärfen.

Bemerkung

MipMaps werden in GLES2 aufgrund der schlechten Leistung und Kompatibilität mit älteren Geräten nicht generiert.

SCREEN_TEXTURE Beispiel

SCREEN_TEXTURE can be used for many things. There is a special demo for Screen Space Shaders, that you can download to see and learn. One example is a simple shader to adjust brightness, contrast and saturation:

shader_type canvas_item;

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

While this seems magical, it's not. In 2D, the SCREEN_TEXTURE built-in, when first found in a node that is about to be drawn, does a full-screen copy to a back-buffer. Subsequent nodes that use it in shaders will not have the screen copied for them, because this ends up being inefficient. In 3D, the screen is copied after the opaque geometry pass, but before the transparent geometry pass, so transparent objects will not be captured in the SCREEN_TEXTURE.

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

../../_images/texscreen_demo1.png

In the above image, the second sphere (top right) is using the same source for SCREEN_TEXTURE as the first one below, so the first one "disappears", or is not visible.

In 2D, this can be corrected via the BackBufferCopy node, which can be instantiated between both spheres. BackBufferCopy can work by either specifying a screen region or the whole screen:

../../_images/texscreen_bbc.png

Bei richtigem Kopieren des Rückpuffers zeigen die beiden Kugeln einen korrekten Übergang:

../../_images/texscreen_demo2.png

In 3D, there is less flexibility to solve this particular issue because the SCREEN_TEXTURE is only captured once. Be careful when using SCREEN_TEXTURE in 3D as it won't capture transparent objects and may capture some opaque objects that are in front of the object.

You can reproduce the back-buffer logic in 3D by creating a Viewport with a camera in the same position as your object, and then use the Viewport's texture instead of SCREEN_TEXTURE.

Rückpufferlogik

Um es verständlicher zu machen, funktioniert die Rückpuffer-Kopierlogik in Godot folgendermaßen:

  • Wenn ein Node SCREEN_TEXTURE verwendet, wird der gesamte Bildschirm in den Backpuffer kopiert, noch bevor dieser Node gezeichnet wird. Dies geschieht nur beim ersten Mal; nachfolgende Nodes lösen dies nicht aus.
  • If a BackBufferCopy node was processed before the situation in the point above (even if SCREEN_TEXTURE was not used), the behavior described in the point above does not happen. In other words, automatic copying of the entire screen only happens if SCREEN_TEXTURE is used in a node for the first time and no BackBufferCopy node (not disabled) was found before in tree-order.
  • BackBufferCopy can copy either the entire screen or a region. If set to only a region (not the whole screen) and your shader uses pixels not in the region copied, the result of that read is undefined (most likely garbage from previous frames). In other words, it's possible to use BackBufferCopy to copy back a region of the screen and then use SCREEN_TEXTURE on a different region. Avoid this behavior!

DEPTH_TEXTURE

Bei 3D-Shadern ist es auch möglich, auf den Bildschirmtiefenpuffer zuzugreifen. Hierzu wird das integrierte DEPTH_TEXTURE verwendet. Diese Textur ist nicht linear; es muss über die inverse Projektionsmatrix konvertiert werden.

Der folgende Code ruft die 3D-Position unter dem gezeichneten Pixel ab:

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 * 2.0 - 1.0, 1.0);
    vec3 pixel_position = upos.xyz / upos.w;
}