Up to date
This page is up to date for Godot 4.3.
If you still find outdated information, please open an issue.
高級後期處理
前言
本教學描述了一種在 Godot 中進行後期處理的高級方法。值得注意的是,它將解釋如何編寫使用深度緩衝區的後期處理著色器。您應該已經熟悉後期處理,特別是使用:ref:`自訂後期處理教學 <doc_custom_postprocessing>`中介紹的方法。
全屏四邊形
One way to make custom post-processing effects is by using a viewport. However, there are two main drawbacks of using a Viewport:
無法存取深度緩衝區
在編輯器中看不到後期處理著色器的效果
要解決使用深度緩衝區的限制,請使用 MeshInstance 並設定 QuadMesh 像素。這樣我們就可以使用空間著色器,並且可以存取該場景的深度紋理。接下來,請使用頂點著色器讓這個四邊形始終覆蓋螢幕,以便始終應用後期處理效果,包括在編輯器中。
首先,新建一個 MeshInstance,並將其網格設定為 QuadMesh。這將建立一個以座標 (0, 0, 0) 為中心的四邊形,寬度和高度均為 1。請將其寬度和高度設定為 2。現在,這個四邊形在世界空間中佔據了原點的位置;但是,我們希望它能隨著相機的移動而移動,這樣它就能始終覆蓋整個螢幕。為此,我們將繞過座標轉換,該轉換通過不同的座標空間轉換頂點位置,並將頂點視為已位於裁剪空間中。
The vertex shader expects coordinates to be output in clip space, which are coordinates
ranging from -1 at the left and bottom of the screen to 1 at the top and right
of the screen. This is why the QuadMesh needs to have height and width of 2.
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 the
POSITION built-in to our desired position. POSITION bypasses the built-in transformations
and sets the vertex position in clip space directly.
shader_type spatial;
// Prevent the quad from being affected by lighting and fog. This also improves performance.
render_mode unshaded, fog_disabled;
void vertex() {
POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}
備註
In versions of Godot earlier than 4.3, this code recommended using POSITION = vec4(VERTEX, 1.0);
which implicitly assumed the clip-space near plane was at 0.0.
That code is now incorrect and will not work in versions 4.3+ as we
use a "reversed-z" depth buffer now where the near plane is at 1.0.
即使有了這樣的頂點著色器,這個四邊形仍會消失。這是因為視錐剔除的緣故,是在 CPU 上完成的。視錐剔除使用相機矩陣和 Mesh 的 AABB 來確定 Mesh 是否可見,然後再傳遞給 GPU。CPU 不知道我們對頂點做了什麼,所以它認為指定的座標指的是世界座標,而不是裁剪空間的座標,這就導致了 Godot 在我們旋轉、離開場景中心時對四邊形進行剔除。為了防止四邊形被剔除,有這麼幾個選項:
將 QuadMesh 作為子節點新增到相機,這樣相機就會始終指向它
在 QuadMesh 中將幾何屬性
extra_cull_margin設定得盡可能大
第二個選項會確保四邊形在編輯器中可見,而第一個選項能夠保證即使相機移出剔除邊緣也它仍可見。您也可以同時使用這兩個選項。
深度紋理
要讀取深度紋理,我們首先需要使用「hint_depth_texture」來建立一個設定為深度緩衝區的均勻紋理。
uniform sampler2D depth_texture : source_color, hint_depth_texture;
定義後,可以使用“texture()”函式讀取深度紋理。
float depth = texture(depth_texture, SCREEN_UV).x;
備註
與存取螢幕紋理類似,存取深度紋理只有在從目前視口讀取時才能進行。深度紋理不能從你已經算繪的另一個視口中存取。
The values returned by depth_texture are between 1.0 and 0.0 (corresponding to
the near and far plane, respectively, because of using a "reverse-z" depth buffer) and are nonlinear.
When displaying depth directly from the depth_texture, everything will look almost
black unless it is very close due to that nonlinearity. 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.
首先, 取螢幕空間座標並將其轉換為正規化裝置座標(NDC).NDC從 -1 到 1 , 類似於裁剪空間座標. 使用 SCREEN_UV 來重建NDC的 x 和 y 軸, 以及 z 的深度值.
void fragment() {
float depth = texture(depth_texture, SCREEN_UV).x;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
}
備註
This tutorial assumes the use of the Forward+ or Mobile renderers, which both
use Vulkan NDCs with a Z-range of [0.0, 1.0]. In contrast, the Compatibility
renderer uses OpenGL NDCs with a Z-range of [-1.0, 1.0]. For the Compatibility
renderer, replace the NDC calculation with this instead:
vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
通過將NDC乘以 INV_PROJECTION_MATRIX , 將NDC轉換成視圖空間. 回顧一下, 視圖空間給出了相對於相機的位置, 所以 z 值將給我們提供到該點的距離.
void fragment() {
...
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
float linear_depth = -view.z;
}
因為相機是朝向負的 z 方向的, 所以座標會有一個負的 z 值. 為了得到一個可用的深度值, 我們必須否定 view.z .
The world position can be constructed from the depth buffer using the following code, using the
INV_VIEW_MATRIX to transform the position from view space into world space.
void fragment() {
...
vec4 world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
vec3 world_position = world.xyz / world.w;
}
Example shader
Once we add a line to output to ALBEDO, we have a complete shader that looks something like this.
This shader lets you visualize the linear depth or world space coordinates, depending on which
line is commented out.
shader_type spatial;
// Prevent the quad from being affected by lighting and fog. This also improves performance.
render_mode unshaded, fog_disabled;
uniform sampler2D depth_texture : source_color, hint_depth_texture;
void vertex() {
POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}
void fragment() {
float depth = texture(depth_texture, SCREEN_UV).x;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
float linear_depth = -view.z;
vec4 world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
vec3 world_position = world.xyz / world.w;
// Visualize linear depth
ALBEDO.rgb = vec3(fract(linear_depth));
// Visualize world coordinates
//ALBEDO.rgb = fract(world_position).xyz;
}
優化
您可以使用單個大三角形而不是使用全屏四邊形. 解釋的原因在 這裡 . 但是, 這種好處非常小, 只有在運作特別複雜的片段著色器時才有用.
將MeshInstance中的Mesh設定為 ArrayMesh. ArrayMesh是一個工具, 允許您從頂點, 法線, 顏色等方便地從陣列建構網格.
現在, 將腳本附加到MeshInstance並使用以下程式碼:
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)
備註
三角形在標準化裝置座標中指定. 回想一下,NDC在 x 和 y 方向都從 -1 到 1 運作. 這使得螢幕 2 單位寬, 2 單位高. 為了用一個三角形覆蓋整個螢幕, 使用一個 4 單位寬和 4 單位高的三角形, 高度和寬度加倍.
從上面分配相同的頂點著色器, 所有內容應該看起來完全相同.
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.