NEKOPARTY STUDIO

Godot自定义后处理

字数统计: 1.3k阅读时长: 6 min
2025/10/16
loading

在Godot更新4.3版本之后,新增的Compositor类终于提供了一种比较优雅但是不一定更快的方式去做全屏后处理。但这个类比较晦涩难懂,使用起来也能感到处处不便,毕竟调用的API相对底层。但由于其过度强大的功能,为了用得爽就不得不用。本文提供了我自己使用这个类的快捷方法打包。

Compositor参考类模板

这里提供一个我自己好使的参考类模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@tool
class_name LensFlare
extends CompositorEffect

var rd : RenderingDevice

var shader : RID
var shader_layer2 : RID
var shader_layer3 : RID
var pipeline : RID
var pipeline_layer2 : RID
var pipeline_layer3 : RID

var texture_1_rid : RID
var texture_2_rid : RID

var linear_sampler : RID
var nearest_sampler : RID

var context : StringName = "layer1"
var texture_name_1 : StringName = "name1"
var texture_name_2 : StringName = "name2"

@export var texture1 : Texture2D = load("...")
@export var texture2 : Texture2D = load("...")

对于比较复杂的后处理,可能需要进行多层操作。因此这边提供了多个shader和pipeline变量用于使用。

有三个用于获取RDUniform的快捷函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func get_image_uniform(image : RID, binding : int = 0) -> RDUniform:
var uniform : RDUniform = RDUniform.new()
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
uniform.binding = binding
uniform.add_id(image)

return uniform


func get_sampler_uniform(image : RID, binding : int = 0, linear : bool = true) -> RDUniform:
var uniform : RDUniform = RDUniform.new()
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
uniform.binding = binding
if linear:
uniform.add_id(linear_sampler)
else:
uniform.add_id(nearest_sampler)
uniform.add_id(image)

return uniform

func get_texture_sampler_uniform(image : RID, binding : int = 0, linear : bool = true) -> RDUniform:
var uniform : RDUniform = RDUniform.new()
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
uniform.binding = binding
if linear:
uniform.add_id(linear_sampler)
else:
uniform.add_id(nearest_sampler)
uniform.add_id(RenderingServer.texture_get_rd_texture(image))

return uniform

这里有两个获取sampler的函数。前一个用来生成直接从RenderingDevice里面取的Buffer的Sampler,后一个用来获取从外部加载的Texture的RDUniform。

注意,外部Texture不用每帧更新。

以下是初始化代码,加载对应的shader生成pipeline,并且初始化了对应的sampler采样模式。销毁代码也包括了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE and shader.is_valid():
RenderingServer.free_rid(shader)

func _init() -> void:
RenderingServer.call_on_render_thread(initialize_compute_shader)

func initialize_compute_shader() -> void:
rd = RenderingServer.get_rendering_device()
if not rd:
return

var glsl_file : RDShaderFile = load("path/to/shader")
shader = rd.shader_create_from_spirv(glsl_file.get_spirv())
pipeline = rd.compute_pipeline_create(shader)

glsl_file = load("path/to/shader")
shader_layer1 = rd.shader_create_from_spirv(glsl_file.get_spirv())
pipeline_layer1 = rd.compute_pipeline_create(shader_layer1)

glsl_file = load("path/to/shader")
shader_layer2 = rd.shader_create_from_spirv(glsl_file.get_spirv())
pipeline_layer2 = rd.compute_pipeline_create(shader_layer2)

texture_1_rid = texture1.get_rid()
texture_2_rid = texture2.get_rid()

var sampler_state : RDSamplerState = RDSamplerState.new()
sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
linear_sampler = rd.sampler_create(sampler_state)

sampler_state = RDSamplerState.new()
sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_NEAREST
sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_NEAREST
nearest_sampler = rd.sampler_create(sampler_state)

实际的处理过程发生在渲染回调线程里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
func _render_callback(effect_callback_type: int, render_data: RenderData) -> void:
if not rd:
return

var scene_buffers : RenderSceneBuffersRD = render_data.get_render_scene_buffers()
if not scene_buffers:
return

var size : Vector2i = scene_buffers.get_internal_size()
if size.x == 0 or size.y == 0:
return

if scene_buffers.has_texture(context, texture_name_1):
scene_buffers.clear_context(context)

if !scene_buffers.has_texture(context, texture_name_1):
var usage_bits : int = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice.TEXTURE_USAGE_STORAGE_BIT
scene_buffers.create_texture(context, texture_name_1, RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT, usage_bits, RenderingDevice.TEXTURE_SAMPLES_1, size, 1, 1, true, true)
scene_buffers.create_texture(context, texture_name_2, RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT, usage_bits, RenderingDevice.TEXTURE_SAMPLES_1, size, 1, 1, true, true)

for view in scene_buffers.get_view_count():
var screen_texture : RID = scene_buffers.get_color_layer(view)
var texture_layer_1 : RID = scene_buffers.get_texture_slice(context, texture_name_1, view, 0, 1, 1)
var texture_layer_2 : RID = scene_buffers.get_texture_slice(context, texture_name_2, view, 0, 1, 1)

var uniform : RDUniform = get_image_uniform(texture_layer_1, 0)
var image_uniform_set : RID = UniformSetCacheRD.get_cache(shader, 0, [uniform])

