[lane A] Procedural epithelium: a Voronoi cell pavement in the wall shader
Stolen from the shader half of Sebastian Lague's graphics-library video (the Voronoi farm-plot trick), because it turns out to be an anatomy tool rather than a decoration: mucosa at SEM scale IS a Voronoi diagram. Lane D's own oral prompt asks FLUX for a "cobblestone pavement of flat polygonal squamous epithelial cells" — that is a Voronoi described in English, and generating it in the fragment shader instead is free, never repeats, and can do the one thing a baked texture never will: BREATHE. Membrane width rides vPulse, the same wave the vertex shader displaces with, so the pavement compresses exactly where the muscle contracts. - F2-F1 Voronoi (9-tap), multiplied into the detail LUMINANCE, never added — D's texture stays the author of the look and this is structure underneath it. Applied to both the textured and the assets-optional fallback path. - Rides D's own tiling (duv * scale), so cells hold their physical size on a radius-7 hiatus and a radius-70 sea — the upstream arc-length fix does that work for free. - Compiled out with a define, not branched out on a uniform: a biome that wants no pavement pays nothing for nine hashes a pixel. Per-biome, because a pavement is a CLAIM ABOUT TISSUE and not a filter: oral 0.50 (squamous epithelium is the strongest case in the body), appendix 0.48 and colon 0.42 (biofilm mats are literally cells), stomach 0.38 (gastric pits), small intestine 0.20 and esophagus 0.18 (villi are projections and folds are folds — there it is a whisper that only breaks the texture repeat). Verified: per-biome values reach the material (esophagus 0.18, not the 0.35 default), the effect reads on the wall at gameplay distance, and world.hash() is byte-identical (bea3807c) before and after — this is pixels only, the determinism law is untouched. Not measured: frame cost on real hardware. Nine hashes per wall pixel is not free and the budget is 60fps on mid laptops; if it bites, the honest lever is dropping the low-gain biomes to 0 (which compiles it out entirely) rather than cheapening the shader. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a318cf09f7
commit
d39c0b9358
@ -17,6 +17,7 @@
|
||||
|
||||
export const BIOMES = {
|
||||
oral: {
|
||||
cells: 0.5, cellScale: 6.0, // squamous epithelium IS a cobblestone pavement — the strongest case in the body
|
||||
id: 'oral',
|
||||
palette: { tint: 0xa8c4d8, rim: 0xdff2ff, void: 0x06090e },
|
||||
fog: 0.016, // clinical, bright, tutorial-readable => you can see across the cavity
|
||||
@ -25,6 +26,7 @@ export const BIOMES = {
|
||||
wave: { amp: 0.5, breathe: 0.10 }, // saliva tides, not real peristalsis
|
||||
},
|
||||
esophagus: {
|
||||
cells: 0.18, cellScale: 7.0, // ribbed longitudinal folds, not a pavement: barely a whisper, just to break the repeat
|
||||
id: 'esophagus',
|
||||
palette: { tint: 0x1e6e64, rim: 0x5affd2, void: 0x02100d },
|
||||
fog: 0.028,
|
||||
@ -33,6 +35,7 @@ export const BIOMES = {
|
||||
wave: { amp: 1.4, breathe: 0.15 }, // the signature: ribbed rings rushing past (measured)
|
||||
},
|
||||
stomach: {
|
||||
cells: 0.38, cellScale: 6.5, // gastric pits — a pitted field reads as cells at this scale
|
||||
id: 'stomach',
|
||||
palette: { tint: 0xb0571e, rim: 0xffb13a, void: 0x120801 },
|
||||
fog: 0.020,
|
||||
@ -41,6 +44,7 @@ export const BIOMES = {
|
||||
wave: { amp: 0.7, breathe: 0.25 }, // churn, not transit
|
||||
},
|
||||
small_intestine: {
|
||||
cells: 0.2, cellScale: 7.5, // villi are PROJECTIONS, not a pavement; keep it under D's texture
|
||||
id: 'small_intestine',
|
||||
palette: { tint: 0x7a2a6e, rim: 0xff6ad5, void: 0x0d0312 },
|
||||
fog: 0.050, // dense: villi forest occludes sightlines
|
||||
@ -49,6 +53,7 @@ export const BIOMES = {
|
||||
wave: { amp: 1.5, breathe: 0.18 },
|
||||
},
|
||||
large_intestine: {
|
||||
cells: 0.42, cellScale: 6.0, // smooth mucosa under biofilm — the cells are the visible structure here
|
||||
id: 'large_intestine',
|
||||
palette: { tint: 0x3a4a26, rim: 0x9dff4a, void: 0x050702 },
|
||||
fog: 0.075, // darkness is the mechanic here; scanner light is the safety bubble
|
||||
@ -57,6 +62,7 @@ export const BIOMES = {
|
||||
wave: { amp: 1.2, breathe: 0.2 },
|
||||
},
|
||||
appendix: {
|
||||
cells: 0.48, cellScale: 5.5, // the biofilm reservoir: mats of cells are literally the subject
|
||||
id: 'appendix',
|
||||
palette: { tint: 0x8a7020, rim: 0xffe066, void: 0x0a0800 },
|
||||
fog: 0.030,
|
||||
|
||||
@ -37,6 +37,19 @@ export function createWallMaterial({
|
||||
matcapGain = 0.14,
|
||||
radiusHint = 10, // used only to pick a square-ish default tiling
|
||||
side = THREE.FrontSide,
|
||||
// --- procedural epithelium (round 3) ------------------------------------------------
|
||||
// A Voronoi cell pavement multiplied into the detail luminance. This is not decoration for
|
||||
// its own sake: mucosa at SEM scale IS a Voronoi diagram — Lane D's own oral prompt asks
|
||||
// FLUX for a "cobblestone pavement of flat polygonal squamous epithelial cells", which is a
|
||||
// Voronoi described in words. Generating it in the shader instead costs no texture memory,
|
||||
// never repeats (the tiling it hides is the one thing a baked texture cannot fix), and —
|
||||
// the part a texture can never do — it can BREATHE, because the peristalsis phase is already
|
||||
// a varying right here.
|
||||
//
|
||||
// Per-biome, because the pavement is a claim about tissue: squamous epithelium (oral) is all
|
||||
// cells, ribbed esophageal folds are not. `biome.cells` overrides; 0 compiles it out entirely.
|
||||
cells = biome.cells ?? 0.35,
|
||||
cellScale = biome.cellScale ?? 7.0, // cells per texture repeat
|
||||
}) {
|
||||
for (const t of [detail, detailB, normalMap]) { // we consume D's textures; set what we rely on
|
||||
if (t) { t.wrapS = t.wrapT = THREE.RepeatWrapping; t.needsUpdate = true; }
|
||||
@ -65,6 +78,9 @@ export function createWallMaterial({
|
||||
const repB = repeatB ?? [rep[0] / 2.7, rep[1] / 2.7];
|
||||
|
||||
const defines = {};
|
||||
// Compiled out, not branched out: a 9-tap Voronoi is ~9 hashes per pixel over the whole wall,
|
||||
// and a `uniform == 0` test would still pay for it. A biome that wants no pavement pays zero.
|
||||
if (cells > 0) defines.USE_CELLS = '';
|
||||
if (detail) defines.USE_DETAIL = '';
|
||||
if (detail && detailB) defines.USE_DETAIL_B = '';
|
||||
if (normalMap) defines.USE_NORMAL = '';
|
||||
@ -98,6 +114,8 @@ export function createWallMaterial({
|
||||
uRepeat: { value: new THREE.Vector2(rep[0], rep[1]) },
|
||||
uRepeatB: { value: new THREE.Vector2(repB[0], repB[1]) },
|
||||
uThetaSpan: { value: thetaSpan },
|
||||
uCells: { value: cells },
|
||||
uCellScale: { value: cellScale },
|
||||
},
|
||||
vertexShader: /* glsl */`
|
||||
attribute vec3 aInward;
|
||||
@ -169,6 +187,33 @@ export function createWallMaterial({
|
||||
#endif
|
||||
varying vec2 vUv; varying vec3 vN; varying vec3 vView; varying float vPulse;
|
||||
|
||||
#ifdef USE_CELLS
|
||||
uniform float uCells, uCellScale;
|
||||
// Hash -> a stable per-cell offset. No trig-free cleverness needed: this is the standard
|
||||
// sin-fract hash and it is deterministic on every GPU we ship to, which is what the house
|
||||
// determinism law cares about (core/rng.js governs SIMULATION randomness; this is texture).
|
||||
vec2 cellHash(vec2 p) {
|
||||
p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
|
||||
return fract(sin(p) * 43758.5453);
|
||||
}
|
||||
// F2 - F1 Voronoi: ~0 on a cell border, large in a cell's interior. That difference (not
|
||||
// F1 alone) is what gives clean membrane lines of even width instead of round blobs.
|
||||
float cellEdge(vec2 p) {
|
||||
vec2 g = floor(p), f = fract(p);
|
||||
float f1 = 8.0, f2 = 8.0;
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
vec2 o = vec2(float(x), float(y));
|
||||
vec2 r = o + cellHash(g + o) - f;
|
||||
float d = dot(r, r);
|
||||
if (d < f1) { f2 = f1; f1 = d; }
|
||||
else if (d < f2) { f2 = d; }
|
||||
}
|
||||
}
|
||||
return sqrt(f2) - sqrt(f1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NORMAL
|
||||
// three's perturbNormal2Arb, lifted from Lane D's web/dev/laneD_texview.html.
|
||||
// The tube carries no tangent attribute, so rebuild the TBN per-pixel from screen
|
||||
@ -189,6 +234,21 @@ export function createWallMaterial({
|
||||
// .x from the arc-length density (vThetaU), .y from D's units-of-s-per-repeat.
|
||||
vec2 duv = vec2(vThetaU, vUv.y * uRepeat.y);
|
||||
|
||||
// Procedural epithelium. Rides D's OWN tiling (duv * scale), so cells are the same
|
||||
// physical size on a radius-7 hiatus and a radius-70 sea — the arc-length fix upstream
|
||||
// does this work for free. Membranes tighten on the crest: vPulse is the same wave the
|
||||
// vertex shader displaces with, so the pavement compresses exactly where the muscle
|
||||
// does. That is the one thing a baked texture can never do, and it is why this exists.
|
||||
float cellMod = 1.0;
|
||||
#ifdef USE_CELLS
|
||||
float edge = cellEdge(duv * uCellScale);
|
||||
float w = 0.16 - 0.05 * vPulse; // membranes squeeze as it contracts
|
||||
float pave = smoothstep(0.0, w, edge); // 0 = membrane line, 1 = cell body
|
||||
// Multiplied into luminance, never added: D's texture stays the author of the look and
|
||||
// this is structure underneath it. Cell bodies keep their value, membranes go dark.
|
||||
cellMod = mix(1.0, 0.42 + 0.58 * pave, uCells);
|
||||
#endif
|
||||
|
||||
#ifdef USE_DETAIL
|
||||
// Lane D authors grayscale luminance/AO; the biome tint is applied here, so one
|
||||
// texture serves two biomes at different tints (ART_BIBLE §FLUX prompt kit).
|
||||
@ -201,7 +261,7 @@ export function createWallMaterial({
|
||||
// D's round-1 curve was (0.35 + d*0.95)*0.9, measured on the raw framebuffer.
|
||||
// Post-colorspace that pedestal alone displays as ~60% grey; re-measured on the
|
||||
// contact sheet so a mid-grey texel lands near the tint's own luminance.
|
||||
vec3 base = uTint * (0.12 + detail * 0.60);
|
||||
vec3 base = uTint * (0.12 + detail * cellMod * 0.60);
|
||||
#else
|
||||
// Assets-optional law: no texture is the *shipping* look until D lands, not an error
|
||||
// state. Ridged folds around theta + striation along s = a passable SEM stand-in.
|
||||
@ -210,7 +270,7 @@ export function createWallMaterial({
|
||||
// detail² not detail: the sine bands only span ~0.4..1.0, and post-colorspace that
|
||||
// narrow range reads as a flat bright wall (the oral biome was near white-out).
|
||||
// Squaring restores the dark troughs the SEM look needs; still fallback art.
|
||||
vec3 base = uTint * detail * detail * 0.65;
|
||||
vec3 base = uTint * detail * detail * cellMod * 0.65;
|
||||
#endif
|
||||
|
||||
vec3 N = normalize(vN);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user