Advanced post-processing
Introduction
custom post-processing tutorial. Viewport and then rendered the Viewport in a ViewportContainer 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 spatial shaders and Viewports do not maintain depth information.
Full screen quad
custom post-processing tutorial, we covered how to use a Viewport to make custom post-processing effects. There are two main drawbacks of using a Viewport:
- The depth buffer cannot be accessed
- The effect of the post-processing shader is not visible in the editor
MeshInstance with a QuadMesh primitive. This allows us to use a spatial 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.
(0, 0, 0)with a width and height of1. Set the width and height to2. 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.-1at the left and bottom of the screen to1at the top and right of the screen. This is why the QuadMesh needs to have height and width of2. Godot handles the transform from model to view space to clip space behind the scenes, so we need to nullify the effects of Godot’s transformations. We do this by setting thePOSITIONbuilt-in to our desired position.POSITIONbypasses the built-in transformations and sets the vertex position directly.
before passing it to the GPU. The CPU has no knowledge of what we are doing with the vertices, so it assumes the coordinates specified refer to world positions, not clip space positions, which results in Godot culling the quad when we turn away from the center of the scene. In order to keep the quad from being culled, there are a few options:shader_type spatial;void vertex() { POSITION = vec4(VERTEX, 1.0);}
- Add the QuadMesh as a child to the camera, so the camera is always pointed at it
extra_cull_marginas large as possible in the QuadMesh The second option ensures that the quad is visible in the editor, while the first option guarantees that it will still be visible even if the camera moves outside the cull margin. You can also use both options.Depth texture
texture()and the uniform variableDEPTH_TEXTURE.
Note Similar to accessing the screen texture, accessing the depth texture is only possible when reading from the current viewport. The depth texture cannot be accessed from another viewport to which you have rendered.float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
DEPTH_TEXTUREare between0and1and are nonlinear. When displaying depth directly from theDEPTH_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 variableINV_PROJECTION_MATRIX.-1to1, similar to clip space coordinates. Reconstruct the NDC usingSCREEN_UVfor thexandyaxis, and the depth value forz.void fragment() { float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;}
INV_PROJECTION_MATRIX. Recall that view space gives positions relative to the camera, so thezvalue will give us the distance to the point.void fragment() { ... vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0); view.xyz /= view.w; float linear_depth = -view.z;}
zdirection, the position will have a negativezvalue. In order to get a usable depth value, we have to negateview.z.CAMERA_MATRIXis 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 = CAMERA_MATRIX;}void fragment() { ... vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0); vec3 world_position = world.xyz / world.w;}
An optimization
here. However, the benefit is quite small and only beneficial when running especially complex fragment shaders. 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 MeshInstance and use the following code:
Noteextends MeshInstancefunc _ready(): # Create a single triangle out of vertices: var verts = PoolVector3Array() 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)
-1to1in both thexandydirections. This makes the screen2units wide and2units tall. In order to cover the entire screen with a single triangle, use a triangle that is4units wide and4units tall, double its height and width. Assign the same vertex shader from above and everything should look exactly the same. 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 modelling program and use that in the MeshInstance instead.
