diff --git a/examples/interior-mapping/README.md b/examples/interior-mapping/README.md index 35de7c43..f2141f74 100644 --- a/examples/interior-mapping/README.md +++ b/examples/interior-mapping/README.md @@ -39,3 +39,14 @@ cd examples/interior-mapping && ../../build/linux-debug/src/engine/eve run - `assets/room_atlas.png` — 预投影房间图集(4×4) - `scripts/gen_room_atlas.py` — 重新生成图集 - `config.nut` — 窗口配置 + +## Mapping notes + +The fragment shader builds a **per-window room space** (window on `z=+1`, back wall on `z=-1`) +from tiled mesh UVs, casts a view ray with Joost’s far-hit box test, then samples the +pre-projected atlas with `scale = B / (B + d)`. + +Debug: set `fgSteps` to `99` via the live uniform path to visualize face hits +(floor=red, ceiling=cyan, walls=green/blue, back=yellow). + +Controls: O orbit, A/D yaw, W/S pitch, [/] depth, -/= perspective, F furniture, R random rooms. diff --git a/examples/interior-mapping/assets/room_atlas.png b/examples/interior-mapping/assets/room_atlas.png index f9964974..a9ae12af 100644 Binary files a/examples/interior-mapping/assets/room_atlas.png and b/examples/interior-mapping/assets/room_atlas.png differ diff --git a/examples/interior-mapping/main.nut b/examples/interior-mapping/main.nut index 9518a872..ec563fc8 100644 --- a/examples/interior-mapping/main.nut +++ b/examples/interior-mapping/main.nut @@ -21,9 +21,9 @@ persist imYaw = 0.35 persist imPitch = 0.18 persist imOrbit = true persist imRoomDepth = 0.55 -persist imPerspective = 1.15 +persist imPerspective = 1.0 persist imRandomRoom = 1.0 -persist imShowFg = 1.0 +persist imShowFg = 0.0 persist imFgSteps = 16.0 persist imStatus = "orbit on" @@ -48,7 +48,7 @@ function pushUniforms() { imShader.sendFloat("fgSteps", imFgSteps); imShader.sendFloat("time", imTime); imShader.sendFloat("showFg", imShowFg); - imShader.sendFloat("frameWidth", 0.055); + imShader.sendFloat("frameWidth", 0.04); } function buildAtlas() { @@ -94,15 +94,15 @@ function updateCamera() { function applyPreset(which) { if (which == 1) { imRoomDepth = 0.35; - imPerspective = 0.85; + imPerspective = 1.2; imStatus = "preset shallow"; } else if (which == 2) { imRoomDepth = 0.55; - imPerspective = 1.15; + imPerspective = 1.0; imStatus = "preset default"; } else { - imRoomDepth = 0.78; - imPerspective = 1.6; + imRoomDepth = 0.75; + imPerspective = 0.65; imStatus = "preset deep"; } } @@ -190,11 +190,11 @@ eve_update = function(dt) { imStatus = "depth " + imRoomDepth; } if (key_just_pressed("-") || key_just_pressed("_")) { - imPerspective = clampf(imPerspective - 0.1, 0.2, 3.0); + imPerspective = clampf(imPerspective - 0.1, 0.2, 2.0); imStatus = "perspective " + imPerspective; } if (key_just_pressed("=") || key_just_pressed("+")) { - imPerspective = clampf(imPerspective + 0.1, 0.2, 3.0); + imPerspective = clampf(imPerspective + 0.1, 0.2, 2.0); imStatus = "perspective " + imPerspective; } if (key_just_pressed("f") || key_just_pressed("F")) { diff --git a/examples/interior-mapping/scripts/gen_room_atlas.py b/examples/interior-mapping/scripts/gen_room_atlas.py index e0fea128..15489517 100644 --- a/examples/interior-mapping/scripts/gen_room_atlas.py +++ b/examples/interior-mapping/scripts/gen_room_atlas.py @@ -1,5 +1,9 @@ #!/usr/bin/env python3 -"""Generate the pre-projected room atlas PNG for examples/interior-mapping.""" +"""Perspective room atlas matching the shader's behind-facade convention. + +Camera at window z=+1 looking toward -Z (into the building). +film (u,v) → ray origin (0,0,+1), direction (u, v, -1). +""" from __future__ import annotations import struct @@ -13,84 +17,105 @@ def clamp01(v: float) -> float: return 0.0 if v < 0.0 else 1.0 if v > 1.0 else v -def palette(room_id: int) -> tuple[float, float, float]: +def palette(room_id: int): walls = [ - (0.78, 0.74, 0.68), - (0.55, 0.68, 0.72), - (0.72, 0.58, 0.52), - (0.62, 0.66, 0.55), - (0.70, 0.62, 0.78), - (0.80, 0.72, 0.58), - (0.58, 0.60, 0.68), - (0.74, 0.70, 0.62), - (0.66, 0.74, 0.70), - (0.76, 0.66, 0.60), - (0.60, 0.70, 0.78), - (0.68, 0.60, 0.58), - (0.72, 0.72, 0.66), - (0.58, 0.64, 0.58), - (0.70, 0.58, 0.66), - (0.64, 0.68, 0.74), + (0.78, 0.74, 0.68), (0.55, 0.68, 0.72), (0.72, 0.58, 0.52), (0.62, 0.66, 0.55), + (0.70, 0.62, 0.78), (0.80, 0.72, 0.58), (0.58, 0.60, 0.68), (0.74, 0.70, 0.62), + (0.66, 0.74, 0.70), (0.76, 0.66, 0.60), (0.60, 0.70, 0.78), (0.68, 0.60, 0.58), + (0.72, 0.72, 0.66), (0.58, 0.64, 0.58), (0.70, 0.58, 0.66), (0.64, 0.68, 0.74), ] return walls[room_id % len(walls)] -def paint_room(buf: bytearray, ox: int, oy: int, stride: int, room_id: int) -> None: +def ray_box_far(origin, direction): + ox, oy, oz = origin + dx, dy, dz = direction + eps = 1e-5 + + def safe(v): + if abs(v) < eps: + return -eps if v < 0.0 else eps + return v + + dx, dy, dz = safe(dx), safe(dy), safe(dz) + inv = (1.0 / dx, 1.0 / dy, 1.0 / dz) + t_far = (abs(inv[0]) - ox * inv[0], abs(inv[1]) - oy * inv[1], abs(inv[2]) - oz * inv[2]) + t = min(t_far) + return (ox + t * dx, oy + t * dy, oz + t * dz) + + +def face_of(hit): + ax, ay, az = abs(hit[0]), abs(hit[1]), abs(hit[2]) + if az >= ax and az >= ay: + return "back" if hit[2] < 0.0 else "window" + if ay >= ax: + return "ceil" if hit[1] > 0.0 else "floor" + return "right" if hit[0] > 0.0 else "left" + + +def shade_room(room_id, hit, face): wall = palette(room_id) - floor_c = (wall[0] * 0.55, wall[1] * 0.48, wall[2] * 0.42) - ceil_c = (wall[0] * 1.08, wall[1] * 1.06, wall[2] * 1.05) + floor_c = (wall[0] * 0.50, wall[1] * 0.42, wall[2] * 0.36) + ceil_c = (min(1.0, wall[0] * 1.12), min(1.0, wall[1] * 1.10), min(1.0, wall[2] * 1.08)) + left_c = (wall[0] * 0.78, wall[1] * 0.78, wall[2] * 0.82) + right_c = (wall[0] * 0.86, wall[1] * 0.84, wall[2] * 0.80) accent = ( 0.35 + 0.4 * ((room_id * 17) % 5) / 4.0, 0.25 + 0.35 * ((room_id * 9) % 7) / 6.0, 0.20 + 0.45 * ((room_id * 13) % 4) / 3.0, ) - back_door = room_id % 3 == 0 - window_side = room_id % 5 == 2 - shelf = room_id % 4 != 1 + hx, hy, hz = hit + if face == "floor": + board = 0.88 if int((hx + 1.0) * 4.0) % 2 == 0 else 1.0 + return (floor_c[0] * board, floor_c[1] * board, floor_c[2] * board) + if face == "ceil": + if abs(hx) < 0.12 and abs(hz + 0.2) < 0.12: + return (0.95, 0.90, 0.70) + return ceil_c + if face == "left": + if room_id % 4 != 1 and -0.2 < hy < 0.55 and -0.7 < hz < 0.0: + if int((hy + 1.0) * 10.0) % 4 == 0: + return (accent[0] * 0.5, accent[1] * 0.5, accent[2] * 0.5) + return accent + return left_c + if face == "right": + if room_id % 5 == 2 and -0.15 < hy < 0.45 and -0.75 < hz < -0.15: + return (0.45, 0.62, 0.82) + return right_c + col = wall + if room_id % 3 == 0 and -0.35 < hx < 0.35 and -1.0 < hy < 0.55: + col = (0.22, 0.18, 0.14) + if 0.18 < hx < 0.28 and -0.05 < hy < 0.08: + col = (0.75, 0.65, 0.35) + if room_id % 5 == 1 and 0.35 < hx < 0.75 and -0.2 < hy < 0.45: + col = (0.40, 0.55, 0.75) + if room_id % 4 == 2 and -0.55 < hx < 0.55 and -1.0 < hy < -0.35 and hz < -0.85: + col = (0.42, 0.26, 0.14) + return col + + +def paint_room(buf, ox, oy, stride, room_id): + origin = (0.0, 0.0, 1.0) for y in range(CELL): for x in range(CELL): - u = (x + 0.5) / CELL - v = (y + 0.5) / CELL - if v < 0.18: - col = ceil_c - elif v > 0.78: - board = 0.92 if int(u * 8.0) % 2 == 0 else 1.0 - col = (floor_c[0] * board, floor_c[1] * board, floor_c[2] * board) - elif u < 0.12: - col = (wall[0] * 0.82, wall[1] * 0.82, wall[2] * 0.85) - elif u > 0.88: - col = (wall[0] * 0.88, wall[1] * 0.86, wall[2] * 0.84) - else: - col = wall - if back_door and 0.38 < u < 0.62 and 0.28 < v < 0.78: - col = (0.22, 0.18, 0.14) - if 0.56 < u < 0.60 and 0.48 < v < 0.54: - col = (0.75, 0.65, 0.35) - if window_side and 0.70 < u < 0.86 and 0.32 < v < 0.62: - col = (0.45, 0.62, 0.82) - if shelf and 0.16 < u < 0.34 and 0.40 < v < 0.70: - if int(v * 20.0) % 4 == 0: - col = (accent[0] * 0.5, accent[1] * 0.5, accent[2] * 0.5) - else: - col = accent - if 0.46 < u < 0.54 and 0.20 < v < 0.28: - col = (0.95, 0.90, 0.70) - edge = ( - clamp01(u * 4.0) - * clamp01((1.0 - u) * 4.0) - * clamp01(v * 3.0) - * clamp01((1.0 - v) * 3.0) - ) - edge = 0.75 + 0.25 * edge + u = (x + 0.5) / CELL * 2.0 - 1.0 + v = 1.0 - (y + 0.5) / CELL * 2.0 # PNG top = +Y + direction = (u, v, -1.0) # into the building + hit = ray_box_far(origin, direction) + face = face_of(hit) + if face == "window": + face = "back" + col = shade_room(room_id, hit, face) + edge = clamp01(1.0 - max(abs(u), abs(v)) * 0.15) i = ((oy + y) * stride + (ox + x)) * 4 - buf[i] = int(clamp01(col[0] * edge) * 255 + 0.5) - buf[i + 1] = int(clamp01(col[1] * edge) * 255 + 0.5) - buf[i + 2] = int(clamp01(col[2] * edge) * 255 + 0.5) + buf[i] = int(clamp01(col[0] * (0.85 + 0.15 * edge)) * 255 + 0.5) + buf[i + 1] = int(clamp01(col[1] * (0.85 + 0.15 * edge)) * 255 + 0.5) + buf[i + 2] = int(clamp01(col[2] * (0.85 + 0.15 * edge)) * 255 + 0.5) buf[i + 3] = 255 -def write_png(path: Path, width: int, height: int, rgba: bytes) -> None: - def chunk(tag: bytes, data: bytes) -> bytes: +def write_png(path, width, height, rgba): + def chunk(tag, data): return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) raw = bytearray() @@ -103,15 +128,13 @@ def write_png(path: Path, width: int, height: int, rgba: bytes) -> None: path.write_bytes(png) -def main() -> None: +def main(): w, h = COLS * CELL, ROWS * CELL buf = bytearray(w * h * 4) room_id = 0 for row in range(ROWS): for col in range(COLS): - # Image top = GL V=1 after typical upload; paint row0 at top. - oy = row * CELL - paint_room(buf, col * CELL, oy, w, room_id) + paint_room(buf, col * CELL, row * CELL, w, room_id) room_id += 1 out = Path(__file__).resolve().parent.parent / "assets" / "room_atlas.png" out.parent.mkdir(parents=True, exist_ok=True) diff --git a/examples/interior-mapping/shaders/interior_mapping.frag b/examples/interior-mapping/shaders/interior_mapping.frag index 97eb80dd..9f065c62 100644 --- a/examples/interior-mapping/shaders/interior_mapping.frag +++ b/examples/interior-mapping/shaders/interior_mapping.frag @@ -1,8 +1,8 @@ #version 450 -// Interior Mapping (pre-projected 2D atlas) — based on -// 「Interior Mapping室内映射技术拆解」: tangent-space ray–AABB, -// perspective correction, atlas room selection, optional foreground -// ray-march against procedural furniture bounds. +// Interior Mapping — per-window object space (XY facade, +Z outward). +// Avoids cotangentFrame handedness bugs by reconstructing room-space +// camera from tiled UVs. Window at z=+1, back at z=-1. +// Atlas: scale = B/(B+d), d = 1 - hit.z. layout(location = 0) in vec3 vNormal; layout(location = 1) in vec2 vUV; @@ -12,139 +12,98 @@ layout(location = 4) in vec3 vCameraPos; layout(location = 5) in vec3 vViewPos; layout(location = 0) out vec4 outColor; -struct Light3D { - vec4 posRadius; - vec4 color; -}; +struct Light3D { vec4 posRadius; vec4 color; }; layout(set = 0, binding = 0, std140) uniform Frame { - mat4 mvp; - mat4 model; - vec4 lightDirIntensity; - vec4 lightColor; - vec4 tint; - vec4 cameraPos; - vec4 ambient; + mat4 mvp; mat4 model; + vec4 lightDirIntensity; vec4 lightColor; vec4 tint; vec4 cameraPos; vec4 ambient; Light3D lights[8]; - vec4 texBomb; - vec4 parallax; - mat4 view; - vec4 clipInfo; - vec4 cloud; - vec4 cloudWind; + vec4 texBomb; vec4 parallax; mat4 view; vec4 clipInfo; vec4 cloud; vec4 cloudWind; } ubo; layout(set = 0, binding = 1) uniform sampler2D albedoSampler; +layout(push_constant) uniform Externals { float data[32]; } pc; -layout(push_constant) uniform Externals { - float data[32]; -} pc; - -// pc layout (declareFloat order): -// 0 atlasColumns 1 atlasRows 2 perspective 3 roomDepth -// 4 randomRoom 5 randomSeed 6 fgSteps 7 time -// 8 showFg 9 frameWidth +// Facade layout matches main.nut (WIN_X=6, WIN_Y=4, 1 world unit per window). +const float kWinX = 6.0; +const float kWinY = 4.0; float hash21(vec2 p) { p = fract(p * vec2(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); } - vec2 hash22(vec2 p) { float n = hash21(p); return vec2(n, hash21(p + n + 19.19)); } - -// Cotangent frame (Mesh3D has no mesh tangents). -mat3 cotangentFrame(vec3 N, vec3 p, vec2 uv) { - vec3 dp1 = dFdx(p); - vec3 dp2 = dFdy(p); - vec2 duv1 = dFdx(uv); - vec2 duv2 = dFdy(uv); - vec3 dp2perp = cross(dp2, N); - vec3 dp1perp = cross(N, dp1); - vec3 T = dp2perp * duv1.x + dp1perp * duv2.x; - vec3 B = dp2perp * duv1.y + dp1perp * duv2.y; - float invMax = inversesqrt(max(dot(T, T), dot(B, B))); - return mat3(T * invMax, B * invMax, N); -} - -// Far-hit only ray vs unit box [-1,1]^3 (Joost / article form). vec3 rayBoxFarHit(vec3 rayPos, vec3 rayDir) { - vec3 safeDir = vec3( + // Slab method: return the FAR intersection with the unit box [-1,1]^3. + vec3 d = vec3( abs(rayDir.x) < 1e-5 ? (rayDir.x < 0.0 ? -1e-5 : 1e-5) : rayDir.x, abs(rayDir.y) < 1e-5 ? (rayDir.y < 0.0 ? -1e-5 : 1e-5) : rayDir.y, abs(rayDir.z) < 1e-5 ? (rayDir.z < 0.0 ? -1e-5 : 1e-5) : rayDir.z); - vec3 invDir = 1.0 / safeDir; - vec3 tFar = abs(invDir) - rayPos * invDir; - float t = min(min(tFar.x, tFar.y), tFar.z); - return rayPos + t * rayDir; + vec3 inv = 1.0 / d; + vec3 t0 = (-1.0 - rayPos) * inv; + vec3 t1 = ( 1.0 - rayPos) * inv; + vec3 tMin = min(t0, t1); + vec3 tMax = max(t0, t1); + float tNear = max(max(tMin.x, tMin.y), tMin.z); + float tFar = min(min(tMax.x, tMax.y), tMax.z); + float t = tFar > tNear ? tFar : max(tFar, 0.0); + return rayPos + t * d; +} +vec2 projectToAtlas(vec3 hit, float perspective) { + float d = max(1.0 - hit.z, 1e-3); + float B = max(perspective, 0.05); + float scale = B / (B + d); + vec2 uv = hit.xy * scale * 0.5 + 0.5; + uv.y = 1.0 - uv.y; + return clamp(uv, 0.001, 0.999); +} +vec3 faceColor(vec3 hit) { + vec3 a = abs(hit); + if (a.z >= a.x && a.z >= a.y) + return hit.z < 0.0 ? vec3(1.0, 0.85, 0.35) : vec3(1.0, 0.0, 1.0); + if (a.y >= a.x) + return hit.y > 0.0 ? vec3(0.2, 0.85, 1.0) : vec3(1.0, 0.25, 0.1); + return hit.x > 0.0 ? vec3(0.2, 1.0, 0.3) : vec3(0.3, 0.35, 1.0); } - -// Per-room furniture AABB in room space [-1,1], z from -1 (window) to +1 (back). -// Returns bounds as (minXYZ, maxXYZ) packed; empty if no furniture. bool furnitureBounds(vec2 roomIndex, out vec3 bmin, out vec3 bmax) { float style = hash21(roomIndex + 3.17); - if (style < 0.18) { - // empty room - return false; - } - if (style < 0.45) { - // desk against back wall - bmin = vec3(-0.55, -1.0, 0.35); - bmax = vec3(0.55, -0.35, 0.95); - return true; - } - if (style < 0.70) { - // sofa / cabinet left - bmin = vec3(-0.95, -1.0, -0.15); - bmax = vec3(-0.35, -0.15, 0.55); - return true; - } - // tall plant / column right-center - bmin = vec3(0.35, -1.0, -0.25); - bmax = vec3(0.75, 0.35, 0.35); - return true; + if (style < 0.22) return false; + if (style < 0.50) { bmin=vec3(-0.45,-1.0,-0.92); bmax=vec3(0.45,-0.40,-0.40); return true; } + if (style < 0.75) { bmin=vec3(-0.92,-1.0,-0.55); bmax=vec3(-0.40,-0.20,-0.05); return true; } + bmin=vec3(0.40,-1.0,-0.40); bmax=vec3(0.72,0.30,-0.05); return true; } - bool insideAABB(vec3 p, vec3 bmin, vec3 bmax) { return all(greaterThanEqual(p, bmin)) && all(lessThanEqual(p, bmax)); } - bool rayMarchFurniture(inout vec3 pos, vec3 rayDir, int steps, vec3 bmin, vec3 bmax) { - // March from window (z~-1) toward back; pos is room-space. - float maxZ = bmax.z + 0.02; - float stepDepth = max(maxZ - pos.z, 0.05) / float(max(steps, 1)); + float minZ = bmin.z - 0.02; + float stepDepth = max(pos.z - minZ, 0.05) / float(max(steps, 1)); vec3 stepSize = rayDir * (stepDepth / max(abs(rayDir.z), 1e-5)); vec3 prev = pos; for (int i = 0; i < 32; ++i) { if (i >= steps) break; - if (pos.z > maxZ || abs(pos.x) > 1.05 || abs(pos.y) > 1.05) break; + if (pos.z < minZ || abs(pos.x) > 1.05 || abs(pos.y) > 1.05) break; if (insideAABB(pos, bmin, bmax)) { - vec3 lo = prev; - vec3 hi = pos; + vec3 lo = prev, hi = pos; for (int j = 0; j < 5; ++j) { vec3 mid = 0.5 * (lo + hi); - if (insideAABB(mid, bmin, bmax)) hi = mid; - else lo = mid; + if (insideAABB(mid, bmin, bmax)) hi = mid; else lo = mid; } - pos = hi; - return true; + pos = hi; return true; } - prev = pos; - pos += stepSize; + prev = pos; pos += stepSize; } return false; } - vec3 furnitureColor(vec2 roomIndex, vec3 hit) { float h = hash21(roomIndex + 9.1); - vec3 wood = vec3(0.45, 0.28, 0.14); - vec3 cloth = vec3(0.25 + 0.35 * h, 0.22, 0.35 + 0.2 * (1.0 - h)); - vec3 metal = vec3(0.55, 0.58, 0.62); - float band = smoothstep(-0.4, -0.2, hit.y); - return mix(wood, mix(cloth, metal, step(0.7, h)), band); + return mix(vec3(0.45,0.28,0.14), + mix(vec3(0.25+0.35*h,0.22,0.35), vec3(0.55,0.58,0.62), step(0.7,h)), + smoothstep(-0.4,-0.2,hit.y)); } void main() { @@ -154,88 +113,70 @@ void main() { float roomDepth = clamp(pc.data[3], 0.05, 0.95); float randomRoom = pc.data[4]; float randomSeed = pc.data[5]; - int fgSteps = int(clamp(pc.data[6], 1.0, 32.0)); + float fgStepsF = pc.data[6]; + int fgSteps = int(clamp(fgStepsF, 1.0, 32.0)); float showFg = pc.data[8]; float frameWidth = clamp(pc.data[9], 0.0, 0.25); + bool debugFaces = fgStepsF > 90.0; - vec2 uv = vUV; - vec2 tileIndex = floor(uv); - vec2 localUV = fract(uv); + vec2 tileIndex = floor(vUV); + vec2 localUV = fract(vUV); vec2 atlasSize = floor(max(vec2(atlasColumns, atlasRows), vec2(1.0))); - vec2 roomIndex = tileIndex - floor(tileIndex / atlasSize) * atlasSize; - if (randomRoom > 0.5) { - vec2 rnd = hash22(tileIndex + randomSeed * 7.31); - roomIndex = floor(rnd * atlasSize); - } + if (randomRoom > 0.5) + roomIndex = floor(hash22(tileIndex + randomSeed * 7.31) * atlasSize); - vec3 N = normalize(vNormal); - mat3 TBN = cotangentFrame(N, vWorldPos, uv); - // Article: view from camera to surface, then into tangent space. - vec3 viewWS = vWorldPos - vCameraPos; - vec3 tangentViewDir = transpose(TBN) * viewWS; // world→tangent (TBN columns are T,B,N) + // World XY of this window's center (mesh: UV0→(-kWinX/2,-kWinY/2), 1 unit/window). + vec2 winCenterWS = tileIndex + 0.5 - vec2(kWinX, kWinY) * 0.5; - // Depth scale: remap roomDepth like the cubemap path (farFrac). + // Camera in room space: XY relative to window center (window spans [-1,1]), + // Z so facade world z=0 maps to room z=+1 (window plane). + vec3 camRoom; + // Scale so window half-extent (0.5 world) maps to room 1.0. + camRoom.xy = (vCameraPos.xy - winCenterWS) * 2.0; + camRoom.z = vCameraPos.z + 1.0; + + vec3 rayPos = vec3(localUV * 2.0 - 1.0, 0.999); + vec3 rayDir = rayPos - camRoom; // camera → surface (Joost) + + // Depth control: compress/expand the into-room Z before abs. float depthScale = 1.0 / max(1.0 - roomDepth, 1e-4) - 1.0; - tangentViewDir.z *= max(depthScale, 1e-3); - - vec3 rayDir = tangentViewDir; - // March into the room (+Z of tangent space after abs on Z). - rayDir.z = max(abs(rayDir.z), 1e-4); - // Window plane at z = -1, XY in [-1,1]. - vec3 rayPos = vec3(localUV * 2.0 - 1.0, -1.0); + rayDir.z *= max(depthScale, 1e-3); + rayDir.z = -max(abs(rayDir.z), 1e-4); vec3 hitPos = rayBoxFarHit(rayPos, rayDir); - // Perspective correction (similar triangles): Scale = B / (B + d). - float hitDepthNorm = clamp(hitPos.z * 0.5 + 0.5, 0.0, 1.0); - float perspectiveScale = perspective / (perspective + hitDepthNorm); - vec2 roomUV = hitPos.xy * perspectiveScale; - roomUV = roomUV * 0.5 + 0.5; - roomUV = clamp(roomUV, 0.001, 0.999); - - vec2 atlasUV = (roomIndex + roomUV) / atlasSize; - vec4 bgColor = texture(albedoSampler, atlasUV); - - vec3 color = bgColor.rgb; - bool hitFg = false; - if (showFg > 0.5) { - vec3 bmin, bmax; - if (furnitureBounds(roomIndex, bmin, bmax)) { - vec3 fgPos = rayPos; - hitFg = rayMarchFurniture(fgPos, rayDir, fgSteps, bmin, bmax); - if (hitFg) { - color = furnitureColor(roomIndex, fgPos); - // Slight lighting from room-space normal of nearest furniture face. - float shade = 0.75 + 0.25 * max(-fgPos.z, 0.0); - color *= shade; + vec3 color; + if (debugFaces) { + color = faceColor(hitPos); + } else { + vec2 roomUV = projectToAtlas(hitPos, clamp(perspective, 0.05, 2.0)); + color = texture(albedoSampler, (roomIndex + roomUV) / atlasSize).rgb; + if (showFg > 0.5) { + vec3 bmin, bmax; + if (furnitureBounds(roomIndex, bmin, bmax)) { + vec3 fgPos = rayPos; + if (rayMarchFurniture(fgPos, rayDir, fgSteps, bmin, bmax)) { + color = furnitureColor(roomIndex, fgPos); + color *= 0.75 + 0.25 * max(fgPos.z, 0.0); + } } } } - // Window frame (mullions) in local UV. float fx = min(localUV.x, 1.0 - localUV.x); float fy = min(localUV.y, 1.0 - localUV.y); - float frame = 1.0 - smoothstep(frameWidth * 0.55, frameWidth, min(fx, fy)); - float mullionX = 1.0 - smoothstep(frameWidth * 0.2, frameWidth * 0.45, abs(localUV.x - 0.5)); - float mullionY = 1.0 - smoothstep(frameWidth * 0.2, frameWidth * 0.45, abs(localUV.y - 0.5)); - float mullion = max(mullionX, mullionY) * 0.85; - float frameMask = max(frame, mullion); - vec3 frameCol = vec3(0.08, 0.09, 0.11); - color = mix(color, frameCol, frameMask); + float frameMask = 1.0 - smoothstep(frameWidth * 0.55, frameWidth, min(fx, fy)); + color = mix(color, vec3(0.08, 0.09, 0.11), frameMask); - // Grazing-angle glass fresnel (FH4 tip from the article lineage). - vec3 viewDir = normalize(vCameraPos - vWorldPos); - float ndotv = clamp(dot(N, viewDir), 0.0, 1.0); - float fresnel = pow(1.0 - ndotv, 3.0); - vec3 sky = vec3(0.55, 0.68, 0.85); - color = mix(color, sky, fresnel * 0.35 * (1.0 - frameMask)); - - // Mild exterior lighting on the facade plane. - vec3 L = normalize(ubo.lightDirIntensity.xyz); - float diff = 0.55 + 0.45 * max(dot(N, L), 0.0); - color *= mix(1.0, diff, 0.25); - color += ubo.ambient.rgb * 0.08; + if (!debugFaces) { + vec3 N = normalize(vNormal); + float fresnel = pow(1.0 - clamp(dot(N, normalize(vCameraPos - vWorldPos)), 0.0, 1.0), 3.0); + color = mix(color, vec3(0.55, 0.68, 0.85), fresnel * 0.35 * (1.0 - frameMask)); + float diff = 0.55 + 0.45 * max(dot(N, normalize(ubo.lightDirIntensity.xyz)), 0.0); + color *= mix(1.0, diff, 0.25); + color += ubo.ambient.rgb * 0.08; + } outColor = vec4(color * vTint.rgb, vTint.a); }