@tool
class_name NubisNoiseGenerator
## Generates 3D noise textures for Nubis clouds via compute shader.
## Call generate() once at startup. Textures persist for the effect's lifetime.
var rd: RenderingDevice
var perlin_worley_texture: RID # 128³ RGBA16F
var detail_texture: RID # 32³ RGBA16F
var noise_sampler: RID
var _pw_shader: RID
var _pw_pipeline: RID
func setup(rendering_device: RenderingDevice) -> bool:
rd = rendering_device
# Load noise generation shader
var pw_file := load("res://addons/nubis/shaders/noise_perlin_worley.glsl") as RDShaderFile
if pw_file == null:
push_error("[NubisNoise] Failed to load noise_perlin_worley.glsl")
return false
var pw_spirv := pw_file.get_spirv()
if pw_spirv == null:
push_error("[NubisNoise] Failed to get noise SPIR-V")
return false
_pw_shader = rd.shader_create_from_spirv(pw_spirv)
if not _pw_shader.is_valid():
push_error("[NubisNoise] Failed to create noise shader")
return false
_pw_pipeline = rd.compute_pipeline_create(_pw_shader)
# Create sampler for later use in ray-march shader
var ss := RDSamplerState.new()
ss.min_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
ss.mag_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
ss.mip_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
ss.repeat_u = RenderingDevice.SAMPLER_REPEAT_MODE_REPEAT
ss.repeat_v = RenderingDevice.SAMPLER_REPEAT_MODE_REPEAT
ss.repeat_w = RenderingDevice.SAMPLER_REPEAT_MODE_REPEAT
noise_sampler = rd.sampler_create(ss)
return true
func generate() -> bool:
if rd == null:
return false
var t0 := Time.get_ticks_usec()
# Generate 128³ Perlin-Worley base texture
perlin_worley_texture = _generate_3d_noise(128, 4.0, 0.0)
if not perlin_worley_texture.is_valid():
push_error("[NubisNoise] Failed to generate Perlin-Worley texture")
return false
# Generate 32³ detail texture (same shader, higher frequency, different seed)
detail_texture = _generate_3d_noise(32, 8.0, 42.0)
if not detail_texture.is_valid():
push_error("[NubisNoise] Failed to generate detail texture")
return false
var dt := (Time.get_ticks_usec() - t0) / 1000.0
print("[NubisNoise] Generated 128³ + 32³ noise textures in %.1fms" % dt)
return true
func _generate_3d_noise(size: int, frequency: float, seed: float) -> RID:
# Create 3D texture
var fmt := RDTextureFormat.new()
fmt.format = RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT
fmt.width = size
fmt.height = size
fmt.depth = size
fmt.array_layers = 1
fmt.mipmaps = 1
fmt.texture_type = RenderingDevice.TEXTURE_TYPE_3D
fmt.usage_bits = (
RenderingDevice.TEXTURE_USAGE_STORAGE_BIT |
RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT
)
var tex := rd.texture_create(fmt, RDTextureView.new())
if not tex.is_valid():
return RID()
# Bind as storage image
var tex_uniform := RDUniform.new()
tex_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
tex_uniform.binding = 0
tex_uniform.add_id(tex)
var uniform_set := UniformSetCacheRD.get_cache(_pw_shader, 0, [tex_uniform])
# Push constants: size (int), frequency (float), seed (float), pad (float)
var push_bytes := PackedByteArray()
push_bytes.append_array(PackedInt32Array([size]).to_byte_array())
push_bytes.append_array(PackedFloat32Array([frequency, seed, 0.0]).to_byte_array())
var groups := ceili(float(size) / 8.0)
# Dispatch within compute list — no submit/sync on shared RenderingDevice.
# The compute_list_end acts as a barrier before next dispatch.
var compute_list := rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, _pw_pipeline)
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
rd.compute_list_set_push_constant(compute_list, push_bytes, push_bytes.size())
rd.compute_list_dispatch(compute_list, groups, groups, groups)
rd.compute_list_end()
return tex
func cleanup() -> void:
if rd == null:
return
if perlin_worley_texture.is_valid(): rd.free_rid(perlin_worley_texture); perlin_worley_texture = RID()
if detail_texture.is_valid(): rd.free_rid(detail_texture); detail_texture = RID()
if noise_sampler.is_valid(): rd.free_rid(noise_sampler); noise_sampler = RID()
if _pw_pipeline.is_valid(): rd.free_rid(_pw_pipeline); _pw_pipeline = RID()
if _pw_shader.is_valid(): rd.free_rid(_pw_shader); _pw_shader = RID()