Granite: rectangular (lath) crystals and refactor check
Branch jiadeng/tbricks-semantic, commits 15809be33 (refactor) and 47cd161de (lath crystals). Renders: Cycles, 32 samples, head-on camera, 1024 px unless noted. Click any image for full size.
Very dark lath granite preset (2026-10-12, commit 918b05aeb)
First candidate (dark1) | dark4 | dark5 = granite_dark_lath_preset | red lath preset for comparison. Mean display lightness in parentheses. Bottom: 2x zoom.
Same crystal geometry and texture as the red lath preset: charcoal crystals, dark-gray pale crystals, near-black matrix, fewer pale crystals.
Two things kept it from looking dark at first: the matrix color jitter scattered white specks through the dark matrix (now low), and Blender's AgX view lifts dark colors so much that crystals at albedo 0.03 still read mid-gray. The preset uses 0.008.
The random lath generator's "dark" palette family gives dark crystals with light-gray pale crystals, so it does not produce stones this dark overall.
One color range per stone: main crystals use the preset's pink model for any palette family (a main color plus a slightly more orange, duller one, mixed by a skewed weight; no on/off gray group).
Main vs pale crystals always separate: pale crystals were 1.2-1.8x the main brightness, so white, cream and gray stones blurred. Now they are 1.6x the palette value plus 0.06-0.12 and the main crystals sit at 0.8x. Crystal brightness gap (95th pct minus median, weakest pale setting): white 0.080 → 0.122, cream 0.101 → 0.146, gray 0.107 → 0.150, and larger for every other family.
Ranges swept: each of the 26 random draws rendered at its min and max with the rest at midpoints (61 renders), ranges that barely changed the image widened and re-swept, then streak and per-crystal brightness capped after random seeds showed wood-grain striping and near-black crystals.
Each palette family at the weakest pale setting, before (white and cream barely separate).After.
Full sweep sheets (each row: min full | min 2x zoom | max full | max 2x zoom): geometry axes, color axes.
Red lath preset tuning, round 2: matrix, flecks, white patches (2026-10-12)
Pink fixed at the preset's. Variants in lath_tune_materials.py; the preset itself is unchanged.
Photo | current preset | x1 coarser, lighter, warmer matrix | x2 + larger, softer, dark-gray flecks | x3 + granular warm light grains, fewer white rectangles | x4 granular light grains only, no white rectangles.Photo | current preset | x2 | y1: x2 with low matrix color jitter | y2: y1 + moderate granular light grains.
Found: the matrix minerals' color texture uses a LINEAR_LIGHT value jitter that drives dark grains to black, so the matrix read black/white instead of dark gray. Lowering it (y1) gives real dark-gray patches: darkest band 12% to 5%, mid-dark up to 7.7% (photo 12-14%).
Still wrong in y1: the softer flecks look like blurry, out-of-focus gray smudges; the matrix lost the photo's fine crisp light/dark grain; white crystals are still crisp rectangles.
x3/x4 overshoot: granular light grains made the matrix mostly white (mid-dark only 3-4%).
Code: new fleck_softness (default keeps the old crisp edge), and a fix for a crash when under_lath_angles is empty but under_lath_size is a per-layer tuple.
Pink as one continuous range, not two colors (2026-10-08, commit 4c130e880)
Photo | r1: two pinks (previous commit) | u1: one pink with a wide tail (shows khaki crystals) | w1: one pink with a narrow continuous range (now the preset). Rows: full, 2x zoom, 4x zoom.
r1 read as two distinct pinks. One sample per crystal shows why: the symmetric hue pair made a separate redder group, and the 15% of crystals pulled to gray made a separate dull group. The photo has one peak with a gradual range. Now each crystal mixes a main pink with a slightly more orange, duller pink by a skewed random weight (most crystals near the main pink), and the gray group is off.
The previous pink matched only a loose median; it was one uniform hue and slightly too blue. The photo's crystals vary from rosier to more orange.
New option lath_color2: each pink crystal takes a random mix of two pinks of different hue. The preset uses a pair 0.04 apart in hue, plus a wider per-crystal brightness range.
Renders use Blender's AgX view transform, which compresses saturation and highlights, so the color was corrected from measured render response over three iterations rather than by a single linear ratio.
Still short: the brightest pink crystals reach value 0.80 vs 0.84 in the photo. Matrix, white crystals and flecks are unchanged (see round 1 below).
Red lath preset tuning, round 1 (2026-10-08)
Photo | current preset | v1 matrix lighter and warmer | v2 + warm light-gray white crystals | v3 + larger, fewer, less black flecks | v4 + granular warm light-gray grains in the matrix. Each variant adds one change to the previous one. Bottom row: 2x zoom.
Share of pixels by sRGB lightness band, with each band's median color:
The darkest band dropped from 12% to 6.5% (photo 4.4%), but the mid-dark band (0.2-0.35) is still half the photo's (6% vs 12%). The photo's matrix is coarse, soft gray-dark patches; ours is still fine, high-contrast black/white speckle.
Larger flecks became hard black blobs (v3, v4) instead of the photo's soft dark patches: the fleck edge ramp has a fixed width in the code, so it does not soften as flecks grow.
White crystals: warming them barely moved the brightest band, which is still neutral (.80,.78,.76) and smaller than the photo's warm (.87,.81,.76). They are still crisp rectangles; v4's granular light grains are hardly visible.
Pink crystals still match. The photo's pink crystals also have more ragged, blurrier outlines and touch each other more.
Generators: before vs after this session's changes (seeds 1-4)
Head-on camera, 1024 px, 32 samples, rendered on ionic. "Before" is commit e3d58a1 (before the refactor) for salt-and-pepper and porphyritic, and 47cd161de (first lath version, aligned crystals) for the lath generator. "After" is f811eb0ae (current).
granite_salt_pepper_rand and granite_porphyritic_rand are unchanged: every seed matches within 1/255 (max difference 1, no pixel above 2/255).
granite_salt_pepper_rand: before | after | difference x8 (all black = identical)granite_porphyritic_rand: before | after | difference x8
granite_lath_rand changed on purpose: crystals are now randomly oriented, and dropping the angle draws shifted the random stream, so the same seed gets a different palette (e.g. seed 3 went from cream to red).
granite_lath_rand: before (aligned) | after (random orientation) | difference x8
0. Update: crystals randomly oriented (no dominant direction)
In the prototype every crystal pointed within ±0.35 rad of three fixed layer angles (0.4-1.3 rad), and each layer's Voronoi frame was stretched along its angle, so all crystals leaned the same way. Now elongation is applied in each crystal's own frame and each crystal gets its own uniformly random rotation. This is the default, and the preset and generator use it. Crystal size and coverage are unchanged.
Photo | preset before (aligned, as r25_b) | preset after (random orientation). Bottom: 2x zoom.Generator seeds 1-4 with random orientation. Dropping the angle draws shifted the random stream, so these seeds have different palettes from section 2 (dark gray, white, red, white). Still open: when both crystal families are light (seeds 2 and 4), the rectangles blend together.
Sections 1-2 below show the earlier aligned version.
1. Red lath tile: photo vs prototype r25_b vs ported preset
The preset reproduces r25_b. Its color under the prototype's flat surface matches within 1/255, after two deliberate changes: white crystals take brightness from the same random channel as the pink ones, and the preset uses the honed layered surface (about 1.7/255 darker on average).
Still different from the photo (not tuned yet):
Pink crystal color matches within about 0.01 per channel.
The dark matrix is too dark and too cold: median brightness 0.14 vs 0.25 in the photo.
Twice the share of very dark pixels (14% vs 7%).
About 16% crisp neutral-white rectangles, where the photo has granular warm light-gray patches.
All crystals lean along one diagonal (angles span only 0.4-1.3 rad); the photo's are more varied.
Black flecks are small and crisp; the photo has larger soft dark patches.
Photo, then seeds 1-4 (gold 16.5 mm, gray 10.9 mm, cream 10.5 mm, gold 12.5 mm). Top: full; bottom: 2x zoom.
Pink and red together are 18% of the shared palette, so none of these four draws is pink. In seeds 2 and 3 the main and pale crystals have similar brightness, so the rectangles blur together. Every seed shares the single-direction alignment. The ranges are centered on the preset, not calibrated by sweeps.
Seed 1Seed 2Seed 3Seed 4
3. Refactor check: 16 materials before vs after
Each material: before | after | difference x8. Default demo camera, 512 px.
Realized nodes went from 287-288 to 136-253 per material. The largest per-pixel difference is 12/255, never more than 0.07% of pixels differ by more than 2/255, and every pixel off by more than 4/255 is isolated.
Reference photo
scripts/granite_dev/reference_red_lath_tile.jpg. The red tile is in the middle; the gray tile around it is open item 3.