fix(interior-mapping): correct room-space rays and atlas projection
The mapping looked flat/skewed because rays were cast on the wrong side of the facade, world directions were mixed with room-local origins, and B/(B+d) UV crush / center mullions distorted the pre-projected atlas. Use per-window object space (window at z=+1 into -Z), Joost far-hit slab tests, bounded B/(B+d) sampling, outer sash only, and a matching perspective atlas bake. Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>
This commit is contained in:
5 files changed
+208
-233
No files matched your search
@@ -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.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 16 KiB |
@@ -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")) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user