code wiki / (root) / nx_relief_lib.nx

nx_relief_lib.nx source

↩ module page · 526 lines · 29739 B

1// nx_relief_lib.nx -- ★THE ONE OWNER OF THE SKIN MICRO-RELIEF LAW, and of the height field the baked 2// normal map is made of. Extracted 2026-08-25 so the BAKER and the SDF RENDERER cannot disagree about 3// the relief they are respectively writing and reading. [@penner2011] /compare/graphics row 4// "RENDER: pre-integrated SSS bound to baked normals". 5// 6// WHY A LEAF LIB AND NOT A MIRROR. Until today nx_sdfrender carried HAND-COPIED constants 7// (SSS_RELIEF_UM / SSS_RELIEF_LAMBDA_UM) with a comment naming the baker as their source, and a gate 8// tooth that compared the copies. That is the duplicate-ruler defect wearing a test: the copy can only 9// be caught AFTER it drifts, and only if somebody keeps running the comparison. The reason given for 10// the copy was real -- importing nx_nxa_texbake_lib drags nx_nxa + nx_trimesh + nx_png + nx_skin_ita 11// into every consumer of the renderer -- so the fix is not "import the baker", it is to give the law 12// its own LEAF home that both sides import. This file imports nothing but nx_syscalls. 13// 14// THE SEAM, stated so the next reader does not move the wrong half: 15// * THIS FILE owns the PHYSICS -- microns of relief, the wavelength it varies over, the slope that 16// follows from the two, and the value-noise field itself. 17// * nx_nxa_texbake_lib keeps the TEXEL ENCODING -- the byte midpoint/scale that turn a slope into an 18// RGB8 tangent-space texel. That is an atlas format decision, not a property of skin. 19// * nx_sdfrender reads the physics and never sees a texel: a ray-marched implicit surface has no UV, 20// so it samples the SAME height field volumetrically at the lattice its own sampler can resolve. 21// 22// license_tier: ORIGINAL No hw writes (Rule 26). 23import "nx_syscalls.nx" 24 25// --------------------------------------------------------------------------------------------- 26// THE FIELD. Moved VERBATIM out of nx_trimesh (tm_hash3 / tm_noise3), which now delegates here, so 27// every existing caller of tm_noise3 -- the baker, nx_worldpipe, the engine gates -- is behaviourally 28// identical BY CONSTRUCTION rather than by a second implementation that happens to agree today. 29// The two shift amounts are the incumbent's xxHash-class avalanche constants; they are NAMED here 30// rather than retyped as bare literals so the move is auditable, and they are not tunable. 31const RLF_HASH_A: i64 = 374761393 32const RLF_HASH_B: i64 = 668265263 33const RLF_HASH_C: i64 = 1610612741 34const RLF_HASH_MIX: i64 = 1274126177 35const RLF_AVAL_1: i64 = 13 36const RLF_AVAL_2: i64 = 16 37const RLF_UNIT: i64 = 1024 // interpolation unit AND the hash codomain modulus 38const RLF_OFF: i64 = 1048576 // coordinate bias so floor-division stays positive for negative coords 39// the field's codomain is 0..RLF_H_RANGE inclusive, i.e. RLF_UNIT-1. The compiler takes literal const 40// initialisers only, so the relationship is PINNED BY A GATE TOOTH instead of by arithmetic here. 41const RLF_H_RANGE: i64 = 1023 42 43func rlf_hash3(a: i64, b: i64, c: i64) -> i64 { 44 var h: i64 = a*RLF_HASH_A + b*RLF_HASH_B + c*RLF_HASH_C 45 h = h ^ (h >> RLF_AVAL_1); h = h * RLF_HASH_MIX; h = h ^ (h >> RLF_AVAL_2) 46 if h < 0 { h = 0 - h } 47 return h % RLF_UNIT 48} 49// trilinear value noise at 3D position, lattice scale S. Returns 0..RLF_H_RANGE (smooth). 50func rlf_noise3(x: i64, y: i64, z: i64, S: i64) -> i64 { 51 let ox: i64 = x + RLF_OFF; let oy: i64 = y + RLF_OFF; let oz: i64 = z + RLF_OFF 52 let gx: i64 = ox/S; let gy: i64 = oy/S; let gz: i64 = oz/S 53 let fx: i64 = (ox - gx*S)*RLF_UNIT/S; let fy: i64 = (oy - gy*S)*RLF_UNIT/S; let fz: i64 = (oz - gz*S)*RLF_UNIT/S 54 let c000: i64 = rlf_hash3(gx,gy,gz); let c100: i64 = rlf_hash3(gx+1,gy,gz) 55 let c010: i64 = rlf_hash3(gx,gy+1,gz); let c110: i64 = rlf_hash3(gx+1,gy+1,gz) 56 let c001: i64 = rlf_hash3(gx,gy,gz+1); let c101: i64 = rlf_hash3(gx+1,gy,gz+1) 57 let c011: i64 = rlf_hash3(gx,gy+1,gz+1); let c111: i64 = rlf_hash3(gx+1,gy+1,gz+1) 58 let x00: i64 = c000 + (c100-c000)*fx/RLF_UNIT 59 let x10: i64 = c010 + (c110-c010)*fx/RLF_UNIT 60 let x01: i64 = c001 + (c101-c001)*fx/RLF_UNIT 61 let x11: i64 = c011 + (c111-c011)*fx/RLF_UNIT 62 let y0: i64 = x00 + (x10-x00)*fy/RLF_UNIT 63 let y1: i64 = x01 + (x11-x01)*fy/RLF_UNIT 64 return y0 + (y1-y0)*fz/RLF_UNIT 65} 66 67// --------------------------------------------------------------------------------------------- 68// THE LAW. Micro-relief in MICRONS with the wavelength it varies over. Every strength downstream is 69// DERIVED from these two against a run, never dialled -- which is the parameter this whole class of 70// pipeline usually hand-waves. They also tell the truth about resolution: at 4096 over a whole body a 71// texel is ~415um, so fine wrinkles (~1.2mm) resolve and PORES (~100um) DO NOT. Pores need a 72// per-region atlas, not a bigger whole-body one; that is a rung, not a knob. 73const RLF_RELIEF_UM: i64 = 60 74const RLF_RELIEF_LAMBDA_UM: i64 = 1200 75const RLF_BODY_UM: i64 = 1700000 // a 1.7 m adult; the atlas covers the whole body 76// peak tilt of a relief of full swing A over wavelength L is about 4A/L -- rise 2A over run L/2, plain 77// geometry, stated as a model and not measured. 78const RLF_TILT_K: i64 = 4 79 80func rlf_texel_um(res: i64) -> i64 { 81 var t: i64 = RLF_BODY_UM / res 82 if t < 1 { t = 1 } 83 return t 84} 85// the relief lattice in TEXELS at an atlas resolution. Clamped at 2 because a lattice under two texels 86// is not band-limited by the grid it is sampled on -- and the clamp is exactly why an atlas can encode 87// LESS relief than the law states (see rlf_atlas_tilt_full). 88func rlf_relief_lat(res: i64) -> i64 { 89 var lat: i64 = RLF_RELIEF_LAMBDA_UM / rlf_texel_um(res) 90 if lat < 2 { lat = 2 } 91 return lat 92} 93// relief height at an ATLAS texel, 0..RLF_H_RANGE, band-limited to the stated wavelength. 94func rlf_relief_h(x: i64, y: i64, res: i64, seed: i64) -> i64 { 95 return rlf_noise3(x, y, seed, rlf_relief_lat(res)) 96} 97// the SAME height field sampled VOLUMETRICALLY at a caller-declared lattice, for a consumer that has 98// no UV to index an atlas with. Same hash, same interpolation, same codomain -- it is the baked map's 99// field, read where a ray-marcher can reach it. 100func rlf_relief_h3(x: i64, y: i64, z: i64, cell: i64) -> i64 { 101 var c: i64 = cell 102 if c < 2 { c = 2 } 103 return rlf_noise3(x, y, z, c) 104} 105// a raw height DELTA (codomain units) -> the physical rise it represents, in microns. 106func rlf_slope_um(dh: i64) -> i64 { return dh * RLF_RELIEF_UM / RLF_H_RANGE } 107// the run a two-sided texel gradient spans, in microns. 108func rlf_run_um(res: i64) -> i64 { return 2 * rlf_texel_um(res) } 109// THE RELIEF'S OWN ANGULAR WIDTH on a caller's unit-normal scale (n_full = the value of a full normal 110// component). This is the quantity a diffusion length competes against. 111func rlf_tilt_full(n_full: i64) -> i64 { return n_full * RLF_TILT_K * RLF_RELIEF_UM / RLF_RELIEF_LAMBDA_UM } 112// THE PEAK TILT AN ATLAS AT res CAN ACTUALLY ENCODE -- full swing over the two-texel run. Below 113// rlf_tilt_full whenever the texel footprint has clamped the lattice, which on a whole-body atlas it 114// always has. Published rather than assumed, so nobody reads a re-sampled atlas as the law itself. 115func rlf_atlas_tilt_full(res: i64, n_full: i64) -> i64 { return n_full * RLF_RELIEF_UM / rlf_run_um(res) } 116 117// --------------------------------------------------------------------------------------------- 118// MULTI-BAND MICRO-RELIEF AND THE DISCRETE PORE LAYER (2026-08-28). /compare/graphics C5, symbol 119// rlf_pore_mask. STRICTLY ADDITIVE: nothing above this line changed, so nx_trimesh, 120// nx_worldpipe_core, nx_nxa_texbake_lib and nx_sdfrender keep byte-identical behaviour and 121// nx_sdfrender_sss_gate's T1 (flat skin bit-exact) and T4 (shader field IS baker field) still hold. 122// 123// WHY MORE BANDS, IN ARITHMETIC RATHER THAN TASTE. rlf_tilt_full says tilt = RLF_TILT_K * A / L, 124// so what a shading model can see of a band is its ASPECT RATIO A/L, never its depth. One band at 125// 1200um is therefore a ceiling no amplitude can lift: nx_sdfrender_sss_gate measures the shipped 126// sampler reaching 19 of the law's own 51 on the N.L axis. A SHALLOWER, FINER band outranks a 127// deeper coarse one -- band 1 below is a third the depth of band 0 and carries 60% more tilt. 128// That is the measured shape of "the bodies are low definition": right shape, one frequency. 129// 130// A PORE IS NOT A SPATIAL FREQUENCY, AND THAT DISTINCTION IS THE RUNG. A follicular orifice is a 131// sparse discrete pit carrying TWO scales -- a coarse PITCH between orifices and a fine DIAMETER -- 132// and only the pitch has to be carried by a lattice. So the pore layer is a jittered-grid cellular 133// field (one pit per cell, centre displaced by a bounded hash offset, blue-noise spaced rather than 134// gridded) with the pit profile evaluated ANALYTICALLY from the distance to that centre. A noise 135// octave cannot represent it at any resolution, which is why a third octave would not have done and 136// a mask was needed. 137// 138// THE NUMBERS ARE DATA. Every band has a row in knowledge/relief_bands.conf carrying its source and 139// nx_relief_bands_gate REFUSES if a const here and its row disagree. Two of three sources read 140// UNPINNED: the estate's sovereign index returned no dermatology literature on 2026-08-28 and 141// nothing was mirrored, so an honest absence is recorded rather than a borrowed citation. 142// 143// DECLARED IMPRECISION, and it is the whole finding: a band only exists once the sampler's lattice 144// can carry it. rlf_band_res_needed publishes, per band, the atlas resolution at which a band stops 145// being clamped away -- and on a WHOLE-BODY atlas neither new band survives (415um texels at 4096 146// against a 250um band), while both resolve comfortably on a per-REGION atlas. This file therefore 147// ships the capability AND the statement of where it is real, so nobody reads a pore claim as 148// delivered on the whole-body path. That was predicted in this file's own header on 2026-08-25 149// (pores need a per-region atlas, not a bigger whole-body one); it is now arithmetic a gate can 150// check instead of a sentence. 151 152// ---- band 1: SECONDARY LINES ---- 153const RLF_SEC_UM: i64 = 20 // swing, microns 154const RLF_SEC_LAMBDA_UM: i64 = 250 // wavelength, microns 155// ---- band 2: PORES (discrete) ---- 156const RLF_PORE_UM: i64 = 30 // pit depth, microns 157const RLF_PORE_PITCH_UM: i64 = 800 // mean spacing between orifices, microns 158const RLF_PORE_DIA_UM: i64 = 100 // orifice diameter, microns 159const RLF_NBAND: i64 = 3 160// jitter swing of a pit centre, in RLF_UNIT-of-a-cell. Bounded to HALF a cell ON PURPOSE: that 161// bound is exactly what makes the 3x3 neighbourhood search below EXACT rather than approximate, 162// because no point's nearest centre can then lie outside its own or an adjacent cell. 163const RLF_PORE_JIT: i64 = 512 164// larger than any squared distance the 3x3 search can produce, so it is a safe starting minimum. 165const RLF_PORE_D2_INF: i64 = 1000000000 166// pi x 1000, for the pore coverage fraction. The only transcendental in this file. 167const RLF_PI_E3: i64 = 3142 168 169// per-band angular width on a caller's unit-normal scale. rlf_tilt_full IS this at band 0 and the 170// gate pins that identity -- ONE ruler, so a band can never be measured by a second convention. 171func rlf_band_tilt_full(n_full: i64, a_um: i64, lambda_um: i64) -> i64 { 172 if lambda_um <= 0 { return 0 } 173 return n_full * RLF_TILT_K * a_um / lambda_um 174} 175// the lattice, in texels, a band of this wavelength wants at an atlas resolution -- the same clamp 176// rlf_relief_lat applies, because the reason for the clamp is the sampling grid, not the band. 177func rlf_band_lat(res: i64, lambda_um: i64) -> i64 { 178 var lat: i64 = lambda_um / rlf_texel_um(res) 179 if lat < 2 { lat = 2 } 180 return lat 181} 182// 1 = this resolution carries the band. 0 = the texel footprint clamped it away, and what gets 183// baked is a DIFFERENT, coarser band wearing this one's name. 184func rlf_band_resolvable(res: i64, lambda_um: i64) -> i64 { 185 if lambda_um / rlf_texel_um(res) < 2 { return 0 } 186 return 1 187} 188// THE ACTIONABLE NUMBER: the atlas resolution at which a band of this wavelength stops being 189// clamped, over a surface spanning span_um. Two texels per wavelength is the same bar 190// rlf_relief_lat enforces, so this INVERTS that function rather than inventing a second rule. 191func rlf_band_res_needed(span_um: i64, lambda_um: i64) -> i64 { 192 if lambda_um <= 0 { return 0 } 193 return 2 * span_um / lambda_um 194} 195 196// texel footprint over an ARBITRARY span. rlf_texel_um is exactly this at the whole-body span, and 197// the gate pins that identity -- the whole-body case is a special case here, not a second rule. 198func rlf_texel_um_span(span_um: i64, res: i64) -> i64 { 199 var t: i64 = span_um / res 200 if t < 1 { t = 1 } 201 return t 202} 203// 1 = an atlas of this resolution over a region of this span carries the band. rlf_band_resolvable 204// is exactly this at the whole-body span, which is why a per-REGION atlas can carry what a 205// whole-body one clamps away -- the same band, a smaller footprint. 206func rlf_band_resolvable_span(span_um: i64, res: i64, lambda_um: i64) -> i64 { 207 if lambda_um / rlf_texel_um_span(span_um, res) < 2 { return 0 } 208 return 1 209} 210 211// jittered pit centre for lattice cell (cx,cy), one axis, in RLF_UNIT-of-a-cell. 212func rlf_pore_centre(cx: i64, cy: i64, axis: i64) -> i64 { 213 let h: i64 = rlf_hash3(cx, cy, axis + 1) 214 return (RLF_UNIT / 2) + (h % (RLF_PORE_JIT + 1)) - (RLF_PORE_JIT / 2) 215} 216// squared distance from (x,y) to the nearest pit centre, in (RLF_UNIT-of-a-cell) squared. 217func rlf_pore_d2(x: i64, y: i64, cell: i64) -> i64 { 218 var c: i64 = cell 219 if c < 2 { c = 2 } 220 let ox: i64 = x + RLF_OFF 221 let oy: i64 = y + RLF_OFF 222 let gx: i64 = ox / c 223 let gy: i64 = oy / c 224 let px: i64 = (ox - gx * c) * RLF_UNIT / c 225 let py: i64 = (oy - gy * c) * RLF_UNIT / c 226 var best: i64 = RLF_PORE_D2_INF 227 var dy: i64 = 0 - 1 228 while dy <= 1 { 229 var dx: i64 = 0 - 1 230 while dx <= 1 { 231 let qx: i64 = dx * RLF_UNIT + rlf_pore_centre(gx + dx, gy + dy, 0) - px 232 let qy: i64 = dy * RLF_UNIT + rlf_pore_centre(gx + dx, gy + dy, 1) - py 233 let d: i64 = qx * qx + qy * qy 234 if d < best { best = d } 235 dx = dx + 1 236 } 237 dy = dy + 1 238 } 239 return best 240} 241// the pit radius in RLF_UNIT-of-a-cell: the orifice diameter as a fraction of the pitch. DERIVED 242// from the two conf numbers, so changing either moves the pit and nothing has to be re-tuned. 243func rlf_pore_radius() -> i64 { return RLF_UNIT * RLF_PORE_DIA_UM / (2 * RLF_PORE_PITCH_UM) } 244// the fraction of skin a pit covers, in permil. Sparse BY CONSTRUCTION -- this is what makes the 245// layer a pore field and not a fourth noise octave, and it is published so the claim is checkable. 246func rlf_pore_area_permil() -> i64 { 247 let r: i64 = rlf_pore_radius() 248 return RLF_PI_E3 * r * r / (RLF_UNIT * RLF_UNIT) 249} 250// THE PORE MASK. 0 on flat skin, rising to RLF_H_RANGE at a pit centre, quadratic in between so 251// the pit floor is smooth and the rim carries no cusp. 252func rlf_pore_mask(x: i64, y: i64, cell: i64) -> i64 { 253 let r: i64 = rlf_pore_radius() 254 if r <= 0 { return 0 } 255 let r2: i64 = r * r 256 let d2: i64 = rlf_pore_d2(x, y, cell) 257 if d2 >= r2 { return 0 } 258 return (r2 - d2) * RLF_H_RANGE / r2 259} 260// A PIT IS NOT A WAVE, so its tilt is NOT rlf_tilt_full's 4A/L. The steepest point of a quadratic 261// pit of depth D and radius R is the rim, where the wall rises D over the run R: slope = D/R. 262// Stated separately rather than forced through the wave formula, because reusing a derivation that 263// does not apply is how a plausible number stops being a derived one. 264func rlf_pore_tilt_full(n_full: i64) -> i64 { 265 let r_um: i64 = RLF_PORE_DIA_UM / 2 266 if r_um <= 0 { return 0 } 267 return n_full * RLF_PORE_UM / r_um 268} 269// the total swing the three bands can produce, in microns. A function and not a const because the 270// compiler takes literal const initialisers only; the gate pins the relationship. 271func rlf_total_um() -> i64 { return RLF_RELIEF_UM + RLF_SEC_UM + RLF_PORE_UM } 272// the tilt the CONTINUOUS bands (0 and 1) together present to a shading model. They compose in 273// SLOPE, which is why this is a sum. A band the atlas cannot carry contributes NOTHING -- the 274// honest number rather than the nominal one. The pore term is deliberately NOT summed in here: it 275// covers about 1% of the surface, so adding a sparse peak to a continuous one would report an 276// envelope as an average. Ask rlf_pore_tilt_full and rlf_pore_area_permil for that band. 277func rlf_multi_tilt_full(res: i64, n_full: i64) -> i64 { 278 var t: i64 = rlf_band_tilt_full(n_full, RLF_RELIEF_UM, RLF_RELIEF_LAMBDA_UM) 279 if rlf_band_resolvable(res, RLF_SEC_LAMBDA_UM) == 1 { 280 t = t + rlf_band_tilt_full(n_full, RLF_SEC_UM, RLF_SEC_LAMBDA_UM) 281 } 282 return t 283} 284// THE COMPOSED ATLAS HEIGHT, IN MICRONS. Bands 0 and 1 are additive noise weighted by their own 285// swings; the pore layer SUBTRACTS, because an orifice is a pit and not a bump. Signed on purpose: 286// a pit below the mean surface is the physical truth, and clamping it here would hide it. 287func rlf_relief_um2(x: i64, y: i64, res: i64, seed: i64) -> i64 { 288 let h0: i64 = rlf_relief_h(x, y, res, seed) 289 let h1: i64 = rlf_noise3(x, y, seed, rlf_band_lat(res, RLF_SEC_LAMBDA_UM)) 290 let pm: i64 = rlf_pore_mask(x, y, rlf_band_lat(res, RLF_PORE_PITCH_UM)) 291 let up: i64 = (h0 * RLF_RELIEF_UM + h1 * RLF_SEC_UM) / RLF_H_RANGE 292 return up - pm * RLF_PORE_UM / RLF_H_RANGE 293} 294 295// --------------------------------------------------------------------------------------------- 296// PER-REGION COMPOSITION OVER AN ARBITRARY SPAN (2026-08-30, /compare/graphics GR29 297// gpe_skin_microsurface, the ASSET half). STRICTLY ADDITIVE: nothing above this line changed. 298// rlf_band_resolvable_span already said WHERE a band is real; these are the field and the run at 299// that span, so a per-REGION atlas (nx_nxa_texbake_region_lib) bakes the same law the whole-body 300// baker bakes, on the footprint it actually has. ONE declared difference from rlf_relief_um2: a 301// band the span cannot carry contributes NOTHING here, where the whole-body composition clamps its 302// lattice to two texels and bakes a coarser alias wearing the band's name. That alias is exactly 303// what the GR29 row measured as "painted", so the region path refuses to produce it. 304// the lattice in texels a band wants over span_um at res -- the same 2-texel clamp, same reason 305func rlf_band_lat_span(span_um: i64, res: i64, lambda_um: i64) -> i64 { 306 var lat: i64 = lambda_um / rlf_texel_um_span(span_um, res) 307 if lat < 2 { lat = 2 } 308 return lat 309} 310// the two-sided gradient run over span_um at res, in microns (rlf_run_um is this at the body span) 311func rlf_run_um_span(span_um: i64, res: i64) -> i64 { return 2 * rlf_texel_um_span(span_um, res) } 312// the bands (span,res) carries, as a mask: 1 primary, 2 secondary, 4 pore-grid (pitch), 8 pore-orifice 313// (diameter). The orifice is a STRICTER bar than its lattice and is a separate bit for that reason. 314func rlf_bands_carried_span(span_um: i64, res: i64) -> i64 { 315 var m: i64 = 0 316 if rlf_band_resolvable_span(span_um, res, RLF_RELIEF_LAMBDA_UM) == 1 { m = m + 1 } 317 if rlf_band_resolvable_span(span_um, res, RLF_SEC_LAMBDA_UM) == 1 { m = m + 2 } 318 if rlf_band_resolvable_span(span_um, res, RLF_PORE_PITCH_UM) == 1 { m = m + 4 } 319 if rlf_band_resolvable_span(span_um, res, RLF_PORE_DIA_UM) == 1 { m = m + 8 } 320 return m 321} 322// how many of the four bands a mask carries 323func rlf_bands_count(mask: i64) -> i64 { 324 var n: i64 = 0 325 if (mask & 1) == 1 { n = n + 1 } 326 if (mask & 2) == 2 { n = n + 1 } 327 if (mask & 4) == 4 { n = n + 1 } 328 if (mask & 8) == 8 { n = n + 1 } 329 return n 330} 331// the pore mask with an EXPLICIT pit radius (RLF_UNIT-of-a-cell). rlf_pore_mask is this at the 332// derived radius and now delegates -- one pit profile. The explicit radius exists for the case the 333// span carries the pore LATTICE but not the ORIFICE: the region baker then widens the pit to one 334// texel and scales its depth by the area ratio (a shallower dimple, announced as DIMPLE), instead of 335// baking a sub-texel pit that lands on a texel centre by luck and reads as sparkle. 336func rlf_pore_mask_r(x: i64, y: i64, cell: i64, r: i64) -> i64 { 337 if r <= 0 { return 0 } 338 let r2: i64 = r * r 339 let d2: i64 = rlf_pore_d2(x, y, cell) 340 if d2 >= r2 { return 0 } 341 return (r2 - d2) * RLF_H_RANGE / r2 342} 343// THE COMPOSED HEIGHT OVER span_um AT res, IN MICRONS. Bands enter only where carried (see the 344// header above). Pore pits enter when the LATTICE carries; the pit radius is the derived one when 345// the orifice carries and one texel otherwise, depth scaled by (r_true/r_eff)^2 so the pit keeps 346// its volume. Signed: a pit is below the mean surface. 347func rlf_relief_um2_span(x: i64, y: i64, span_um: i64, res: i64, seed: i64) -> i64 { 348 let m: i64 = rlf_bands_carried_span(span_um, res) 349 var up: i64 = 0 350 if (m & 1) == 1 { up = up + rlf_noise3(x, y, seed, rlf_band_lat_span(span_um, res, RLF_RELIEF_LAMBDA_UM)) * RLF_RELIEF_UM } 351 if (m & 2) == 2 { up = up + rlf_noise3(x, y, seed, rlf_band_lat_span(span_um, res, RLF_SEC_LAMBDA_UM)) * RLF_SEC_UM } 352 up = up / RLF_H_RANGE 353 if (m & 4) == 4 { 354 let cell: i64 = rlf_band_lat_span(span_um, res, RLF_PORE_PITCH_UM) 355 let rt: i64 = rlf_pore_radius() 356 var re: i64 = rt 357 var depth: i64 = RLF_PORE_UM 358 if (m & 8) != 8 { 359 // one texel, in RLF_UNIT-of-a-cell 360 let one: i64 = RLF_UNIT / cell 361 if one > rt { re = one; depth = RLF_PORE_UM * rt * rt / (re * re) } 362 } 363 up = up - rlf_pore_mask_r(x, y, cell, re) * depth / RLF_H_RANGE 364 } 365 return up 366} 367 368// --------------------------------------------------------------------------------------------- 369// THE PERIODIC MICRO TILE (2026-08-30, GR29 gpe_skin_microsurface asset half; operator directive 370// "for per region we need texture ... make sure we aren't just doing more smooth surface painting"). 371// STRICTLY ADDITIVE: nothing above this line changed. 372// 373// WHY A TILE AT ALL, IN ARITHMETIC. A UNIQUE atlas samples the body at (chart extent / tile px), and 374// on the incumbent per-joint cylindrical charts that extent is a bone's CIRCUMFERENCE -- measured by 375// nx_nxa_texbake_region_lib per tile, not assumed from stature. No single-atlas resolution this 376// estate ships can put two texels inside a 250um secondary line around a 900mm chest. The field's 377// practice for exactly this case is a TILING micro-relief whose texel pitch is physical by 378// construction and which the shader repeats per chart at (chart extent / tile repeat): MetaHuman 379// keeps wrinkles in the unique normal and puts PORES in a tiling texture [@mh-materials], and the 380// scan vendors ship the micro band as its own channel beside the unique displacement [@txyz-vface]. 381// The unique per-region atlas therefore carries what its footprint can (measured), and the three 382// finer bands ride THIS tile, from the SAME law and the SAME constants. 383// 384// PERIODICITY IS EXACT, NOT FADED. The tile's texel is two per pore ORIFICE (the same 2-texel bar 385// every band is judged by, applied to the finest feature), so every band's lattice is an integer 386// count of texels; the tile edge is the LCM of those lattices, so every band closes on itself; and 387// the lattice hash wraps its cell index modulo the tile's cell count, so the texel at x and the 388// texel at x + tile are the SAME bytes. A gate tooth pins that identity rather than a blend seam. 389// RLF_TILE_LCM_MULT buys a longer physical repeat than the bare LCM (240 texels = 12mm) so the 390// repeat is not visible as a grid at arm's length; 2 gives 24mm, one gland field wide. 391const RLF_TILE_LCM_MULT: i64 = 2 392// band mask bits shared with rlf_bands_carried_span: 1 primary, 2 secondary, 4 pore-grid, 8 orifice 393const RLF_BAND_PRIMARY: i64 = 1 394const RLF_BAND_SECONDARY: i64 = 2 395const RLF_BAND_POREGRID: i64 = 4 396const RLF_BAND_ORIFICE: i64 = 8 397const RLF_BAND_ALL: i64 = 15 398 399func rlf_gcd(a: i64, b: i64) -> i64 { 400 var x: i64 = a 401 var y: i64 = b 402 if x < 0 { x = 0 - x } 403 if y < 0 { y = 0 - y } 404 while y != 0 { let t: i64 = x % y; x = y; y = t } 405 return x 406} 407func rlf_lcm(a: i64, b: i64) -> i64 { 408 let g: i64 = rlf_gcd(a, b) 409 if g == 0 { return 0 } 410 return a / g * b 411} 412// the tile's texel pitch in microns: two texels per pore orifice, DERIVED from the orifice diameter 413func rlf_tile_texel_um() -> i64 { return RLF_PORE_DIA_UM / 2 } 414// a band's lattice on the tile, in texels (24 / 5 / 16 at the shipped constants) 415func rlf_tile_lat(lambda_um: i64) -> i64 { 416 var l: i64 = lambda_um / rlf_tile_texel_um() 417 if l < 2 { l = 2 } 418 return l 419} 420// the tile edge in texels: LCM of the three lattices, times the repeat multiplier (480 at shipped constants) 421func rlf_tile_px() -> i64 { 422 let l0: i64 = rlf_tile_lat(RLF_RELIEF_LAMBDA_UM) 423 let l1: i64 = rlf_tile_lat(RLF_SEC_LAMBDA_UM) 424 let lp: i64 = rlf_tile_lat(RLF_PORE_PITCH_UM) 425 return rlf_lcm(rlf_lcm(l0, l1), lp) * RLF_TILE_LCM_MULT 426} 427// the physical repeat of one tile, in microns (24000 at shipped constants) 428func rlf_tile_repeat_um() -> i64 { return rlf_tile_px() * rlf_tile_texel_um() } 429// the bands the tile carries, by the same 2-texel bar rlf_bands_carried_span applies -- all four 430// at the shipped constants, and a gate pins that rather than this comment 431func rlf_tile_bands_carried() -> i64 { return rlf_bands_carried_span(rlf_tile_repeat_um(), rlf_tile_px()) } 432// wrap a texel coordinate onto [0, px) 433func rlf_wrap(x: i64, px: i64) -> i64 { 434 var r: i64 = x % px 435 if r < 0 { r = r + px } 436 return r 437} 438// the lattice hash with the cell index wrapped modulo per: cell per-1 and cell 0 are neighbours 439func rlf_hash3_per(gx: i64, gy: i64, z: i64, per: i64) -> i64 { 440 return rlf_hash3(rlf_wrap(gx, per), rlf_wrap(gy, per), z) 441} 442// PERIODIC value noise: rlf_noise3's interpolation on a lattice of S texels whose cell count per 443// edge is per, so the field repeats every S*per texels. No RLF_OFF bias -- the coordinate is wrapped 444// onto the tile first, which is what keeps the seam ON the tile edge instead of at an arbitrary 445// multiple of S. 446func rlf_noise3_per(x: i64, y: i64, z: i64, S: i64, per: i64) -> i64 { 447 let px: i64 = S * per 448 let ox: i64 = rlf_wrap(x, px) 449 let oy: i64 = rlf_wrap(y, px) 450 let gx: i64 = ox / S 451 let gy: i64 = oy / S 452 let fx: i64 = (ox - gx*S)*RLF_UNIT/S 453 let fy: i64 = (oy - gy*S)*RLF_UNIT/S 454 let c00: i64 = rlf_hash3_per(gx, gy, z, per) 455 let c10: i64 = rlf_hash3_per(gx+1, gy, z, per) 456 let c01: i64 = rlf_hash3_per(gx, gy+1, z, per) 457 let c11: i64 = rlf_hash3_per(gx+1, gy+1, z, per) 458 let x0: i64 = c00 + (c10-c00)*fx/RLF_UNIT 459 let x1: i64 = c01 + (c11-c01)*fx/RLF_UNIT 460 return x0 + (x1-x0)*fy/RLF_UNIT 461} 462// periodic pit centre: the same jitter, the cell index wrapped 463func rlf_pore_centre_per(cx: i64, cy: i64, axis: i64, per: i64) -> i64 { 464 let h: i64 = rlf_hash3_per(cx, cy, axis + 1, per) 465 return (RLF_UNIT / 2) + (h % (RLF_PORE_JIT + 1)) - (RLF_PORE_JIT / 2) 466} 467// periodic squared distance to the nearest pit centre; the 3x3 search is exact for the same reason 468// as rlf_pore_d2 (jitter bounded to half a cell), and the wrap makes the tile edge a real neighbour 469func rlf_pore_d2_per(x: i64, y: i64, cell: i64, per: i64) -> i64 { 470 var c: i64 = cell 471 if c < 2 { c = 2 } 472 let px: i64 = c * per 473 let ox: i64 = rlf_wrap(x, px) 474 let oy: i64 = rlf_wrap(y, px) 475 let gx: i64 = ox / c 476 let gy: i64 = oy / c 477 let fx: i64 = (ox - gx * c) * RLF_UNIT / c 478 let fy: i64 = (oy - gy * c) * RLF_UNIT / c 479 var best: i64 = RLF_PORE_D2_INF 480 var dy: i64 = 0 - 1 481 while dy <= 1 { 482 var dx: i64 = 0 - 1 483 while dx <= 1 { 484 let qx: i64 = dx * RLF_UNIT + rlf_pore_centre_per(gx + dx, gy + dy, 0, per) - fx 485 let qy: i64 = dy * RLF_UNIT + rlf_pore_centre_per(gx + dx, gy + dy, 1, per) - fy 486 let d: i64 = qx * qx + qy * qy 487 if d < best { best = d } 488 dx = dx + 1 489 } 490 dy = dy + 1 491 } 492 return best 493} 494// the periodic pit profile, same quadratic as rlf_pore_mask_r 495func rlf_pore_mask_per(x: i64, y: i64, cell: i64, r: i64, per: i64) -> i64 { 496 if r <= 0 { return 0 } 497 let r2: i64 = r * r 498 let d2: i64 = rlf_pore_d2_per(x, y, cell, per) 499 if d2 >= r2 { return 0 } 500 return (r2 - d2) * RLF_H_RANGE / r2 501} 502// THE COMPOSED TILE HEIGHT IN MICRONS with a band mask (the gate's smooth control is this with a 503// narrower mask -- same code path, so the control differs from the subject ONLY in the bands). 504// Periodic in x and y with period rlf_tile_px(). Signed: pits go below the mean surface. 505func rlf_relief_um2_tile_bands(x: i64, y: i64, seed: i64, mask: i64) -> i64 { 506 let px: i64 = rlf_tile_px() 507 var up: i64 = 0 508 if (mask & RLF_BAND_PRIMARY) == RLF_BAND_PRIMARY { 509 let l0: i64 = rlf_tile_lat(RLF_RELIEF_LAMBDA_UM) 510 up = up + rlf_noise3_per(x, y, seed, l0, px / l0) * RLF_RELIEF_UM 511 } 512 if (mask & RLF_BAND_SECONDARY) == RLF_BAND_SECONDARY { 513 let l1: i64 = rlf_tile_lat(RLF_SEC_LAMBDA_UM) 514 up = up + rlf_noise3_per(x, y, seed, l1, px / l1) * RLF_SEC_UM 515 } 516 up = up / RLF_H_RANGE 517 if (mask & RLF_BAND_POREGRID) == RLF_BAND_POREGRID { 518 let lp: i64 = rlf_tile_lat(RLF_PORE_PITCH_UM) 519 up = up - rlf_pore_mask_per(x, y, lp, rlf_pore_radius(), px / lp) * RLF_PORE_UM / RLF_H_RANGE 520 } 521 return up 522} 523// the shipped tile: every band 524func rlf_relief_um2_tile(x: i64, y: i64, seed: i64) -> i64 { 525 return rlf_relief_um2_tile_bands(x, y, seed, RLF_BAND_ALL) 526}