Terrain-Shader: Hex-Tiling + Makro-Variation statt Raster-Kachel (Büschel nicht mehr im Raster, keine Diagonalstreifen)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
99a06c6cb8
commit
a1f386f5e6
2 changed files with 102 additions and 12 deletions
|
|
@ -87,18 +87,48 @@ vec2 warp(vec2 g) {
|
|||
return g + o * 0.9; // eine Oktave: Quest-Budget (8 Hash statt 16)
|
||||
}
|
||||
|
||||
// Anti-Wiederholung (Karte 25.10., Sly: „Texturen wiederholen sich zu krass“):
|
||||
// Kachel 3 m statt 1 m; zweite Abtastung gedreht + 2,3× größer, ein
|
||||
// großflächiges Rauschen (~9 m) mischt beide, ein zweites (~30 m) variiert die
|
||||
// Helligkeit leicht. 2 Albedo-Fetches je Material statt 1.
|
||||
vec3 alb_anti(float id, vec2 wp) {
|
||||
// Anti-Wiederholung v2 (Karte 25.10., Sly: Büschel sitzen im Raster, diagonale
|
||||
// Streifen): Hex-Tiling (Mikkelsen/Heitz-Neyret): die Ebene wird in ein
|
||||
// Dreiecksgitter geteilt, jede Ecke bekommt zufälligen Versatz + Drehung, die
|
||||
// 3 Abtastungen werden mit weichen Gewichten überblendet — kein Raster, keine
|
||||
// Achsen mehr, keine harten Nähte. Quest-Kosten: 3 Albedo-Fetches je Material
|
||||
// (nur 1 Material in ~allen Pixeln, die Mischzone nimmt 2). Danach Makro-
|
||||
// Variation: Helligkeit (~35 m) + Farbstich (~120 m), je 1 Value-Noise.
|
||||
vec3 alb_hex(float id, vec2 wp, vec2 gx, vec2 gy) {
|
||||
float T = max(tile_size, 0.05);
|
||||
vec2 uv1 = wp / T;
|
||||
vec2 uv2 = vec2(0.8196 * wp.x - 0.5729 * wp.y, 0.5729 * wp.x + 0.8196 * wp.y) / (T * 2.3) + vec2(0.37, 0.71);
|
||||
vec3 a = texture(albedo_array, vec3(uv1, id)).rgb;
|
||||
vec3 b = texture(albedo_array, vec3(uv2, id)).rgb;
|
||||
vec3 col = mix(a, b, smoothstep(0.35, 0.65, vnoise(wp * 0.11)));
|
||||
return col * mix(0.88, 1.08, vnoise(wp * 0.035 + vec2(17.0, 3.0)));
|
||||
vec2 st = wp / T * 1.7320508; // sqrt(3): Dreieckskante ≈ 0,58 Kachel (größer = weniger Mischzone)
|
||||
vec2 sk = vec2(st.x - 0.57735027 * st.y, 1.15470054 * st.y);
|
||||
vec2 base = floor(sk);
|
||||
vec2 fr = fract(sk);
|
||||
float tz = 1.0 - fr.x - fr.y;
|
||||
float up = step(0.0, tz); // 1: unteres Dreieck, 0: oberes
|
||||
vec3 w = mix(vec3(-tz, 1.0 - fr.y, 1.0 - fr.x), vec3(tz, fr.y, fr.x), up);
|
||||
vec2 v1 = mix(base + vec2(1.0), base, up);
|
||||
vec2 v2 = mix(base + vec2(1.0, 0.0), base + vec2(0.0, 1.0), up);
|
||||
vec2 v3 = mix(base + vec2(0.0, 1.0), base + vec2(1.0, 0.0), up);
|
||||
w = w * w * w; // Mitte stärker, Nähte weicher
|
||||
w /= (w.x + w.y + w.z);
|
||||
float wn = sqrt(dot(w, w)); // für Kontrast-Erhalt (Varianz der Mischung)
|
||||
vec2 uv = wp / T;
|
||||
vec3 acc = vec3(0.0);
|
||||
for (int k = 0; k < 3; k++) {
|
||||
vec2 v = k == 0 ? v1 : (k == 1 ? v2 : v3);
|
||||
float wk = k == 0 ? w.x : (k == 1 ? w.y : w.z);
|
||||
float a = hash2(v + vec2(31.0, 7.0)) * 6.2831853;
|
||||
vec2 off = vec2(hash2(v), hash2(v + vec2(53.0, 11.0)));
|
||||
float c = cos(a);
|
||||
float s = sin(a);
|
||||
vec2 r = vec2(c * uv.x - s * uv.y, s * uv.x + c * uv.y) + off;
|
||||
acc += wk * textureGrad(albedo_array, vec3(r, id), gx, gy).rgb;
|
||||
}
|
||||
// Kontrast-Erhalt: Mischen dreier Abtastungen glättet auf den Mittelwert →
|
||||
// Abweichung vom Mittelwert (grober Mip) durch ||w|| teilen.
|
||||
vec3 mean_c = textureLod(albedo_array, vec3(0.5, 0.5, id), 8.0).rgb;
|
||||
acc = mean_c + (acc - mean_c) / max(wn, 0.577);
|
||||
float bright = mix(0.86, 1.12, vnoise(wp * 0.028 + vec2(17.0, 3.0)));
|
||||
float tint = vnoise(wp * 0.0085 + vec2(5.0, 41.0)) - 0.5;
|
||||
vec3 col = acc * bright;
|
||||
return col * vec3(1.0 + 0.10 * tint, 1.0 + 0.03 * tint, 1.0 - 0.10 * tint);
|
||||
}
|
||||
|
||||
float mat_id(ivec2 p) {
|
||||
|
|
@ -173,7 +203,13 @@ void fragment() {
|
|||
float m1 = max(material_res - 1.0, 1.0);
|
||||
vec2 g = clamp(warp(clamp(map_uv, 0.0, 1.0) * m1), vec2(0.0), vec2(m1));
|
||||
vec3 t2 = top2(g);
|
||||
vec3 alb = mix(alb_anti(t2.y, world_pos.xz), alb_anti(t2.x, world_pos.xz), t2.z);
|
||||
// Ableitungen VOR dem Branch (Mip-Wahl bleibt gültig, textureGrad).
|
||||
vec2 guv = world_pos.xz / max(tile_size, 0.05);
|
||||
vec2 gx = dFdx(guv);
|
||||
vec2 gy = dFdy(guv);
|
||||
// Reiner Boden (Anteil ≥ 0,999, Normalfall): nur 3 statt 6 Fetches.
|
||||
vec3 alb = t2.z > 0.999 ? alb_hex(t2.x, world_pos.xz, gx, gy)
|
||||
: mix(alb_hex(t2.y, world_pos.xz, gx, gy), alb_hex(t2.x, world_pos.xz, gx, gy), t2.z);
|
||||
col = alb * col; // Tönung/Shading multipliziert (wie Client)
|
||||
}
|
||||
// --- Konturlinien (unverändert, Karte t_3ebe4086) ---
|
||||
|
|
|
|||
54
tests/probe_steppe_tiling.gd
Normal file
54
tests/probe_steppe_tiling.gd
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
extends SceneTree
|
||||
## Render-Probe Boden-Wiederholung (Steppe, 256 m flach, Sandpfad): Top-Down
|
||||
## 60 m + flache Perspektive. Start (BRAUCHT Rendering):
|
||||
## godot --path . --rendering-driver opengl3 -s res://tests/probe_steppe_tiling.gd -- <prefix>
|
||||
|
||||
func _initialize() -> void:
|
||||
_run()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
await process_frame
|
||||
await process_frame
|
||||
var prefix := OS.get_cmdline_user_args()[0] if OS.get_cmdline_user_args().size() > 0 else "/tmp/steppe"
|
||||
var size := 256.0
|
||||
var res := 129
|
||||
var mres := SvmapIO.material_resolution_for(res, size, size)
|
||||
var heights := PackedFloat32Array()
|
||||
heights.resize(res * res)
|
||||
var mats := PackedByteArray()
|
||||
mats.resize(mres * mres)
|
||||
var mb := PackedByteArray()
|
||||
mb.resize(mres * mres)
|
||||
var mw := PackedByteArray()
|
||||
mw.resize(mres * mres)
|
||||
for i in 60:
|
||||
var t := float(i) / 59.0
|
||||
TerrainField.apply_material_brush_mix(mats, mb, mw, mres, size, size,
|
||||
-25.0 + 50.0 * t, -10.0 + 8.0 * sin(t * 3.0), 3.0, 1, 0.0)
|
||||
var view := Node3D.new()
|
||||
view.set_script(load("res://src/viewport/terrain_mesh.gd"))
|
||||
root.add_child(view)
|
||||
view.call("set_mix", mb, mw)
|
||||
view.call("update_heights", heights, res, size, size, mats)
|
||||
view.call("set_contours", false)
|
||||
var cam := Camera3D.new()
|
||||
root.add_child(cam)
|
||||
cam.projection = Camera3D.PROJECTION_ORTHOGONAL
|
||||
cam.size = 60.0
|
||||
cam.position = Vector3(0.0, 50.0, 10.01)
|
||||
cam.look_at(Vector3(0, 0, 10.0), Vector3(0, 0, -1))
|
||||
cam.current = true
|
||||
for i in 4:
|
||||
await process_frame
|
||||
await RenderingServer.frame_post_draw
|
||||
root.get_texture().get_image().save_png(prefix + "_top.png")
|
||||
cam.projection = Camera3D.PROJECTION_PERSPECTIVE
|
||||
cam.fov = 70.0
|
||||
cam.position = Vector3(0.0, 6.0, 60.0)
|
||||
cam.look_at(Vector3(0, 0, 20.0), Vector3.UP)
|
||||
for i in 3:
|
||||
await process_frame
|
||||
await RenderingServer.frame_post_draw
|
||||
root.get_texture().get_image().save_png(prefix + "_persp.png")
|
||||
quit(0)
|
||||
Loading…
Reference in a new issue