Animate water and lava with gentle waves and flowing textures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f4622d285d
commit
f18af0d64c
2 files changed
+51
-10
No files matched your search
+44
-6
@@ -210,9 +210,19 @@ varying mediump vec3 wnorm;
|
||||
|
||||
uniform mat4 Model;
|
||||
uniform mat4 Projection;
|
||||
uniform highp float Time;
|
||||
|
||||
void main() {
|
||||
vec4 w = Model * vec4(position, 1.0);
|
||||
// river surfaces (marked by the vertex alpha, see LIQ_WATER_A / LIQ_LAVA_A) swell gently;
|
||||
// lava rolls slower and flatter than water
|
||||
float a = color0.a;
|
||||
if (a > 96.0 && a < 192.0) {
|
||||
bool lava = a < 128.0;
|
||||
float sp = lava ? 0.55 : 1.6;
|
||||
float amp = lava ? 0.018 : 0.028;
|
||||
w.y += amp * (sin(w.x * 1.7 + Time * sp) * 0.6 + sin(w.z * 2.3 - Time * sp * 1.3) * 0.4);
|
||||
}
|
||||
wpos = w.xyz;
|
||||
wnorm = normal.xyz;
|
||||
color = color0 / 255.0;
|
||||
@@ -232,12 +242,27 @@ uniform sampler2D Texture;
|
||||
uniform vec4 LightPos[24];
|
||||
uniform vec4 LightCol[24];
|
||||
uniform float LightCount;
|
||||
uniform highp float Time; // same precision as in the vertex shader (required for linking)
|
||||
uniform vec2 AtlasSize;
|
||||
|
||||
void main() {
|
||||
vec4 tex = texture2D(Texture, uv);
|
||||
vec2 tuv = uv;
|
||||
float wave = 0.0;
|
||||
bool liquid = color.a > 0.38 && color.a < 0.75;
|
||||
if (liquid) {
|
||||
// flowing texture: sampled in world space inside the tile's (seamless) atlas cell
|
||||
bool lava = color.a < 0.5;
|
||||
vec2 flow = lava ? vec2(0.035, 0.015) : vec2(0.11, 0.045);
|
||||
vec2 p = wpos.xz + flow * Time;
|
||||
p += (lava ? 0.03 : 0.05) * vec2(sin(wpos.z * 3.0 + Time * (lava ? 0.4 : 1.1)), sin(wpos.x * 3.0 + Time * (lava ? 0.3 : 0.9)));
|
||||
vec2 cell = floor(uv * AtlasSize / 16.0) * 16.0;
|
||||
tuv = (cell + fract(p) * 16.0) / AtlasSize;
|
||||
wave = sin(wpos.x * 5.0 + wpos.z * 3.0 + Time * (lava ? 0.7 : 2.2)) * sin(wpos.z * 4.0 - wpos.x * 2.0 + Time * (lava ? 0.5 : 1.7));
|
||||
}
|
||||
vec4 tex = texture2D(Texture, tuv);
|
||||
if (tex.a < 0.5) discard;
|
||||
// see-through units (invisibility): every other pixel left out
|
||||
if (color.a < 0.99 && mod(floor(gl_FragCoord.x) + floor(gl_FragCoord.y), 2.0) < 1.0) discard;
|
||||
if (color.a < 0.37 && mod(floor(gl_FragCoord.x) + floor(gl_FragCoord.y), 2.0) < 1.0) discard;
|
||||
vec3 n = normalize(wnorm);
|
||||
vec3 light = color.rgb;
|
||||
for (int i = 0; i < 24; i++) {
|
||||
@@ -249,6 +274,8 @@ void main() {
|
||||
float ndl = max(dot(n, d / max(dist, 0.001)), 0.0) * 0.8 + 0.2;
|
||||
light += LightCol[i].rgb * LightCol[i].a * att * ndl;
|
||||
}
|
||||
// light rippling over the surface: glints on water, a slow pulse of the glow on lava
|
||||
if (liquid) light *= 1.0 + wave * (color.a < 0.5 ? 0.12 : 0.1);
|
||||
gl_FragColor = vec4(tex.rgb * min(light, vec3(1.7)), 1.0);
|
||||
}
|
||||
"#;
|
||||
@@ -262,6 +289,8 @@ fn lit_material() -> Material {
|
||||
UniformDesc::new("LightPos", UniformType::Float4).array(MAX_LIGHTS),
|
||||
UniformDesc::new("LightCol", UniformType::Float4).array(MAX_LIGHTS),
|
||||
UniformDesc::new("LightCount", UniformType::Float1),
|
||||
UniformDesc::new("Time", UniformType::Float1),
|
||||
UniformDesc::new("AtlasSize", UniformType::Float2),
|
||||
],
|
||||
textures: vec![],
|
||||
},
|
||||
@@ -518,6 +547,10 @@ fn solid_at(map: &Map, x: i32, z: i32) -> bool {
|
||||
|
||||
const OUTLINE_W: f32 = 0.035;
|
||||
const LIQ_D: f32 = 0.3; // depth of the river surface below the floor
|
||||
// vertex alpha that marks river surfaces for the lit shader (waves and flowing texture)
|
||||
const LIQ_WATER_A: f32 = 0.6;
|
||||
const LIQ_LAVA_A: f32 = 0.42;
|
||||
const LIQ_SKIRT: f32 = 0.06; // river banks reach a bit below the surface, so waves leave no gap
|
||||
const DECK_Y: f32 = 0.03; // height of bridge decks
|
||||
const OUTLINE_C: [u8; 4] = [8, 5, 6, 150];
|
||||
|
||||
@@ -633,10 +666,12 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
|
||||
let (la, lb) = cell_uv(if lava { T_LAVA } else { T_WATER } + (t.var as u32 & 3));
|
||||
let a = amb_at(amb, xf + 0.5, zf + 0.5);
|
||||
let lg = lava_at(glow, xf + 0.5, zf + 0.5);
|
||||
let lc = if lava { col(vec3(1.0, 0.95, 0.9), 1.0) } else { col(lava_lit(Vec3::splat(0.25 + a * 0.9), lg), 1.0) };
|
||||
let lc = if lava { col(vec3(1.0, 0.95, 0.9), LIQ_LAVA_A) } else { col(lava_lit(Vec3::splat(0.25 + a * 0.9), lg), LIQ_WATER_A) };
|
||||
mb.quad(
|
||||
[vec3(xf, -LIQ_D, zf), vec3(xf + 1.0, -LIQ_D, zf), vec3(xf + 1.0, -LIQ_D, zf + 1.0), vec3(xf, -LIQ_D, zf + 1.0)],
|
||||
[vec2(la.x, la.y), vec2(lb.x, la.y), vec2(lb.x, lb.y), vec2(la.x, lb.y)],
|
||||
// the shader samples the flowing texture in world space; the uv only names the cell
|
||||
// (its centre, robust even with low precision uvs)
|
||||
[(la + lb) * 0.5; 4],
|
||||
[lc; 4],
|
||||
[Vec3::Y; 4],
|
||||
);
|
||||
@@ -655,7 +690,7 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
|
||||
let inward = vec3(-dx as f32, 0.0, -dz as f32);
|
||||
let bc = col(if lava { Vec3::splat(0.7) } else { lava_lit(Vec3::splat(0.18 + a * 0.5), lg) }, 1.0);
|
||||
mb.quad(
|
||||
[p, q, q - Vec3::Y * LIQ_D, p - Vec3::Y * LIQ_D],
|
||||
[p, q, q - Vec3::Y * (LIQ_D + LIQ_SKIRT), p - Vec3::Y * (LIQ_D + LIQ_SKIRT)],
|
||||
[vec2(fa.x, fa.y), vec2(fb.x, fa.y), vec2(fb.x, fb.y), vec2(fa.x, fb.y)],
|
||||
[bc; 4],
|
||||
[inward; 4],
|
||||
@@ -1159,6 +1194,9 @@ impl R3d {
|
||||
self.lit.set_uniform_array("LightPos", &lp[..MAX_LIGHTS]);
|
||||
self.lit.set_uniform_array("LightCol", &lc[..]);
|
||||
self.lit.set_uniform("LightCount", count);
|
||||
// wrapped so the shader keeps its precision (the flow speeds are whole tiles per period)
|
||||
self.lit.set_uniform("Time", (time % 1000.0) as f32);
|
||||
self.lit.set_uniform("AtlasSize", vec2(ATLAS_W, ATLAS_H));
|
||||
gl_use_material(&self.lit);
|
||||
for cz in 0..self.ncz {
|
||||
for cx in 0..self.ncx {
|
||||
@@ -2607,7 +2645,7 @@ fn draw_parts_solid(g: &Game) {
|
||||
let life = (p.t / p.max).clamp(0.0, 1.0);
|
||||
let k = 1.0 - life * life;
|
||||
let bob = (p.t * 3.0 + p.x * 2.0).sin() * 0.01;
|
||||
let mid = vec3(p.x, -LIQ_D + 0.025 + bob, p.y);
|
||||
let mid = vec3(p.x, -LIQ_D + 0.04 + bob, p.y); // above the swell of the waves
|
||||
let dir = vec3(p.vx, 0.0, p.vy).normalize_or_zero();
|
||||
let half = dir * (0.17 * k);
|
||||
let wood = vec3(0.45, 0.31, 0.18);
|
||||
|
||||
Reference in new issue
Block a user