OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / 4994e81

fog: a height gradient and noise, and Position could not reach the GPU at all
Two knobs on the fog material, and one bug that made an authored cloud impossible to texture. HEIGHT GRADIENT. height_fade_start / height_fade_range, in WORLD y: density falls to zero going up, so a formation thins out at its top instead of ending on the SDF's shell. World rather than volume-local because "high up" is a property of the world -- a cloud moved upward should fade MORE, not carry its gradient with it. The default start is 1e20, so a material that sets neither uniform behaves exactly as before. NOISE. noise_scale / noise_strength / noise_octaves / noise_drift / noise_roughness / noise_lacunarity, modulating the density the SDF gives. strength 0 is a no-op, so it is opt-in, and noise_drift moves it with TIME. ⚠ ps_fbm_noise_3d IS proc_simplex.inc -- the same text the CPU shim and the emitted GLSL compile. The alternative was a hash-and-fbm written into the fog wrapper, which is the second implementation this repo has spent months deleting everywhere else. It is #ifndef-guarded, so a graph that already emitted it includes it once; Godot's shader preprocessor honours the guard, measured. sdf 1106 -> 8058 chars COMPILES simplex 7112 -> 14064 chars COMPILES erosion still the known mutable-globals failure --- ⛔ PositionNode CLAIMED GPU SUPPORT AND EMITTED NOTHING. supports_gpu() has been true since it was written, with no compile_glsl override, so the base returned "" and compile_to_function REFUSED any graph containing a Position node. hyper-games #65 records the identical shape for Random Value: "supports_gpu() returned TRUE with no compile_glsl override at all, so the GPU path never emitted its arithmetic". Found building an authored cloud, where Position is what makes noise vary with place AT ALL: without it a Noise Texture samples its unconnected Vector socket, which is (0,0,0), and the whole volume is one constant. In a density function the position IS `pos`, so the fix is one line. Gated: a Position -> Noise -> Group Output graph compiles (6352 chars) and emits `n0_s0 = pos;` -- the function's own argument, not a socket default. 30/30 -> 32/32. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4994e815297b9d9572618b13e66c3837771bfd75
by plankatron <fleeky@gmail.com> · 2026-09-06T16:41:41.000Z

Changed files (4) +84 -4

