feat(tools): proper mesh textures — per-section albedo from material textures (sf-map-renderer)#261
Merged
Merged
Conversation
…aterial textures (sf-map-renderer) Replaces the hand-picked per-family placeholder colours with the real average albedo of each mesh material. - `MaterialColourSampler` finds a material's diffuse texture (`CMaterialParams2`, AllLayers incl. referenced textures), decodes a small mip with the managed AssetRipper BC/DXT decoder (BC1/2/3/7 + raw BGRA/RGBA), and averages the opaque pixels (alpha-cut leaf/coral cards contribute only their lit pixels). When no texture decodes (e.g. DesertRock's virtual-textured blend material) it falls back to a base-colour vector param, ignoring near-white/black tint defaults; only then the family palette. Cached by material path. - `MeshGeometryCache` samples per LOD0 section, carrying a per-triangle colour (LOD0 excludes the black LOD billboard/imposter materials, and gives trunk vs foliage their own real colours). The rasteriser writes the winning triangle's colour. Uses AssetRipper.TextureDecoder directly (MIT) rather than CUE4Parse-Conversion, which would force the paid-line ImageSharp 3.x — the renderer stays on Apache-2.0 ImageSharp 2.1.x. Adds a `probe matcolour` smoke test. Result: cliffs and desert rock read as sand/tan and blend into the dunes, rock greys are real, forests render in true leaf-greens — the map reads like a natural terrain view. Refs #246 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
StuartMeeks
force-pushed
the
feature/246-mesh-textures
branch
from
July 12, 2026 17:59
e0ee80d to
915aff2
Compare
…trast (sf-map-renderer) Three review tweaks on top of the texture pass: - All coral (trees + shells) renders one fixed purple-ish hue (152,100,170) rather than its sampled albedo — in-game the alien coral glows magenta/purple from its emissive, which the base-texture albedo (green/brown) misses. The rasteriser skips the sampled colour for coral; the per-species coral table is dropped in favour of the single default. - Ocean + wet-sand surface raised a touch, -1755 → -1730cm, so the shoreline east of the Dune Desert sits a little higher. - Inland (non-ocean) water depth ramp widened for more contrast: deep end darker (8,28,72) and shallow end slightly brighter (70,136,190). The ocean band keeps its own ramp. Refs #246 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(sf-map-renderer) The terrain was coloured by a hand-picked colour per weightmap layer, so the desert read green-yellow rather than its true warm sand. Now each layer's colour is sampled from the landscape material itself. - `TerrainLayerColours` maps each weightmap layer (Sand, SandRock, Gravel, SandPebbles, WetSand, CoralRock, Forest, Grass, …) to its diffuse texture in the shared `ULandscapeMaterialInstanceConstant` and multiplies the average albedo by the layer's base-colour tint in linear space — e.g. Sand = TX_Sand_BC × "Sand BaseColor", SandRock = TX_SandRock_Alb × "Sand Rock BaseColor" (its reddish sandstone). Falls back to `TerrainPalette` for any layer whose texture doesn't decode. - `SceneCollector` captures the landscape material instance on each tile; `HeightmapDecoder` builds the colour map once from it and uses it in the weight-blend. `MaterialColourSampler.AverageTexture` is exposed for reuse. - DesertRock meshes track the desert sand (light sandstone) rather than their pale virtual-textured fallback, so rock and sand read alike as in-game. - New `probe terrainmat` dumps the landscape material's textures/tints. Note: the single Sand layer paints dune desert, rocky desert and beaches alike, so the in-game regional variation (orange dune vs yellow rocky) — driven by the material's macro world-space variation, not the weightmap — is not reproduced. Refs #246 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rrain colour (sf-map-renderer) The single Sand weightmap layer paints dune desert, rocky desert and beaches alike, so a flat per-layer colour couldn't split the in-game orange dune from the yellow rocky desert. The landscape material carries that variation in a world-aligned "PigmentMap" (its LandscapeCoords switch is set): a 1024² colour map, mostly white/neutral with large tinted regions — a salmon Dune Desert, a mauve Red Jungle, a blue Blue Crater. - `MacroPigment` decodes the PigmentMap once from the shared landscape material and, per cell, multiplies the weight-blended terrain colour toward the pigment at the cell's landscape UV (column/width, row/height — the frame is the landscape section extent), scaled by a strength. White pigment leaves a cell unchanged, so only the tinted regions shift. - `--pigment <0..1>` (default 0.6, 0 disables) via RenderOptions.PigmentStrength. - `MaterialColourSampler.DecodeRgb` decodes a texture to an RGB grid (reused by the new `probe pigment`, which dumps the map + its scalar/switch params). Result: the Dune Desert now reads distinctly orange while the Rocky Desert stays pale yellow-tan, and the Red Jungle/Red Bamboo Fields gain their rust cast — regional variation matching the game, precisely aligned to the biomes. Refs #246 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rer) The world-aligned pigment tinted only the landscape terrain, so placed rock meshes stayed a flat neutral colour — desert rock read grey against the orange sand around it. The pigment is now built once in the pipeline and shared: the rock rasteriser multiplies each rock cell's colour by the pigment at its landscape UV, exactly as the terrain does. Rock (not trees or coral) now varies by region — orange in the dune desert to sit with the sand, rust in the Red Jungle — instead of one uniform tone. Refs #246 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…renderer) Every rock of a family shared one sampled material colour, so cliffs/boulders read as a single flat tone within a region. `RockJitter` derives a stable per-instance multiplier — a brightness shift plus a small warm/cool tilt, hashed from the instance's world position (reproducible across renders) — applied to each rock before the macro pigment. `--rock-jitter <0..1>` (default 0.18, 0 disables) via RenderOptions.RockJitter. Same-family rock now varies naturally between lighter/darker, warmer/cooler shades instead of one uniform colour. Refs #246 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #246. The "textures" finale.
What
Replaces the hand-picked per-family placeholder colours with the real average albedo of each mesh material, sampled from its diffuse texture.
MaterialColourSampler— finds a material's diffuse texture (CMaterialParams2,AllLayersincl. referenced textures), decodes a small mip with the managed AssetRipper BC/DXT decoder (BC1/2/3/7 + raw BGRA/RGBA), and averages the opaque pixels (alpha-cut leaf/coral cards contribute only their lit pixels). When no texture decodes (e.g. DesertRock's virtual-textured blend material) it falls back to a base-colour vector param (ignoring near-white/black tint defaults); only then the family palette. Cached by material path.MeshGeometryCache— samples per LOD0 section, carrying a per-triangle colour. LOD0 excludes the black LOD billboard/imposter materials and gives trunk vs foliage their own real colours.Dependency note
Uses AssetRipper.TextureDecoder directly (MIT) rather than CUE4Parse-Conversion — that would drag in the paid-line ImageSharp 3.x; the renderer stays on Apache-2.0 ImageSharp 2.1.x. No SkiaSharp, no new licensing exposure.
Result
Cliffs and desert rock read as sand/tan and blend into the dunes, rock greys are real, forests render in true leaf-greens — the map reads like a natural terrain view. (Real AmberTree foliage is green, which the placeholder had guessed as amber — sampling corrects it.)
Testing
sf-map-rendererxUnit: 54 pass (host build).probe matcoloursmoke test across cliffs (tan), desert rock (sandy via vector fallback), boulders/rubble (grey), blue palm (blue-green), coral, and the trees; channel order verified (not R/B-swapped).render --downsample 2 --layerson the host, overlay eyeballed — no billboard/black artifacts, no regressions.Interactive artifact: https://claude.ai/code/artifact/f71233b1-c70a-49f4-b1bd-7448244ee8c7
Not merged — for your review. Possible future polish: sampling the multi-texture blend materials (rock+sand+wetsand) properly rather than one vector colour; overall brightness/gamma if desired.