Fluid System User Manual
This document explains the overall structure of the “Fluid Simulation System” implemented in this project, as well as how to use and configure each node.
It enables the expression of seamless trajectories that cannot be represented with particles.
Sample Project
All the sample effects below are included in the sample project.
After running game_scene with F5,
effects are assigned to ZXCV, ASDF, and QWE. You can verify the assignments in player.tscn.
Script Only
fluid_system_core.zip (68.0 KB)












- FluidBufferPool (Autoload): An internal system that reuses GPU buffers to maintain performance. There is no need to place it directly in the scene.
- Fluid Shaders (inside
shaders/fluid/): Shader files that perform actual fluid calculations on the GPU. No editing is required.
Node 1: FluidSimulationController
Role
This is the “heart” of the entire fluid simulation. It manages everything from GPU buffer allocation and fluid calculations to rendering on the screen. You must place exactly one in your scene.
Note: Placing more than one in a scene will allocate buffers for each instance, straining VRAM.
Key Settings
Basic Settings
| Item | Description | Default |
|---|---|---|
auto_fit_to_window |
Automatically adjusts the simulation area to the window size. If ON, all buffers are reallocated when the window is resized, causing performance spikes. | OFF |
use_deferred_init |
Delays initialization to the next frame. If ON, nothing is rendered for the 1 frame immediately after instantiation. Textures referenced within _ready() will appear empty. |
ON |
texture_size |
Fixed resolution when auto_fit_to_window is OFF. |
768×512 |
sim_area_size |
When auto_fit_to_window is OFF, this fixes the world space size (in pixels) covered by the fluid. If (0,0), it matches texture_size. Changing texture_size without setting this will misalign the UV positions in FluidEffectNode. |
(0, 0) |
Performance / Quality
| Item | Description | Default |
|---|---|---|
simulation_scale |
Resolution scale. 1.0 is highest quality but most expensive. 0.5 is recommended. | 0.5 |
pressure_iterations |
Accuracy of fluid pressure calculations. Higher values look better but increase GPU load. Around 10 is a good guideline. | 10 |
path_bake_resolution |
Direction texture resolution for effect nodes in Path mode (8–64). Lower values are faster. | 16 |
path_bake_sample_density |
Number of samples per curve point in Path mode (1–8). | 4 |
Operation Settings
| Item | Description | Default |
|---|---|---|
simulation_active |
If OFF, completely stops calculations. The visual appearance remains unchanged. | ON |
auto_sleep_enabled |
Automatically stops calculations after ink and input cease. Useful for saving performance. | ON |
Visuals and Glow
| Item | Description | Default |
|---|---|---|
dye_brightness |
Brightness multiplier for the entire ink. | 1.0 |
dye_glow_mode |
Glow style. Two types: “Standard Glow” and “Dye Glow”. | Standard Glow |
dye_glow_boost |
Glow intensity. Higher values make it appear brighter. | 0.0 |
dye_base_ink_opacity |
Ink opacity. At 0, only the glow component is visible. | 1.0 |
dye_alpha_gain |
Amplification multiplier for overall opacity. Use this to make the ink appear denser. | 1.0 |
dye_velocity_visibility_scale |
Exclusive to “Dye Glow” mode. When dye_base_ink_opacity is less than 1, this determines how strongly ink opacity is increased at locations with high fluid velocity. Higher values make ink appear clearer even with weak movement. |
40.0 |
dye_color_variation_strength |
Strength of hue fluctuation over time. 0 disables it. | 0.0 |
dye_color_variation_speed |
Speed at which hue fluctuations change. | 1.3 |
dye_glow_pulse_strength |
Strength of the pulsing glow effect. 0 disables it. | 0.0 |
dye_glow_pulse_speed |
Speed of the pulsing effect. | 1.6 |
dye_alpha_cutoff |
Alpha values below this threshold are cut to full transparency. Prevents garbage pixels. | 0.04 |
Ink Dissipation Settings
| Item | Description | Default |
|---|---|---|
dye_evaporation |
Speed at which ink gradually fades over time. | (Varies) |
dye_hard_clear_threshold |
If ink density falls below this, it is instantly removed. | 0.06 |
decay_distance_factor |
Factor to accelerate dissipation based on movement distance. Ink disappears smoothly from the tip. 0 disables it. | 0.0 |
decay_velocity_factor |
Factor to accelerate dissipation based on velocity. Ink scatters when flying vigorously. 0 disables it. | 0.0 |
Fluid Physics Settings
| Item | Description | Default |
|---|---|---|
viscosity |
Viscosity. Higher values result in thicker, more syrupy fluids. | 0.18 |
curl_strength |
Strength of curl enhancement. Higher values create more complex swirls. | 28.0 |
curl_idle_scale |
How much to maintain swirls when there is no input (0.0–1.0). | 0.08 |
idle_velocity_damping |
Fade-out rate of flow when there is no input. 1.0 means no damping. | 0.975 |
max_velocity |
Upper limit for flow velocity. | 2.4 |
velocity_floor |
Velocities below this value are forcibly treated as zero. Used to improve calculation stability. | 0.006 |
pressure_dissipation |
Pressure dissipation rate. Lowering it reduces repulsion. | 0.999 |
Detailed Velocity Addition Control
Usually, no changes are needed. Use this when you want to fine-tune brush tracking feel or spray momentum.
| Item | Description | Default |
|---|---|---|
input_velocity_cap_scale |
Upper limit multiplier for ink velocity during brush movement. Higher values allow faster tracking to the brush but make it clump more easily. | 10.0 |
input_velocity_add_scale |
Base multiplier for ink velocity addition during brush movement. | 1.0 |
outflow_velocity_cap_scale |
Upper limit multiplier for ink velocity during spray/blowing out. | 2.5 |
outflow_velocity_add_scale |
Base multiplier for ink velocity addition during spray/blowing out. | 0.8 |
Node 2: FluidBrushNode
Role
This node injects “ink” into the fluid. You can set position, shape, color, intensity, etc. Place it as a sibling node to FluidSimulationController (under the same parent). You can place multiple brushes for one simulation.
Note: It will not be recognized if placed as a child or grandchild of the FSC. Always place it as a sibling (under the same parent).
Key Settings
Basic Settings
| Item | Description | Default |
|---|---|---|
emit_mode |
Determines where ink is injected. Mouse: Mouse pointer position / NodeCenter: This node’s position / TargetNode: Follows another node’s position | Mouse |
emit_trigger |
Timing for injection. Click: Only on click / Always: Always inject (ink continues to flow even when stationary) / Signal: Only when emit_once() is called |
Always |
emit_target_path |
Path to the node to follow in TargetNode mode. |
- |
Brush Shape
| Item | Description | Default |
|---|---|---|
brush_shape |
Basic brush shape. Circle: Circular / Ellipse: Elliptical / Texture: Shape of the specified texture / GlobalTexture: Uses the entire image of a separately specified Viewport as the brush / NodeCapture: Copies the visual of another specified node to use as the brush | Circle |
brush_texture |
Texture used as the brush shape in Texture or Ellipse modes. | - |
GlobalTexture Only
| Item | Description |
|---|---|
global_texture_viewport_path |
Path to the SubViewport to capture (automatically resolved during game execution). |
global_texture_include_hidden |
Whether to include hidden nodes in the capture. |
global_texture_use_content_motion |
Whether to feed motion within the Viewport as force to the fluid. |
NodeCapture Only
| Item | Description |
|---|---|
target_node_include_hidden |
Whether to capture and reflect the target node in the fluid even if it is hidden. |
target_node_respect_clip |
Whether to reproduce the clip boundary if clip_children is set on an ancestor of the source node. If false, the entire node is captured. |
Brush Deformation / Angle
| Item | Description | Default |
|---|---|---|
input_radius |
Brush radius (relative ratio to the overall simulation resolution, 0.0–1.0). | 0.035 |
brush_texture_size_mode |
Resolution determination mode for texture brushes. Radius: Determines size from the input_radius ratio / OriginalPixels: Draws using the original texture’s pixel dimensions |
Radius |
brush_texture_pixel_scale |
Additional multiplier in OriginalPixels mode (1.0 is 1:1). |
1.0 |
brush_texture_rotation_degrees |
Basic rotation angle (in degrees) of the brush (texture). | 0.0 |
brush_rotation_source |
Source for the brush rotation angle. Fixed: Only the basic rotation angle above / ThisNode: Adds this node’s own rotation / NodeCapture: Adds the rotation of the target node / RotationNode: Adds rotation from the specified node below | Fixed |
brush_rotation_node_path |
Path to the rotation angle node to reference in RotationNode mode. |
- |
pivot_follow_visual_center |
In NodeCapture mode, whether to follow based on the visual center (e.g., sprite offset) of the target node. If ON, UVs link appropriately during rotation. | OFF |
brush_flip_h |
Flips the brush texture horizontally. | OFF |
brush_flip_v |
Flips the brush texture vertically. | OFF |
Brush Mask
Filters which parts of the brush are “active” using texture information.
| Item | Description | Default |
|---|---|---|
brush_mask_source |
Type of information used for mask judgment. Alpha: Alpha value / Luminance: Luminance / Color Range: Proximity to specified color / Saturation: Saturation | Alpha |
mask_invert |
Inverts the mask (swaps black and white). | OFF |
mask_threshold |
Thin masks below this value are treated as fully transparent (applies to all modes). | 0.0 |
mask_softness |
Smoothness of the mask’s boundary. 0 is sharp, 1 is a smooth fade (applies to all modes). | 0.1 |
mask_target_color |
Cutout reference color in Color Range mode. |
Red |
mask_color_tolerance |
Exclusive to Color Range. How much deviation from the target color is allowed (smaller is stricter). |
0.3 |
Color / Stamp
| Item | Description | Default |
|---|---|---|
emit_dye |
Whether to inject dye (ink image). If false, it becomes a transparent brush that only generates “force to move the fluid (Force)”. | ON |
inject_color_mode |
Method for determining the injected color. Solid: Solid color (uses input_color) / Gradient: Applies gradient texture / BrushTexture: Uses the brush texture’s own color as is |
Solid |
input_color |
Base color of the injected dye. | Bluish white |
inject_strength |
“Density” of a single injection stamp. Higher values make the color appear clearer and stronger. | 1.0 |
uniform_paint |
Ignores the brush shape’s alpha gradient and paints the inside of the brush uniformly with inject_strength. In BrushTexture mode, the color also becomes uniform (white). |
OFF |
inject_blend |
Blending method with existing dye. 0.0 is complete overwrite, 1.0 is complete addition (colors blow out to white). | 0.35 |
edge_hardness |
Sharpness of the brush’s outer edge. Higher values result in sharper boundaries. | 1.25 |
ink_edge_bleed |
Strength with which the new ink’s edge bleeds when drawing over existing ink. 0.0=none, 1.0=maximum. Does not affect areas without existing ink. | 0.0 |
Gradient Only (Only valid when inject_color_mode is Gradient)
| Item | Description | Default |
|---|---|---|
gradient_mode |
Shape of gradient application. Radial (radial) or Linear (linear). | Radial |
gradient_texture |
Texture used for the gradient (e.g., ColorRamp image). | - |
gradient_rotation_degrees |
Rotation angle (in degrees) serving as the gradient’s reference. | 0.0 |
gradient_rotation_source |
Source for obtaining the gradient rotation angle (same options as brush_rotation_source). |
Fixed |
gradient_rotation_node_path |
Path to reference if RotationNode is selected. |
- |
Movement / Force
| Item | Description | Default |
|---|---|---|
force_scale |
Multiplier converting brush movement amount into Force to move the fluid. Higher values cause the fluid to be pulled more strongly when the brush is moved. | 85.0 |
spread_force_reference |
Reference value for correction where ink spreads more as brush input velocity increases. If 0, it is automatically calculated based on spray_force_limit. |
0.0 |
spread_force_to_advection_scale |
Multiplier to make dye advection (flowing movement) spread more according to Force. Higher values cause ink to scatter more when the brush is moved quickly. 0.0 disables it. | 1.0 |
spread_force_to_diffusion_scale |
Multiplier to strengthen dye diffusion (bleeding) according to Force. Higher values cause ink to blur more when the brush is moved quickly. 0.0 disables it. | 0.0 |
Injection / Spray
| Item | Description | Default |
|---|---|---|
spray_direction_mode |
Method for determining spray direction. Motion: Direction of mouse or node movement / Fixed: Fixed vector below / ToTarget: Towards a specific node / Velocity: Direction calculated from node’s movement velocity | Motion |
spray_profile |
Spray shape. Cone: Spreads in a fan shape / Parallel: Moves straight in parallel | Cone |
spray_fixed_direction |
Fixed vector in Fixed mode. |
Right |
spray_target_path |
Path to the target node to move towards in ToTarget mode. |
- |
spray_spread_degrees |
Spread angle (in degrees) for Cone mode. 180 is omnidirectional (circle). | 85.0 |
spray_force |
Base strength of the injection. If 0, no spray effect occurs in the direction of movement (only simple movement tracking). | 0.0 |
spray_force_limit |
Upper limit for spray force. Prevents fluid breakdown. | 0.25 |
spray_outflow_strength |
Strength of the radial force pushing outward from the center of the brush. | 0.0 |
spray_velocity_scale |
How much to add the node’s own movement velocity to the spray force. 1.0 is complete multiplication. | 0.0 |
spray_keep_last_direction_when_idle |
In Motion or Velocity mode, whether to continue spraying in the last spray direction even when movement stops. | ON |
Continuous Drawing
| Item | Description | Default |
|---|---|---|
continuous_ink_mode |
When moving the mouse quickly or the node moves at high speed, it smoothly interpolates and draws between brush traces. If OFF, traces appear as scattered stamps. | ON |
continuous_ink_quality |
Interpolation quality (number of split steps). Higher values are smoother but increase GPU load. 150–200 is recommended. If too large, a large number of splats run in one frame during high-speed movement, clogging the GPU. | 150 |
interpolate_rotation |
Whether to smoothly interpolate changes in rotation angle or scale when using TargetNode, etc., along the trace. | OFF |
spray_use_direction_sweep |
Whether to sweep the spray direction along the trace during continuous drawing. | OFF |
Physics / Dissipation
These override the corresponding parameters in FluidSimulationController only for injections from this brush. Use this when you want different dissipation behaviors for each brush.
| Item | Description | Default |
|---|---|---|
velocity_dissipation |
Dissipation frame rate for the velocity field (flowing force). 1.0 means no dissipation; lower values stop the water flow sooner. | 0.995 |
dye_dissipation |
Fade-out rate for dye (ink image). 1.0 means no dissipation; lower values make ink fade sooner. | 0.985 |
dye_evaporation |
Forces a reduction in ink density by this value every frame. Used for expressions like water droplets disappearing instantly. | 0.12 |
dye_diffusion |
Strength of dye diffusion (bleeding). Higher values cause ink contours to blur and spread into the surroundings as if dissolving in water. | 0.06 |
Node 3: FluidEffectNode
Role
This node applies “force” to the fluid. You can specify effects like suction, blowing, and swirls within a range. Place it as a sibling node to FluidSimulationController.
How to set the range: Add a
CollisionShape2Das a child of this node, and set a Circle / Rectangle / Capsule Shape there. Checking “Add Collision Shape” in the inspector will automatically create aCircleShape2Dwith a radius of 50. If noCollisionShape2Dexists (except in Path and Texture modes), no effect will occur.
Key Settings
Force Settings
| Item | Description | Default |
|---|---|---|
direction |
Direction of the force. -1.0: Suction towards center / 1.0: Blowing out from center | -1.0 (Suction) |
force_mode |
Force application mode. Radial: Radial from center / Direction: Specified single direction / Path: Flows along Path2D / Texture: Force applied in the direction of texture gradient | Radial |
direction_vector |
Direction of the force in Direction mode (direction vector). | Upward |
path |
Path to the Path2D used in Path mode. | - |
Strength
| Item | Description | Default |
|---|---|---|
strength |
Strength of the force. | 1.0 |
strength_gradient |
Strength gradient from center to edge. Uniform if not set. | - |
Swirl Settings
| Item | Description | Default |
|---|---|---|
vortex_strength |
Strength of the swirl. 0 disables it. | 0.0 |
vortex_direction |
Rotation direction of the swirl. 1=Counter-clockwise, -1=Clockwise. | 1.0 |
pure_vortex |
If ON, sets the force towards the center to zero and applies only the swirl. | OFF |
Display Settings
| Item | Description | Default |
|---|---|---|
enabled |
If OFF, disables the effects of this node. | ON |
Node 4: GroupNoiseCaptureViewport
Role
Acquires the “silhouette” of nodes belonging to a specified group (such as character sprites) and generates a texture by carving it with noise. By passing the generated texture to FluidBrushNode’s brush_texture, you can achieve the expression of “ink being injected along the shape of a character.”
Use this for expressions like flames burning up along the shape of the body.
Setup Method
-
Place the
GroupNoiseCaptureViewportnode in the scene. -
Click the “[Editor] Setup All Child Nodes” button in the inspector. Required child nodes are automatically created.
-
Enter the group name of the nodes you want to capture in
target_group(e.g.,"fluid_capture"). -
Add the sprites you want to capture to that group.
-
In the
FluidBrushNodein the same scene, setbrush_shapeto GlobalTexture andglobal_texture_viewport_pathto this node’sFinalLayer. SpecifyingMaskLayerwill result in a pure white image because it lacks color information.
Key Settings
Capture Settings
| Item | Description | Default |
|---|---|---|
target_group |
Group name of the nodes to capture. | “fluid_capture” |
local_scene_only |
Captures only nodes within the same scene (instance). If OFF, you can capture nodes of the same group across scenes. Use this when applying effects to sprites in other scenes. | ON |
update_mode |
Update timing. Always: Updates every frame / Manual: Only when refresh_capture() is called |
Always |
capture_size |
Capture size (in pixels). | Automatic (matches fluid simulation) |
auto_align_to_fluid_simulation |
Automatically aligns the camera and size to the fluid simulation area. ON is recommended. | ON |
pool_subviewports |
Pools SubViewports to suppress regeneration spikes. Use this for effects that are frequently generated and destroyed. Turning this ON in persistent scenes (those left on the stage) has no effect. | OFF |
Noise Mask Settings
| Item | Description | Default |
|---|---|---|
noise_texture |
Noise texture used to carve the silhouette. Creates a bumpy shape resembling flames. | - |
tint_color |
Color multiplication value for the output texture. | White |
noise_threshold |
Noise threshold. Darker parts than this value are carved away. Higher values carve more of the silhouette. | 0.5 |
noise_edge_hardness |
Edge hardness. Closer to 1.0 results in a sharper boundary. | 0.1 |
noise_scroll_speed |
Speed at which the noise scrolls. Creates the movement of flickering flames. | (0, -0.5) |
noise_scale |
Noise UV scale. Higher values make the pattern finer. | 1.0 |
Performance Optimization
FluidBufferPool (Autoload)
Mechanism
This system uses a large number of GPU SubViewports. Normally, allocating GPU buffers every time a node is generated causes stuttering.
FluidBufferPool resides as an Autoload and reuses GPU buffers by keeping them as child nodes, reducing the generation cost for the second and subsequent instances to zero. Even if the effect scene is removed from the tree, the buffers remain in the Pool. When an effect of the same size and iteration count is generated next, the existing buffers are passed directly.
The set of buffers required by one FluidSimulationController is called a BufferSet. The Pool manages BufferSets with “In Use / Free” flags.
-
acquire(): Returns a free BufferSet if available. Otherwise, generates a new one.
-
release(): Returns the BufferSet to “Free”. Does not call
queue_free(). -
prewarm(): Pre-generates BufferSets of a specified size as free.
Works Without Autoload
If FluidBufferPool does not exist as an Autoload, each FluidSimulationController and GroupNoiseCaptureViewport switches to a fallback behavior of generating SubViewports as their own child nodes. Performance will drop, but the operation itself is fine.
Settings
| Item | Description |
|---|---|
pool_enabled |
If false, completely disables the pool. acquire() generates a new one every time, and release() immediately calls queue_free(). Used for baseline measurement when testing pool effects. |
auto_scan_dirs |
List of directories to auto-scan at startup (res:// relative paths). Default is ["res://effects/"]. Recursively scans .tscn files contained here and automatically executes prewarm_from_scene(). |
prewarm_scenes |
Manually registers effect scenes (PackedScene) you want to pre-allocate. |
prewarm_count |
How many sets to allocate for each scene. Increase this if multiple are used simultaneously. |
Prewarm (Pre-warming) Details
prewarm_from_scene(scene, count)
Analyzes the PackedScene, automatically detects settings for contained FluidSimulationController and GroupNoiseCaptureViewport, and pre-generates necessary buffers. Scenes with auto_fit_to_window ON are calculated from the current window size. If called before the window size is determined (e.g., early in _ready()), buffers will not be allocated with the correct size.
GroupNoiseCaptureViewport instances with pool_subviewports OFF are skipped during scanning. Those with auto_align_to_fluid_simulation ON are automatically aligned to the size of the previously detected FluidSimulationController.
prewarm_gncv(size, group, count)
Pre-generates SubViewport pairs (MaskLayer and FinalLayer) for GroupNoiseCaptureViewport. Automatically called from within prewarm_from_scene.
Pool Capacity Limits
| Constant | Value | Description |
|---|---|---|
MAX_FREE_PER_KEY |
8 | Upper limit for holding free BufferSets of the same size and iteration count |
MAX_FREE_TOTAL |
100 | Upper limit for holding free BufferSets across all keys |
Free BufferSets exceeding the limit are queue_free()ed.