demo/glsl_golden_gate.gd +18 -1
……@@ -351,6 +351,23 @@ func _volume_density_via_group_output() -> void:
351351 _check("VolumeToMesh names the density",
352352 vt.find("return n%d_s0;" % vbo) != -1,
353353 "returns the SDF chain, with no Group Output in the graph")
354 # N. POSITION EMITS. It claimed GPU support with no compile_glsl, so any
355 # graph containing it REFUSED -- and Position is what makes a noise field
356 # vary with place, so an authored volume could not have texture at all.
357 var pg := ProcNodeGraph.new()
358 var ppos := pg.add_node(PositionNode.new())
359 var pnz := pg.add_node(NoiseTextureNode.new())
360 pg.connect_sockets(ppos, 0, pnz, 0)
361 var pout := GroupOutputNode.new()
362 pout.set_interface([{"name": "Density", "type": 0}])
363 var pouti := pg.add_node(pout)
364 pg.connect_sockets(pnz, 0, pouti, 0)
365 var pt: String = pg.compile_to_glsl_function("proc_density", false, PackedStringArray())
366 _check("Position emits, graph compiles", not pt.is_empty(),
367 "%d chars" % pt.length() if not pt.is_empty() else "REFUSED -- the gap this closes")
368 _check("Position emits `pos`", pt.find("n%d_s0 = pos;" % ppos) != -1,
369 "the density function's own argument, not a socket default")
370
354371 var vtm := VolumeToMeshNode.new()
355372 _check("VolumeToMesh emits nothing itself",
356373 not vtm.supports_gpu() and vtm.gpu_transparent(),
……@@ -520,7 +537,7 @@ func _init() -> void:
520537 #
521538 # Bumping this by hand when a case is added is the point: an intentional
522539 # change is a one-line edit, an accidental drop is a failure.
523 const EXPECTED_CHECKS := 30
540 const EXPECTED_CHECKS := 32
524541 if _ran != EXPECTED_CHECKS:
525542 _fail += 1
526543 print(" %-34s FAIL %d checks ran, expected %d -- a check was DROPPED, "
src/core/proc_volume.cpp +47 -3
……@@ -1,4 +1,5 @@
11#include "proc_volume.h"
2#include "../gpu/proc_simplex_glsl.gen.h"
23#include <godot_cpp/variant/utility_functions.hpp>
34#include <godot_cpp/classes/shader_material.hpp>
45#include <godot_cpp/classes/shader.hpp>
……@@ -112,6 +113,11 @@ String ProcVolume::build_fog_shader_code() const {
112113 src += "shader_type fog;\n\n";
113114 src += "// Emitted by proc-nodes: ProcNodeGraph::compile_to_glsl_function.\n";
114115 src += "// Do not edit here -- edit the graph.\n";
116 // The simplex library, for the noise uniforms below. It is #ifndef-guarded,
117 // so a graph that already emitted it (any Simplex Noise node) includes it
118 // once -- Godot's shader preprocessor honours the guard, measured.
119 src += String(PROC_SIMPLEX_GLSL);
120 src += "\n";
115121 src += glsl_source;
116122 src += "\n";
117123 src += "uniform vec3 volume_origin = vec3(0.0);\n";
……@@ -119,10 +125,48 @@ String ProcVolume::build_fog_shader_code() const {
119125 src += "uniform float edge_softness : hint_range(0.001, 100.0) = 4.0;\n";
120126 src += "uniform float density_scale : hint_range(0.0, 32.0) = 1.0;\n";
121127 src += "uniform vec4 volume_albedo : source_color = vec4(1.0, 1.0, 1.0, 1.0);\n";
122 src += "uniform vec3 volume_emission = vec3(0.0);\n\n";
128 src += "uniform vec3 volume_emission = vec3(0.0);\n";
129 // ── HEIGHT GRADIENT ─────────────────────────────────────────────────────
130 // Density falls to zero with ALTITUDE, so a cloud thins out at its top
131 // instead of ending on the SDF's hard shell. Both ends are uniforms: a
132 // cloud's shape and where it fades are different decisions, and baking the
133 // fade into the graph would mean recompiling to move it.
134 //
135 // WORLD y, not volume-local: "high up" is a property of the world, and a
136 // cloud moved upward should fade MORE, not carry its gradient with it.
137 src += "uniform float height_fade_start = 1e20;\n";
138 src += "uniform float height_fade_range : hint_range(0.001, 4096.0) = 200.0;\n";
139 // ── NOISE VARIABILITY ───────────────────────────────────────────────────
140 // Modulates the density the SDF gives, so the volume is a formation rather
141 // than a smooth solid. Separate from any noise inside the graph: this one is
142 // tunable on the material without recompiling anything, and it can drift
143 // with TIME.
144 //
145 // ⚠ ps_fbm_noise_3d IS proc_simplex.inc -- the same text the CPU shim and
146 // the emitted GLSL compile. A second noise implementation for the fog path
147 // is precisely the divergence this repo spent months removing.
148 src += "uniform float noise_scale : hint_range(0.0, 1.0) = 0.02;\n";
149 src += "uniform float noise_strength : hint_range(0.0, 1.0) = 0.0;\n";
150 src += "uniform int noise_octaves : hint_range(1, 8) = 3;\n";
151 src += "uniform float noise_drift = 0.0;\n";
152 src += "uniform float noise_roughness : hint_range(0.0, 1.0) = 0.5;\n";
153 src += "uniform float noise_lacunarity : hint_range(1.0, 4.0) = 2.0;\n\n";
123154 src += "void fog() {\n";
124 src += "\tfloat d = proc_density(WORLD_POSITION - volume_origin);\n";
125 src += "\tDENSITY = clamp((d - iso_level) / edge_softness, 0.0, 1.0) * density_scale;\n";
155 src += "\tvec3 vp = WORLD_POSITION - volume_origin;\n";
156 src += "\tfloat d = proc_density(vp);\n";
157 src += "\tfloat dens = clamp((d - iso_level) / edge_softness, 0.0, 1.0);\n";
158 src += "\n\t// Altitude fade. The default start is 1e20, so a material that\n";
159 src += "\t// sets neither uniform behaves exactly as it did before this existed.\n";
160 src += "\tfloat hf = (WORLD_POSITION.y - height_fade_start) / max(height_fade_range, 0.001);\n";
161 src += "\tdens *= 1.0 - clamp(hf, 0.0, 1.0);\n";
162 src += "\n\t// Noise. strength 0 is a no-op, so this is opt-in too.\n";
163 src += "\tif (noise_strength > 0.0) {\n";
164 src += "\t\tvec3 np = vp * noise_scale + vec3(noise_drift * TIME, 0.0, 0.0);\n";
165 src += "\t\tfloat n = ps_fbm_noise_3d(np.x, np.y, np.z, noise_octaves,\n";
166 src += "\t\t\t\tnoise_roughness, noise_lacunarity) * 0.5 + 0.5;\n";
167 src += "\t\tdens *= mix(1.0, n, noise_strength);\n";
168 src += "\t}\n";
169 src += "\n\tDENSITY = dens * density_scale;\n";
126170 src += "\tALBEDO = volume_albedo.rgb;\n";
127171 src += "\tEMISSION = volume_emission;\n";
128172 src += "}\n";
src/nodes/attribute_nodes.cpp +7 -0
……@@ -11,6 +11,13 @@ namespace godot {
1111void PositionNode::_bind_methods() {
1212}
1313
14String PositionNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
15 // `pos` is the density function's argument and the batch shader's per-vertex
16 // position -- the same name in both, which is why this one line serves both
17 // paths. See the note in the header for what its absence cost.
18 return p_ctx.declare(p_node_idx, 0) + " = pos;";
19}
20
1421PositionNode::PositionNode() {
1522 // Blender's Position has NO inputs -- it is a field, evaluated on whatever
1623 // domain the consumer is working over. Ours took a Geometry socket only
src/nodes/attribute_nodes.h +12 -0
……@@ -17,6 +17,18 @@ public:
1717 String get_node_title() const override { return "Position"; }
1818 String get_node_category() const override { return "Input"; }
1919 bool supports_gpu() const override { return true; }
20 // ⛔ IT CLAIMED GPU SUPPORT AND EMITTED NOTHING. supports_gpu() has been
21 // true here since it was written, with no compile_glsl override -- so the
22 // base returned "" and compile_to_function REFUSED any graph containing a
23 // Position node. The identical shape is recorded for Random Value in
24 // hyper-games #65: "supports_gpu() returned TRUE with no compile_glsl
25 // override at all, so the GPU path never emitted its arithmetic".
26 //
27 // Found 2026-09-06 building an authored cloud, where Position is what makes
28 // noise vary with place at all -- without it a Noise Texture samples its
29 // unconnected Vector socket, which is (0,0,0), and the whole volume is one
30 // constant. In a density function the position IS `pos`.
31 String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
2032
2133 void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
2234