Up to date
This page is up to date for Godot 4.2
.
If you still find outdated information, please open an issue.
Post procesamiento avanzado¶
Introducción¶
Este tutorial describe un método avanzado para el post-procesamiento en Godot. En particular, explicará cómo escribir un shader de post-procesamiento que utilice el buffer de profundidad. Ya deberías estar familiarizado con el post-procesamiento en general y, en particular, con los métodos descritos en el tutorial de post-procesamiento personalizado.
In the previous post-processing tutorial, we rendered the scene to a Viewport and then rendered the Viewport in a SubViewportContainer to the main scene. One limitation of this method is that we could not access the depth buffer because the depth buffer is only available in shaders and Viewports do not maintain depth information.
Quad de pantalla completa¶
En el tutorial custom post-processing, cubrimos cómo usar un Viewport para hacer efectos de post-procesamiento personalizados. Hay dos inconvenientes principales de usar un Viewport:
No se puede acceder al buffer de profundidad
El efecto del shader de post-procesamiento no es visible en el editor
To get around the limitation on using the depth buffer, use a MeshInstance3D with a QuadMesh primitive. This allows us to use a shader and to access the depth texture of the scene. Next, use a vertex shader to make the quad cover the screen at all times so that the post-processing effect will be applied at all times, including in the editor.
First, create a new MeshInstance3D and set its mesh to a QuadMesh. This creates
a quad centered at position (0, 0, 0)
with a width and height of 1
. Set
the width and height to 2
and enable Flip Faces. Right now, the quad
occupies a position in world space at the origin. However, we want it to move
with the camera so that it always covers the entire screen. To do this, we will
bypass the coordinate transforms that translate the vertex positions through the
difference coordinate spaces and treat the vertices as if they were already in
clip space.
El sombreador de vértices espera que se emitan coordenadas en el espacio de los clips, que son coordenadas que van desde 1
a la izquierda y la parte inferior de la pantalla hasta 1
en la parte superior y la derecha de la pantalla. Por eso la malla cuádruple necesita tener una altura y un ancho de 2
. Godot maneja la transformación de modelo a espacio de vista para recortar el espacio detrás de las escenas, por lo que necesitamos anular los efectos de las transformaciones de Godot. Lo hacemos poniendo la POSITION
incorporada en nuestra posición deseada. La POSITION
evita las transformaciones incorporadas y establece la posición del vértice directamente.
shader_type spatial;
void vertex() {
POSITION = vec4(VERTEX, 1.0);
}
Incluso con este shader de vértices, el quad sigue desapareciendo. Esto se debe a la recolección de frustum, que se hace en la CPU. El Frustum culling utiliza la matriz de la cámara y el AABB de las mallas para determinar si la malla será visible antes de pasarla a la GPU. La CPU no tiene conocimiento de lo que estamos haciendo con los vértices, por lo que asume que las coordenadas especificadas se refieren a las posiciones del mundo, no a las posiciones del espacio de recorte, lo que resulta en la selección de Godot en el quad cuando nos alejamos del centro de la escena. Para evitar que el quad sea eliminado, hay algunas opciones:
Añade la QuadMesh como un hijo a la cámara, para que la cámara siempre esté apuntando a ella
Establecer la propiedad de Geometría "extra_cull_margin" tan grande como sea posible en el QuadMesh
La segunda opción asegura que el quad sea visible en el editor, mientras que la primera garantiza que seguirá siendo visible incluso si la cámara se mueve fuera del margen de selección. También puedes usar ambas opciones.
Textura de profundidad (depth)¶
To read from the depth texture, we first need to create a texture uniform set to the depth buffer
by using hint_depth_texture
.
uniform sampler2D depth_texture : source_color, hint_depth_texture;
Once defined, the depth texture can be read with the texture()
function.
float depth = texture(depth_texture, SCREEN_UV).x;
Nota
De manera similar al acceso a la textura de la pantalla, el acceso a la textura de profundidad sólo es posible cuando se lee desde el puerto de visualización actual. No se puede acceder a la textura de profundidad desde otra ventana de visualización en la que se haya renderizado.
The values returned by depth_texture
are between 0.0
and 1.0
and are nonlinear.
When displaying depth directly from the depth_texture
, everything will look almost
white unless it is very close. This is because the depth buffer stores objects closer
to the camera using more bits than those further, so most of the detail in depth
buffer is found close to the camera. In order to make the depth value align with world or
model coordinates, we need to linearize the value. When we apply the projection matrix to the
vertex position, the z value is made nonlinear, so to linearize it, we multiply it by the
inverse of the projection matrix, which in Godot, is accessible with the variable
INV_PROJECTION_MATRIX
.
Firstly, take the screen space coordinates and transform them into normalized device
coordinates (NDC). NDC run -1.0
to 1.0
in x
and y
directions and
from 0.0
to 1.0
in the z
direction when using the Vulkan backend.
Reconstruct the NDC using SCREEN_UV
for the x
and y
axis, and
the depth value for z
.
Nota
This tutorial assumes the use of the Vulkan renderer, which uses NDCs with a Z-range
of [0.0, 1.0]
. In contrast, OpenGL uses NDCs with a Z-range of [-1.0, 1.0]
.
void fragment() {
float depth = texture(depth_texture, SCREEN_UV).x;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
}
En primer lugar, tomar las coordenadas del espacio de la pantalla y transformarlas en coordenadas normalizadas del dispositivo (NDC). Las NDC van de -1
a 1
, similares a las coordenadas del espacio de recorte. Reconstruye el NDC usando SCREEN_UV
para el eje x
y y
, y el valor de profundidad para z
.
void fragment() {
...
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
float linear_depth = -view.z;
}
Debido a que la cámara está orientada hacia la dirección z
negativa, la posición tendrá un valor z
negativo. Para obtener un valor de profundidad utilizable, tenemos que negar view.z
.
The world position can be constructed from the depth buffer using the following code. Note
that the INV_VIEW_MATRIX
is needed to transform the position from view space into world space, so
it needs to be passed to the fragment shader with a varying.
varying mat4 CAMERA;
void vertex() {
CAMERA = INV_VIEW_MATRIX;
}
void fragment() {
...
vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
vec3 world_position = world.xyz / world.w;
}
Una optimización¶
Puedes beneficiarte de usar un solo triángulo grande en lugar de usar un cuadrángulo de pantalla completa. La razón de esto se explica aquí. Sin embargo, el beneficio es bastante pequeño y sólo beneficioso cuando se ejecutan shaders de fragmentos especialmente complejos.
Set the Mesh in the MeshInstance3D to an ArrayMesh. An ArrayMesh is a tool that allows you to easily construct a Mesh from Arrays for vertices, normals, colors, etc.
Now, attach a script to the MeshInstance3D and use the following code:
extends MeshInstance3D
func _ready():
# Create a single triangle out of vertices:
var verts = PackedVector3Array()
verts.append(Vector3(-1.0, -1.0, 0.0))
verts.append(Vector3(-1.0, 3.0, 0.0))
verts.append(Vector3(3.0, -1.0, 0.0))
# Create an array of arrays.
# This could contain normals, colors, UVs, etc.
var mesh_array = []
mesh_array.resize(Mesh.ARRAY_MAX) #required size for ArrayMesh Array
mesh_array[Mesh.ARRAY_VERTEX] = verts #position of vertex array in ArrayMesh Array
# Create mesh from mesh_array:
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array)
Nota
The triangle is specified in normalized device coordinates.
Recall, NDC run from -1.0
to 1.0
in both the x
and y
directions. This makes the screen 2
units wide and 2
units
tall. In order to cover the entire screen with a single triangle, use
a triangle that is 4
units wide and 4
units tall, double its
height and width.
Asigne el mismo shader de vértice desde arriba y todo debería verse exactamente igual.
The one drawback to using an ArrayMesh over using a QuadMesh is that the ArrayMesh is not visible in the editor because the triangle is not constructed until the scene is run. To get around that, construct a single triangle Mesh in a modeling program and use that in the MeshInstance3D instead.