perf(renderer): 实现 Raw 8-bit GPU 索引纹理 (R8) + Shader 硬件调色板查表架构 #34
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景与目标
当前预打包实体图集(人物与怪物)采用 8-bit Indexed PNG(Color Type 3),虽然相比运行时 MPQ HTTP Range 解码已将加载时间缩短至 <200ms,但仍存在以下可进一步优化的瓶颈:
Blob -> ImageDecoder -> RGBA8 像素展开过程,上传至 GPU 后占用 4 字节/像素(1024×1024 图集占 4MB 显存)。pal.pl2调色板不同。使用 PNG 时需要在act1..5/entities/重复存储 5 套图集。RandTransforms.dat/palshift.dat)、冰冻变蓝(Cold)、中毒变绿(Poison)均基于 8-bit 调色板索引重映射,固定 RGBA 纹理无法动态换色。核心架构设计
1. 全局单套 Raw 8-bit 索引图集 (
.r8+.json)char-so及全部 66 种怪物图集打包为 全局唯一 的samples/d2-packs/entities/<slug>.r8(纯Uint8Array1 byte/pixel 原始索引流)与<slug>.json。0代表透明(mask === 0),非透明但原索引为0(纯黑)的像素自动重映射至最接近的纯黑非零索引(如索引255或由调色板决定的黑点),确保index == 0严格对应透明。.r8文件体积仅 ~30KB–120KB(传输层天然支持 Gzip/Brotli 压缩),彻底消除跨幕重复存储(总实体包体减少 80%)。2. WebGL2
R8单通道纹理与硬件调色板纹理 (SpriteRenderer)gl.R8):在SpriteRenderer中支持addIndexedAtlas(indices: Uint8Array, width: number, height: number),使用gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, width, height, 0, gl.RED, gl.UNSIGNED_BYTE, indices)直接上传 1 byte/pixel 纹理(NEAREST采样,显存占用与 PCIe 带宽降低 75%,CPU 解码耗时 0ms)。uPaletteTex):将当前幕的 256 色调色板(及后续扩展的多行染色表,如冰冻蓝、中毒绿、精英染色行)上传为256 × N的RGBA8纹理。3. Fragment Shader 硬件级查表与染色支持
uUsePalette(或纹理模式)区分普通RGBA瓦片图集与R8索引实体图集:R8索引图集时,读取红色通道float idx = texture(uAtlas, vUv).r * 255.0;;idx < 0.5(即索引 0),直接discard;texelFetch(uPalette, ivec2(int(idx + 0.5), vPaletteRow), 0)从硬件调色板纹理取出精确 RGBA 颜色输出。renderer.draw(frame, x, y, { atlas, paletteRow })中传入paletteRow(如0= 默认原色,1= 冰冻蓝 tint,2= 中毒绿 tint,3= 精英金怪/蓝怪 tint),实现零显存开销的原版状态染色!✅ 已完成 Raw 8-bit GPU 索引纹理 (
gl.R8) + Shader 硬件调色板查表架构实现 (Commitd4fb176)1. WebGL2 渲染器升级 (
src/render/renderer.ts)gl.R8):新增addIndexedAtlas(indices: Uint8Array, width: number, height: number): AtlasHandle,直接将Uint8Array字节流上传至 GPU VRAM (gl.R8/gl.RED/gl.UNSIGNED_BYTE):createImageBitmap与 PNG zlib 解压;sampler2D兼容:与现有 RGBA 图集共享同一纹理单元数组 (u_textures[0..14]),零额外 Draw Call 开销。u_palette, 256×5 RGBA):新增setPalette(rgb: Uint8Array),在专用纹理单元 (this.paletteUnit) 构建 256×5 硬件调色板查找表:Row 0:当前幕(Act 1..5)标准调色板;Row 1:冰冻/寒冷减速蓝(Cold Chilled Blue);Row 2:中毒绿(Poison Green);Row 3:暗金/精英金(Unique Boss Gold);Row 4:头目/冠军蓝(Champion Elite Blue)。FLOATS_PER_VERTEX = 10(新增a_palette属性);v_palette >= 0时,Fragment Shader 执行int idx = int(texel.r * 255.0 + 0.5)并通过texelFetch(u_palette, ivec2(idx, row), 0)完成硬件级实时着色。2. 全局跨幕无关
.r8实体图集烘焙 (scripts/pack-entity-assets.ts&src/render/atlas.ts)mask=0)、Index 172 恒为纯黑 (rgb(4,4,4)) 的特性,实现buildIndexedAtlas/packSheetToR8:mask=1 && index=0) 统一重映射至172;samples/d2-packs/entities/char-so.{r8,json}及全部 66 种跨幕怪物 的全局monster-<token>-<weapon>.{r8,json};.r8索引图集,切换幕数时仅需调用renderer.setPalette(runtime.palette.rgb)即可切换幕色调。3. 场景加载与自动化测试验证 (
src/scene/act-scene.ts&tests/packed-entity-art.test.ts)runScene加载幕调色板时自动调用renderer.setPalette(runtime.palette.rgb);loadCharacterArt与loadMonsterArtMap优先拉取全局entities/*.r8+.json并通过addIndexedAtlas直传 GPU,同时保留按幕 PNG 与 MPQ 实时提取作为后备;.r8图集与尺寸完整性,537 项测试全部通过。