スクリーン読み取りシェーダー¶
はじめに¶
It is often desired to make a shader that reads from the same screen to which it's writing. 3D APIs, such as OpenGL or DirectX, make this very difficult because of internal hardware limitations. GPUs are extremely parallel, so reading and writing causes all sorts of cache and coherency problems. As a result, not even the most modern hardware supports this properly.
回避策は、画面または画面の一部をバックバッファにコピーし、描画中にそこから読み取ることです。 Godotには、このプロセスを簡単にするツールがいくつか用意されています。
SCREEN_TEXTURE ビルトインテクスチャ¶
Godot シェーディング言語 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);
}
背後にあるものを表示するだけなので、不可視のオブジェクトになります。
textureLodを使用する必要がある理由は、Godotが画面のチャンクをコピーバックするときに、ミップマップに対して効率的な分離可能なガウスぼかしも行うためです。
これにより、画面から読み取るだけでなく、異なる量のブラーを使用して読み取ることができます。
注釈
Mipmaps are not generated in GLES2 due to poor performance and compatibility with older devices.
SCREEN_TEXTUREの例¶
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;
}
舞台裏¶
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
.
As a result, in 2D, if shaders that use SCREEN_TEXTURE
overlap, the second one
will not use the result of the first one, resulting in unexpected
visuals:

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.
2Dでは、これは BackBufferCopy ノードを介して修正できます。これは両方の球の間でインスタンス化できます。 BackBufferCopyは、画面領域または画面全体を指定することで機能します:

バックバッファを正しくコピーすると、2つの球体が正しくブレンドされます:

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
.
バックバッファロジック¶
したがって、わかりやすくするために、Godotでのバックバッファーコピーロジックの動作を次に示します:
If a node uses the
SCREEN_TEXTURE
, the entire screen is copied to the back buffer before drawing that node. This only happens the first time; subsequent nodes do not trigger this.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 ifSCREEN_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¶
For 3D shaders, it's also possible to access the screen depth buffer. For this,
the DEPTH_TEXTURE
built-in is used. This texture is not linear; it must be
converted via the inverse projection matrix.
次のコードは、描画中のピクセルの下の3D位置を取得します:
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;
}