OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / maps / liquid_warp.gdshader

39 lines · gdshader ref:
shader_type spatial;
// Faithful reproduction of Quake's liquid-surface warp (WinQuake/gl_warp.c EmitWaterPolys):
// a cross-coupled amplitude-8 sine distortion of the texture coords on a 64-texel tile, with
// phase = coord*0.125 + realtime. Rendered on the whole liquid BOX (all faces, cull_disabled)
// so the volume reads with depth and warps around you when submerged. Each face projects on
// its dominant-axis plane (Quake's per-face texture axes), unshaded + nearest-filtered for
// the flat, point-sampled look of registered Quake (r_wateralpha 1 → opaque).
render_mode unshaded, cull_disabled;

uniform sampler2DArray tex : filter_nearest, source_color;
uniform float layer = 0.0;         // atlas layer for this liquid's texture (material-index)
uniform float world_scale = 0.028; // meters per Quake unit — maps world position back to texels
uniform float warp_speed = 1.0;    // 1.0 == Quake realtime (≈6.3s per full cycle)

varying vec3 world_pos;
varying vec3 world_norm;

void vertex() {
	world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
	world_norm = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz);
}

void fragment() {
	// Pick the projection plane from the face's dominant axis (Quake's axial base axes): a
	// horizontal face uses world XZ, a wall uses the two axes in its plane. One Quake unit ==
	// one texel for a unit-scale liquid texinfo, tiling every 64 units.
	vec3 an = abs(world_norm);
	vec2 base;
	if (an.y >= an.x && an.y >= an.z) base = world_pos.xz;        // floor / ceiling
	else if (an.x >= an.z) base = world_pos.zy;                   // east / west wall
	else base = world_pos.xy;                                     // north / south wall
	vec2 q = base / world_scale;                                 // world meters → Quake texels
	float ph = TIME * warp_speed;
	float s = q.x + 8.0 * sin(q.y * 0.125 + ph);                // turbsin amplitude 8
	float t = q.y + 8.0 * sin(q.x * 0.125 + ph);
	vec2 uv = vec2(s, t) / 64.0;                                // 64-texel tile → [0,1]
	ALBEDO = texture(tex, vec3(fract(uv), layer)).rgb;
}