192 lines
6.3 KiB
GDScript
192 lines
6.3 KiB
GDScript
@tool
|
|
extends "base_modifier.gd"
|
|
|
|
|
|
static var shader_file: RDShaderFile
|
|
|
|
|
|
@export var iterations : int = 3
|
|
@export var offset_step : float = 0.01
|
|
@export var consecutive_step_multiplier : float = 0.5
|
|
@export var use_computeshader : bool = true
|
|
|
|
|
|
func _init() -> void:
|
|
display_name = "Relax Position"
|
|
category = "Edit"
|
|
global_reference_frame_available = false
|
|
local_reference_frame_available = false
|
|
individual_instances_reference_frame_available = false
|
|
can_restrict_height = true
|
|
restrict_height = true
|
|
|
|
documentation.add_warning(
|
|
"This modifier is has an O(n²) complexity and will be slow with
|
|
large amounts of points, unless your device supports compute shaders.",
|
|
1)
|
|
|
|
var p := documentation.add_parameter("iterations")
|
|
p.set_type("int")
|
|
p.set_cost(2)
|
|
p.set_description(
|
|
"How many times the relax algorithm will run. Increasing this value will
|
|
generally improves the result, at the cost of execution speed."
|
|
)
|
|
|
|
p = documentation.add_parameter("Offset step")
|
|
p.set_type("float")
|
|
p.set_cost(0)
|
|
p.set_description("How far the transform will be pushed away each iteration.")
|
|
|
|
p = documentation.add_parameter("Consecutive step multiplier")
|
|
p.set_type("float")
|
|
p.set_cost(0)
|
|
p.set_description(
|
|
"On each iteration, multiply the offset step by this value. This value
|
|
is usually set between 0 and 1, to make the effect less pronounced on
|
|
successive iterations.")
|
|
|
|
p = documentation.add_parameter("Use compute shader")
|
|
p.set_cost(0)
|
|
p.set_type("bool")
|
|
p.set_description(
|
|
"Run the calculations on the GPU instead of the CPU. This provides
|
|
a significant speed boost and should be enabled when possible.")
|
|
p.add_warning(
|
|
"This parameter can't be enabled when using the OpenGL backend or running
|
|
in headless mode.", 2)
|
|
|
|
|
|
func _process_transforms(transforms, _domain, _seed) -> void:
|
|
var offset := offset_step
|
|
if transforms.size() < 2:
|
|
return
|
|
|
|
# Disable the use of compute shader, if we cannot create a RenderingDevice
|
|
if use_computeshader:
|
|
var rd := RenderingServer.create_local_rendering_device()
|
|
if rd == null:
|
|
use_computeshader = false
|
|
else:
|
|
rd.free()
|
|
rd = null
|
|
|
|
if use_computeshader:
|
|
for iteration in iterations:
|
|
if interrupt_update:
|
|
return
|
|
var movedir: PackedVector3Array = compute_closest(transforms)
|
|
for i in transforms.size():
|
|
var dir = movedir[i]
|
|
if restrict_height:
|
|
dir.y = 0.0
|
|
# move away from closest point
|
|
transforms.list[i].origin += dir.normalized() * offset
|
|
|
|
offset *= consecutive_step_multiplier
|
|
|
|
else:
|
|
# calculate the relax transforms on the cpu
|
|
for iteration in iterations:
|
|
for i in transforms.size():
|
|
if interrupt_update:
|
|
return
|
|
var min_vector = Vector3.ONE * 99999.0
|
|
var threshold := 99999.0
|
|
var distance := 0.0
|
|
var diff: Vector3
|
|
|
|
# Find the closest point
|
|
for j in transforms.size():
|
|
if i == j:
|
|
continue
|
|
|
|
diff = transforms.list[i].origin - transforms.list[j].origin
|
|
distance = diff.length_squared()
|
|
|
|
if distance < threshold:
|
|
min_vector = diff
|
|
threshold = distance
|
|
|
|
if restrict_height:
|
|
min_vector.y = 0.0
|
|
|
|
# move away from closest point
|
|
transforms.list[i].origin += min_vector.normalized() * offset
|
|
|
|
offset *= consecutive_step_multiplier
|
|
|
|
|
|
# compute the closest points to each other using a compute shader
|
|
# return a vector for each point that points away from the closest neighbour
|
|
func compute_closest(transforms) -> PackedVector3Array:
|
|
var padded_num_vecs = ceil(float(transforms.size()) / 64.0) * 64
|
|
var padded_num_floats = padded_num_vecs * 4
|
|
var rd := RenderingServer.create_local_rendering_device()
|
|
var shader_spirv: RDShaderSPIRV = get_shader_file().get_spirv()
|
|
var shader := rd.shader_create_from_spirv(shader_spirv)
|
|
# Prepare our data. We use vec4 floats in the shader, so we need 32 bit.
|
|
var input := PackedFloat32Array()
|
|
for i in transforms.size():
|
|
input.append(transforms.list[i].origin.x)
|
|
input.append(transforms.list[i].origin.y)
|
|
input.append(transforms.list[i].origin.z)
|
|
input.append(0) # needed to use vec4, necessary for byte alignment in the shader code
|
|
# buffer size, number of vectors sent to the gpu
|
|
input.resize(padded_num_floats) # indexing in the compute shader requires padding
|
|
var input_bytes := input.to_byte_array()
|
|
var output_bytes := input_bytes.duplicate()
|
|
# Create a storage buffer that can hold our float values.
|
|
var buffer_in := rd.storage_buffer_create(input_bytes.size(), input_bytes)
|
|
var buffer_out := rd.storage_buffer_create(output_bytes.size(), output_bytes)
|
|
|
|
# Create a uniform to assign the buffer to the rendering device
|
|
var uniform_in := RDUniform.new()
|
|
uniform_in.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
|
|
uniform_in.binding = 0 # this needs to match the "binding" in our shader file
|
|
uniform_in.add_id(buffer_in)
|
|
# Create a uniform to assign the buffer to the rendering device
|
|
var uniform_out := RDUniform.new()
|
|
uniform_out.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
|
|
uniform_out.binding = 1 # this needs to match the "binding" in our shader file
|
|
uniform_out.add_id(buffer_out)
|
|
# the last parameter (the 0) needs to match the "set" in our shader file
|
|
var uniform_set := rd.uniform_set_create([uniform_in, uniform_out], shader, 0)
|
|
|
|
# Create a compute pipeline
|
|
var pipeline := rd.compute_pipeline_create(shader)
|
|
var compute_list := rd.compute_list_begin()
|
|
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
|
|
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
|
|
# each workgroup computes 64 vectors
|
|
# print("Dispatching workgroups: ", padded_num_vecs/64)
|
|
rd.compute_list_dispatch(compute_list, padded_num_vecs/64, 1, 1)
|
|
rd.compute_list_end()
|
|
# Submit to GPU and wait for sync
|
|
rd.submit()
|
|
rd.sync()
|
|
# Read back the data from the buffer
|
|
var result_bytes := rd.buffer_get_data(buffer_out)
|
|
var result := result_bytes.to_float32_array()
|
|
var retval = PackedVector3Array()
|
|
for i in transforms.size():
|
|
retval.append(Vector3(result[i*4], result[i*4+1], result[i*4+2]))
|
|
|
|
# Free the allocated objects.
|
|
# All resources must be freed after use to avoid memory leaks.
|
|
if rd != null:
|
|
rd.free_rid(pipeline)
|
|
rd.free_rid(uniform_set)
|
|
rd.free_rid(shader)
|
|
rd.free_rid(buffer_in)
|
|
rd.free_rid(buffer_out)
|
|
rd.free()
|
|
rd = null
|
|
return retval
|
|
|
|
func get_shader_file() -> RDShaderFile:
|
|
if shader_file == null:
|
|
shader_file = load(get_script().resource_path.get_base_dir() + "/compute_shaders/compute_relax.glsl")
|
|
|
|
return shader_file
|