feat(core+app): alpha atlas loading (Phase 3c.2)
Loads AC's terrain blending alpha masks into a second GL_TEXTURE_2D_ARRAY
alongside the existing terrain atlas. The alpha atlas is built but not
yet sampled by any shader — that wiring lands in Phase 3c.4.
SurfaceDecoder additions:
- Handles PFID_A8 (generic single-byte-alpha) by replicating each
alpha byte into all four RGBA channels
- Same branch handles PFID_CUSTOM_LSCAPE_ALPHA (0xF4), AC's landscape-
specific alpha format — the bit layout is identical, just a different
format ID to distinguish the asset class in the dats. I only found
this by adding a diagnostic in the first iteration (initial attempt
returned Magenta for every alpha map because I only wired PFID_A8)
- 3 new tests: 2x2 A8 round-trip, short-source fallback, and a
CUSTOM_LSCAPE_ALPHA test verifying it's routed through the same path
TerrainAtlas additions:
- New GlAlphaTexture property plus CornerAlphaLayers / SideAlphaLayers
/ RoadAlphaLayers index lists so the coming BuildSurface port can
cite atlas layers by source category
- BuildAlphaAtlas walks TexMerge.CornerTerrainMaps, SideTerrainMaps,
RoadMaps and uploads each decoded mask as a layer in insertion
order; categories carry their atlas-layer index in the respective
list
- Fallback handling (single-layer white) when TexMerge is missing or
every map fails to decode
- Alpha atlas uses ClampToEdge wrap so repeating tile sampling at
mask boundaries doesn't produce seams
- Dispose() now cleans up both textures
On Holtburg's region the log prints:
TerrainAtlas: 33 terrain layers at 512x512
AlphaAtlas: 8 layers at 512x512 (corners=4, sides=1, roads=3)
Tests: 61/61 passing. No visual change expected this commit (shader
still ignores Data0..3 and the alpha sampler).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8b940bd038
commit
a8459eecbb
3 changed files with 324 additions and 19 deletions
|
|
@ -38,6 +38,7 @@ public static class SurfaceDecoder
|
|||
PixelFormat.PFID_DXT1 => DecodeBc(rs, CompressionFormat.Bc1),
|
||||
PixelFormat.PFID_DXT3 => DecodeBc(rs, CompressionFormat.Bc2),
|
||||
PixelFormat.PFID_DXT5 => DecodeBc(rs, CompressionFormat.Bc3),
|
||||
PixelFormat.PFID_A8 or PixelFormat.PFID_CUSTOM_LSCAPE_ALPHA => DecodeA8(rs),
|
||||
PixelFormat.PFID_INDEX16 when palette is not null => DecodeIndex16(rs, palette, isClipMap),
|
||||
_ => DecodedTexture.Magenta,
|
||||
};
|
||||
|
|
@ -104,6 +105,34 @@ public static class SurfaceDecoder
|
|||
Height: 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode single-byte-per-pixel alpha (PFID_A8 / PFID_CUSTOM_LSCAPE_ALPHA)
|
||||
/// into RGBA8 by replicating each alpha byte into all four channels. AC's
|
||||
/// terrain blending alpha masks are stored as PFID_CUSTOM_LSCAPE_ALPHA and
|
||||
/// other generic 8-bit alpha surfaces use PFID_A8; the bit layout is
|
||||
/// identical so one decoder handles both. Replicating into all four
|
||||
/// channels lets the fragment shader pull "the blend amount" from either
|
||||
/// .a or .r without special-casing.
|
||||
/// </summary>
|
||||
private static DecodedTexture DecodeA8(RenderSurface rs)
|
||||
{
|
||||
int expected = rs.Width * rs.Height;
|
||||
if (rs.SourceData.Length < expected)
|
||||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[expected * 4];
|
||||
for (int i = 0; i < expected; i++)
|
||||
{
|
||||
byte a = rs.SourceData[i];
|
||||
int d = i * 4;
|
||||
rgba[d + 0] = a;
|
||||
rgba[d + 1] = a;
|
||||
rgba[d + 2] = a;
|
||||
rgba[d + 3] = a;
|
||||
}
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
private static DecodedTexture DecodeA8R8G8B8(RenderSurface rs)
|
||||
{
|
||||
int expected = rs.Width * rs.Height * 4;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue