Quality Gate / Quality Gate (pull_request) Successful in 6m55s
New visual test site under game/world/sites: flat sandy floor at -40 m, calm surface at 0 m, fixed daytime sun ahead of and above the submarine. Light shafts come from a shadow-only surface occluder whose alpha-scissor ripples open only inside a pool a few tens of metres ahead; a subtractive FogVolume cancels the global volumetric fog above the water, and an unshaded ceiling mesh shows the lit underside of the surface inside that pool. A composed world (sunlit_shallows_world.tscn) mounts the submarine, cockpit and camera rig in it, with the projectors kept out of the fog. Cockpit: the porthole glass moves to its own render layer with a rougher material and the porthole fill light excludes that layer, so interior lamps no longer mirror as hard specular points. Quality gate: new integration suite for the site (42 checks), three glass assertions in the foundation suite, and the Input-access architecture rule now matches both path separators so the gate runs on Windows. project.godot raises the volumetric fog froxel grid to 128 so the shafts stay readable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
40 lines
1.6 KiB
Plaintext
40 lines
1.6 KiB
Plaintext
// Calm-water surface seen only through its shadow: the mesh is invisible
|
|
// (SHADOW_ONLY) and the alpha-scissor cut-outs let the fixed daytime sun carve
|
|
// light shafts into the volumetric fog below.
|
|
shader_type spatial;
|
|
render_mode cull_disabled, unshaded, depth_prepass_alpha;
|
|
|
|
uniform sampler2D ripple_noise : repeat_enable, filter_linear_mipmap;
|
|
uniform float ripple_scale = 0.025;
|
|
uniform float drift_speed = 0.012;
|
|
uniform float coverage : hint_range(0.0, 1.0) = 0.30;
|
|
// World-space pool where the surface lets sunlight through. Outside it the
|
|
// surface is fully opaque, so shafts only form in that patch of water.
|
|
uniform vec2 pool_center_xz = vec2(-14.0, -31.0);
|
|
uniform float pool_radius = 22.0;
|
|
uniform float pool_edge = 12.0;
|
|
|
|
varying vec2 world_xz;
|
|
|
|
void vertex() {
|
|
world_xz = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xz;
|
|
}
|
|
|
|
void fragment() {
|
|
vec2 slow_drift = TIME * drift_speed * vec2(1.0, 0.35);
|
|
vec2 cross_drift = TIME * drift_speed * vec2(-0.4, 0.9);
|
|
float broad = texture(ripple_noise, world_xz * ripple_scale + slow_drift).r;
|
|
float fine = texture(ripple_noise, world_xz * ripple_scale * 1.9 + cross_drift).r;
|
|
float raw_ripple = broad * 0.65 + fine * 0.35;
|
|
// Inside the pool the crests are sharpened so the gaps cut crisp shafts.
|
|
float pool_ripple = smoothstep(0.35, 0.65, raw_ripple);
|
|
// Outside the pool only the deepest troughs open: a dim dapple, not darkness.
|
|
float dim_ripple = smoothstep(0.16, 0.30, raw_ripple);
|
|
float pool = 1.0 - smoothstep(
|
|
pool_radius - pool_edge, pool_radius, distance(world_xz, pool_center_xz)
|
|
);
|
|
ALBEDO = vec3(0.0);
|
|
ALPHA = mix(dim_ripple, pool_ripple, pool);
|
|
ALPHA_SCISSOR_THRESHOLD = coverage;
|
|
}
|