高级后处理

介绍

本教程介绍了在Godot中进行后处理的高级方法。特别是,它将解释如何编写使用深度缓冲区的后处理明暗器。您应该已经熟悉一般的后处理,特别是在 custom post-processing tutorial .

在上一个后期处理教程中,我们将场景渲染为 Viewport 然后在 ViewportContainer 到主场景。此方法的一个限制是,我们无法访问深度缓冲区,因为深度缓冲区仅在空间明暗器中可用,而视区不维护深度信息。

全屏四边形

custom post-processing tutorial 我们介绍了如何使用一个视区来制作自定义后处理效果。使用视区有两个主要缺点:

  1. 无法访问深度缓冲区

  2. 后处理着色器的效果在编辑器中不可见。

要绕过使用深度缓冲区的限制,请使用 MeshInstance 用一个 QuadMesh 原始的。这允许我们使用空间着色和访问场景的深度纹理。接下来,使用顶点明暗器使四边形始终覆盖屏幕,以便在任何时候应用后处理效果,包括在编辑器中。

首先,创建一个新的网格实例并将其网格设置为四元网格。这将创建一个以位置为中心的四边形 (0, 0, 0) 宽度和高度为 1 . 将宽度和高度设置为 2 . 现在,四边形在原点的世界空间中占据了一个位置;但是,我们希望它与相机一起移动,以便它始终覆盖整个屏幕。为了做到这一点,我们将绕过坐标变换,通过差分坐标空间转换顶点位置,并将顶点视为已经在剪辑空间中。

顶点明暗器期望在剪辑空间中输出坐标,剪辑空间的坐标范围为 -1 在屏幕的左侧和底部 1 在屏幕的顶部和右侧。这就是为什么四边形网格的高度和宽度需要为 2 . Godot处理从模型到视图空间到场景背后剪辑空间的转换,因此我们需要消除Godot转换的影响。我们通过设置 POSITION 内置到我们想要的位置。 POSITION 绕过内置转换并直接设置顶点位置。

shader_type spatial;

void vertex() {
  POSITION = vec4(VERTEX, 1.0);
}

即使使用这个顶点明暗器,四元体也会不断消失。这是由于截锥剔除,这是在CPU上完成的。截锥剔除使用相机矩阵和网格的AABBS来确定网格是否可见。 之前 把它传给GPU。CPU不知道我们对顶点做了什么,所以它假定指定的坐标是指世界位置,而不是剪辑空间位置,当我们离开场景中心时,这会导致godot剔除四元。为了防止四合院被剔除,有几个选项:

  1. 将四边形作为子级添加到相机,使相机始终指向它

  2. 设置几何特性 extra_cull_margin 在四边形网格中尽可能大

第二个选项确保四元体在编辑器中可见,而第一个选项确保即使相机移动到剔除边缘之外,它仍然可见。您也可以同时使用这两个选项。

深度纹理

要读取深度纹理,请使用 texture() 以及均匀变量 DEPTH_TEXTURE .

float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;

注解

与访问屏幕纹理类似,只有在从当前视区读取时才能访问深度纹理。无法从已渲染到的另一个视区访问深度纹理。

返回的值 DEPTH_TEXTURE 介于 01 并且是非线性的。当直接从 DEPTH_TEXTURE 如果不是很近的话,所有的东西都会看起来几乎是白色的。这是因为深度缓冲区使用的位比进一步使用的位更多来存储离相机更近的对象,因此深度缓冲区中的大部分细节都位于相机附近。为了使深度值与世界或模型坐标对齐,我们需要将该值线性化。当我们将投影矩阵应用于顶点位置时,z值是非线性的,因此为了使其线性化,我们将其乘以投影矩阵的逆矩阵,在godot中,该逆矩阵可由变量访问。 INV_PROJECTION_MATRIX .

首先,将屏幕空间坐标转换为归一化设备坐标(NDC)。国家数据中心运行来源 -11 ,类似于剪辑空间坐标。使用 SCREEN_UV 对于 xy 轴和的深度值 z .

void fragment() {
  float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
  vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
}

将ndc乘以 INV_PROJECTION_MATRIX . 回想一下,视图空间给出了相对于相机的位置,因此 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 .

可以使用以下代码从深度缓冲区构造世界位置。注意, CAMERA_MATRIX 需要将位置从视图空间转换为世界空间,因此需要使用一个变量将其传递给片段明暗器。

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;
}

优化

您可以从使用单个大三角形而不是使用全屏四元菜单中获益。解释了原因 here . 但是,这样做的好处非常小,只有在运行特别复杂的片段明暗器时才有好处。

将网格实例中的网格设置为 ArrayMesh . ArrayMesh是一种工具,它允许您从数组中轻松构建顶点、法线、颜色等的网格。

现在,将脚本附加到MeshInstance并使用以下代码:

extends MeshInstance

func _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)

注解

三角形以标准化设备坐标指定。召回,国家数据中心从 -11 在两者中 xy 方向。这使得屏幕 2 单位宽度和 2 单位高。为了用一个三角形覆盖整个屏幕,请使用一个三角形 4 单位宽度和 4 单位高,双倍的高度和宽度。

从上面指定相同的顶点明暗器,所有东西看起来应该完全相同。

在使用四边形网格的基础上使用arraymesh的一个缺点是,在编辑器中不能看到arraymesh,因为只有在场景运行之后,才能构建三角形。为了解决这个问题,在一个建模程序中构建一个三角形网格,并在网格实例中使用它。