uniform = get_sampler_uniform(screen_texture, 0)
var sampler_uniform_set : RID = UniformSetCacheRD.get_cache(shader, 1, [uniform])

uniform = get_sampler_uniform_extra(texture_2_rid, 0)
var sampler_uniform_set_2 : RID = UniformSetCacheRD.get_cache(shader, 2, [uniform])


var push_constants : PackedFloat32Array = PackedFloat32Array()

var inv_proj_mat := render_data.get_render_scene_data().get_view_projection(view).inverse()

var x_groups : int = size.x / 16 + 1
var y_groups : int = size.y / 16 + 1

push_constants.append(size.x)
push_constants.append(size.y)
push_constants.append(0.0)
...
(下面这个只是我把projection推送到array的过程打包了以下)
add_projection_to_array(push_constants, inv_proj_mat)

var compute_list : int = rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
rd.compute_list_bind_uniform_set(compute_list, image_uniform_set, 0)
rd.compute_list_bind_uniform_set(compute_list, sampler_uniform_set, 1)
rd.compute_list_bind_uniform_set(compute_list, sampler_uniform_set_2, 2)
#rd.compute_list_bind_uniform_set(compute_list, image_uniform_set_2, 3)
rd.compute_list_set_push_constant(compute_list, push_constants.to_byte_array(), push_constants.size() * 4)
rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1)
rd.compute_list_end()


uniform = get_image_uniform(texture_layer_1, 0)
image_uniform_set = UniformSetCacheRD.get_cache(shader_layer1, 0, [uniform])

uniform = get_image_uniform(texture_layer_2, 0)
sampler_uniform_set = UniformSetCacheRD.get_cache(shader_layer1, 1, [uniform])

x_groups = size.x / 16 + 1
y_groups = size.y / 16 + 1

push_constants.clear()

push_constants.append(size.x)
push_constants.append(size.y)
push_constants.append(...)
push_constants.append(0.0)

compute_list = rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, pipline_1)
rd.compute_list_bind_uniform_set(compute_list, image_uniform_set, 0)
rd.compute_list_bind_uniform_set(compute_list, sampler_uniform_set, 1)
rd.compute_list_set_push_constant(compute_list, push_constants.to_byte_array(), push_constants.size() * 4)
rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1)
rd.compute_list_end()

(可以复用一个Pipeline,来实现参数不同的相同操作)
(这里还有一个额外操作,得把buffer给Pingpong一下)

uniform = get_image_uniform(texture_layer_2, 0)
image_uniform_set = UniformSetCacheRD.get_cache(shader_layer_1, 0, [uniform])

uniform = get_image_uniform(texture_layer_1, 0)
sampler_uniform_set = UniformSetCacheRD.get_cache(shader_layer_1, 1, [uniform])

push_constants.clear()

push_constants.append(size.x)
push_constants.append(size.y)
push_constants.append(0.0)
push_constants.append(...)

compute_list = rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, pipeline_blur)
rd.compute_list_bind_uniform_set(compute_list, image_uniform_set, 0)
rd.compute_list_bind_uniform_set(compute_list, sampler_uniform_set, 1)
rd.compute_list_set_push_constant(compute_list, push_constants.to_byte_array(), push_constants.size() * 4)
rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1)
rd.compute_list_end()

...(执行其他Pipeline)

一个Compositor常见的执行过程就可以概括为以上步骤。

Compositor使用指南

如何获取各种Buffer

获取屏幕颜色Buffer

1
var screen_texture : RID = scene_buffers.get_color_layer(view)

获取depth buffer

1
var depth_texture : RID = scene_buffers.get_depth_layer(view)

获取Normal-Roughness buffer,需要在Compositor里打开对应选项,然后

1
var roughness_texture = render_scene_buffers.get_texture("forward_clustered", "normal_roughness")

如何使用外部Texture

准备好RID

1
2
3
4
5
var texture_1_rid : RID
var texture_2_rid : RID

@export var texture1 : Texture2D = load("...")
@export var texture2 : Texture2D = load("...")

取贴图的RID

1
texture_1_rid = texture1.get_rid()

创建Uniform

1
2
uniform = get_sampler_uniform_extra(texture_2_rid, 0)
var sampler_uniform_set_2 : RID = UniformSetCacheRD.get_cache(shader, 2, [uniform])

如何降分辨率采样

创建一个Buffer的时候,可以不使用全分辨率进行后处理,从而提升处理速度。

1
2
3
4
if !scene_buffers.has_texture(context, texture_name_1):
var usage_bits : int = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice.TEXTURE_USAGE_STORAGE_BIT
scene_buffers.create_texture(context, texture_name_1, RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT, usage_bits, RenderingDevice.TEXTURE_SAMPLES_1, size, 1, 1, true, true)
scene_buffers.create_texture(context, texture_name_2, RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT, usage_bits, RenderingDevice.TEXTURE_SAMPLES_1, size, 1, 1, true, true)

只需要把size改成期望的size就行。需要升采样的时候,把这个buffer当作sampler传入即可。

其他注意事项

不要直接操作输入图像。

CATALOG
  1. 1. Compositor参考类模板
  2. 2. Compositor使用指南
    1. 2.1. 如何获取各种Buffer
    2. 2.2. 如何使用外部Texture
    3. 2.3. 如何降分辨率采样
    4. 2.4. 其他注意事项