Chapter 01
设计哲学与纹理体系架构
OWMap 的地图纹理不是地理数据的简单可视化——它是「深空科技 × 人文诗意」美学哲学的物质载体。
每一像素都承担着在用户与地球之间建立情感连接的使命。
1.1 纹理美学的三个层次
OWMap 的地图纹理体系由三个相互叠加的视觉层次构成,每个层次服务于不同的设计目标:
Layer 0 · 基础地球纹理
深空色调 · 大陆轮廓 · 经纬网格
Layer 1 · 文化圈着色层
六大文化圈 · 低饱和配色 · 半透明叠加
Layer 2 · 故事热力图层
内容密度 · 故事金渐变 · 动态更新
✦
核心设计原则:地球纹理必须「退到背景」——它是故事坐标点位的舞台,而非主角。
整体饱和度控制在 saturation ≤ 15%,亮度偏深,确保故事金色 #F5C842 标记点的视觉绝对优先级。
1.2 OWMap 纹理规格标准
| 纹理层 |
推荐尺寸 |
格式 |
更新频率 |
存储位置 |
| 地球基础纹理 | 4096 × 2048 | PNG / WebP | 静态 / 季节性更新 | CDN · 强缓存 |
| 大气层纹理 | 512 × 512 | PNG(透明) | 静态 | CDN · 强缓存 |
| 文化圈蒙版 | 2048 × 1024 | PNG(透明通道) | 版本迭代 | CDN · 版本号缓存 |
| 热力图层 | 1024 × 512 | 动态 Canvas | 实时 / 每小时 | 内存 / 本地 Canvas |
| 法线贴图(可选) | 2048 × 1024 | PNG(RG通道) | 静态 | CDN · 强缓存 |
| 高光贴图(海洋) | 2048 × 1024 | PNG(灰度) | 静态 | CDN · 强缓存 |
⚠
移动端降级策略:检测到 navigator.deviceMemory < 2 或 hardwareConcurrency < 4 时,
自动降级为 2048 × 1024 基础纹理,禁用法线贴图和动态热力层,目标 FPS ≥ 30。
Chapter 02
数据源完整清单
获取高质量地图纹理素材的关键在于选择合适的数据源。以下覆盖免费开源、
商业授权与 OWMap 自研三个维度,并标注了每个数据源对 OWMap 场景的适配度。
2.1 数据源对比速查表
| 数据源 |
类型 |
最高分辨率 |
许可证 |
OWMap 适配度 |
使用场景 |
| NASA Blue Marble | 卫星影像 | 500m/px | 公共域 | ★★★★★ | 基础地球纹理首选 |
| Natural Earth | 矢量 + 栅格 | 1:10M/1:50M | 公共域 | ★★★★★ | 大陆轮廓、国界线 |
| ETOPO1 / ETOPO2022 | 地形高程 | 1 弧分 | NOAA 公共域 | ★★★★☆ | 海洋深度着色、地形凹凸 |
| Mapbox Satellite | 瓦片影像 | z18(≈0.6m/px) | 商业 | ★★★★☆ | 城市级别 3D 地形细节 |
| MapLibre GL | 矢量瓦片 | z22 | BSD-2 | ★★★★★ | 2D 地图层、分层渲染 |
| OpenStreetMap | 矢量数据 | 节点级别 | ODbL | ★★★☆☆ | 城市内街道、POI(需注意版权归因) |
| SRTM / ASTER GDEM | 高程 DEM | 30m/px | NASA 公共域 | ★★★★☆ | 地形起伏可视化 |
| Sentinel-2 L2A | 多光谱卫星 | 10m/px | Copernicus 开放 | ★★★★☆ | 真彩色/假彩色专题纹理 |
| Stamen Watercolor | 艺术风格 | z18 | CC BY 3.0 | ★★★☆☆ | 特定艺术化展示模式 |
| OWMap 自研纹理库 | Canvas 生成 | 动态分辨率 | 内部版权 | ★★★★★ | 品牌一致性地球仪 / 离线降级 |
2.2 推荐数据源详解
NASA · 卫星影像 · 免费
NASA Blue Marble Next Generation
每月更新的全球真彩色卫星合成影像,去云处理,是制作高质量地球仪基础纹理的黄金标准。提供 500m 至 8km 多种分辨率版本。
4096×2048 可用
月度更新
公共域
Natural Earth · 矢量 · 免费
Natural Earth Data
高质量手工制图矢量数据,包含大陆轮廓、国界、湖泊、河流、城市等要素。OWMap 大陆着色层的主要矢量数据来源。
SVG 可用
GeoJSON 导出
公共域
NOAA · 高程 · 免费
ETOPO2022 全球地形模型
最新版全球海陆高程数据,1 弧分分辨率(约 1.8km/px 赤道)。用于生成地球仪海洋深度着色和地形凹凸贴图。
法线贴图生成
深度着色
NOAA 公共域
Copernicus · 多光谱 · 开放
Sentinel-2 卫星影像
ESA 哨兵2号卫星,10m 分辨率,5天重访周期。支持 RGB / NDVI / 假彩色等多种波段组合,适合制作季节变化纹理。通过 Copernicus Open Access Hub 免费获取。
10m 高分
多光谱
Copernicus 开放
2.3 NASA Blue Marble 获取流程
1
访问 NASA Visible Earth 数据库
前往 visibleearth.nasa.gov,搜索「Blue Marble Next Generation」,
选择目标月份(推荐 9 月:北半球绿色覆盖最佳,南半球雪盖适中)。
2
选择分辨率与投影
OWMap 地球仪使用 等经纬度投影(Equirectangular / Plate Carrée)。
选择 world.topo.bathy.200409.3x5400x2700.png(54MB)或
world.200409.3x4096x2048.png(24MB,推荐)。
3
色彩调整:对齐 OWMap 设计令牌
使用 GIMP / Photoshop / ImageMagick 进行以下处理:
① 整体降饱和 60%;② 曲线压暗至 RGB(10-40) 范围;
③ 蓝色通道轻微提升(+15)增加宇宙感;④ 大陆区域轻微加亮(+8)确保可分辨。
4
转换为 WebP 并上传 CDN
使用 cwebp -q 85 input.png -o earth-texture.webp 压缩。
4096×2048 PNG(约 24MB)→ WebP(约 3.2MB),节省 86% 带宽。
设置 Cache-Control: max-age=31536000(1年强缓存)。
5
在 Three.js 中加载
使用 THREE.TextureLoader 异步加载,并在加载完成前用程序生成的简化纹理占位,
确保 LCP < 3 秒的性能目标。
Chapter 03
Three.js 地球纹理完整制作方案
OWMap 地球仪的核心渲染引擎基于 Three.js WebGL。本章提供从纹理加载、着色器编写
到多层叠加的完整生产级代码,已在 OWMap BSL1 Globe v2 中验证。
3.1 多层纹理材质系统
OWMap 地球采用 PhongMaterial + 自定义 UV 着色 方案,兼顾渲染质量与移动端兼容性。
JavaScript · Three.js r128+import * as THREE from 'three';
// ── 纹理加载管理器(带进度回调)──
const manager = new THREE.LoadingManager();
const loader = new THREE.TextureLoader(manager);
// ── 异步加载所有纹理层 ──
async function loadEarthTextures() {
const [baseMap, normalMap, specMap, cloudMap] = await Promise.all([
loader.loadAsync('/textures/earth-base-4k.webp'),
loader.loadAsync('/textures/earth-normal-2k.webp'),
loader.loadAsync('/textures/earth-specular-2k.webp'),
loader.loadAsync('/textures/earth-clouds-2k.webp'),
]);
// ── 纹理参数 ──
[baseMap, normalMap, specMap, cloudMap].forEach(tex => {
tex.anisotropy = 16; // 最大各向异性过滤,提升斜视质量
tex.minFilter = THREE.LinearMipmapLinearFilter;
tex.magFilter = THREE.LinearFilter;
tex.colorSpace = THREE.SRGBColorSpace; // r152+
});
return { baseMap, normalMap, specMap, cloudMap };
}
// ── 地球体材质 ──
function createEarthMaterial(textures) {
return new THREE.MeshPhongMaterial({
map: textures.baseMap,
normalMap: textures.normalMap,
normalScale: new THREE.Vector2(0.6, 0.6),
specularMap: textures.specMap,
specular: new THREE.Color(0x1a3050),
shininess: 12,
});
}
// ── 云层(独立 Mesh,轻微偏移半径)──
function createCloudLayer(cloudTexture) {
const geo = new THREE.SphereGeometry(1.008, 48, 48);
const mat = new THREE.MeshPhongMaterial({
map: cloudTexture,
transparent: true,
opacity: 0.18,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
const clouds = new THREE.Mesh(geo, mat);
clouds.rotation.y = 0.1; // 初始相位偏移
return clouds;
}
3.2 程序化地球纹理生成(Canvas API)
在纹理文件未加载完成、或 CDN 不可用时,OWMap 使用程序化 Canvas 生成纹理作为占位与降级方案。
这也是 globe.js 的内建实现。
JavaScript · Canvas 2D API/**
* OWMap 程序化地球纹理生成器
* 输出:CanvasTexture(可直接传入 Three.js MeshPhongMaterial.map)
* 目标效果:「深空科技 × 人文诗意」——克制的大陆轮廓,艺术化非写实
*/
function makeProceduralEarthTexture(w = 4096, h = 2048) {
const cv = document.createElement('canvas');
cv.width = w; cv.height = h;
const ctx = cv.getContext('2d');
// ── Step 1:深空海洋渐变 ──
const ocean = ctx.createLinearGradient(0, 0, 0, h);
ocean.addColorStop(0, '#010814'); // 极地深蓝
ocean.addColorStop(0.5, '#03101e'); // 赤道深海
ocean.addColorStop(1, '#010814');
ctx.fillStyle = ocean;
ctx.fillRect(0, 0, w, h);
// ── Step 2:大陆层(低对比度填充 + 边界线描边)──
const CONTINENTS = getContinentPolygons(); // 标准化百分比坐标
ctx.fillStyle = '#091828'; // 石墨暖调
ctx.strokeStyle = 'rgba(180,210,240,0.18)';
ctx.lineWidth = 1.5;
for (const pts of CONTINENTS) {
ctx.beginPath();
pts.forEach(([px, py], i) => {
const x = (px / 100) * w, y = (py / 100) * h;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill(); ctx.stroke();
}
// ── Step 3:经纬网格(极低对比度)──
ctx.strokeStyle = 'rgba(40,80,140,0.07)';
ctx.lineWidth = 0.8;
for (let lon = 0; lon <= 360; lon += 15) {
const x = (lon / 360) * w;
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
}
for (let lat = -90; lat <= 90; lat += 15) {
const y = ((lat + 90) / 180) * h;
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
}
// ── Step 4:赤道 + 本初子午线强调线 ──
ctx.strokeStyle = 'rgba(100,160,220,0.12)';
ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(0, h/2); ctx.lineTo(w, h/2); ctx.stroke();
// ── Step 5:极点冰盖(柔和白色径向渐变)──
[[w/2, 0], [w/2, h]].forEach(([cx, cy]) => {
const r = h * 0.1;
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
g.addColorStop(0, 'rgba(200,220,240,0.35)');
g.addColorStop(1, 'rgba(200,220,240,0)');
ctx.fillStyle = g;
ctx.fillRect(0, 0, w, h);
});
return new THREE.CanvasTexture(cv);
}
⬡
进阶技巧:离屏 Canvas + Web Worker
在 Worker 中使用 OffscreenCanvas 生成纹理,避免阻塞主线程。
生成完成后通过 transferControlToOffscreen() 将 Bitmap 传回主线程,
在 deviceMemory ≥ 4GB 设备上可将纹理尺寸提升至 8192×4096 而不卡顿。
Chapter 04
MapLibre GL 风格系统与矢量纹理
OWMap 的 2D 地图层(GeoCore.RenderKit)基于 MapLibre GL JS,
通过 Sprite + Symbol + 自定义 Style JSON 实现与品牌视觉完全一致的地图渲染。
4.1 OWMap 基础风格 JSON 模板
JSON · MapLibre GL Style v8{
"version": 8,
"name": "OWMap Deep Space",
"metadata": { "owmap:version": "1.0.0" },
"glyphs": "https://fonts.owmap.io/glyphs/{fontstack}/{range}.pbf",
"sprite": "https://cdn.owmap.io/sprites/owmap-v1",
"sources": {
"openmaptiles": {
"type": "vector",
"url": "https://tiles.owmap.io/v1/tiles.json"
}
},
"layers": [
{
"id": "background", "type": "background",
"paint": { "background-color": "#050D18" }
},
{
"id": "water", "type": "fill",
"source": "openmaptiles", "source-layer": "water",
"paint": {
"fill-color": "#03101e",
"fill-opacity": 0.9
}
},
{
"id": "landcover", "type": "fill",
"source": "openmaptiles", "source-layer": "landcover",
"paint": {
"fill-color": ["match", ["get", "class"],
"forest", "#071420",
"grass", "#081828",
"wetland","#060f1c",
"#091828"
],
"fill-opacity": 0.85
}
},
{
"id": "country-boundary", "type": "line",
"source": "openmaptiles", "source-layer": "boundary",
"filter": ["==", ["get", "admin_level"], 2],
"paint": {
"line-color": "rgba(180,210,240,0.12)",
"line-width": ["interpolate", ["linear"], ["zoom"],
2, 0.6, 8, 1.2],
"line-dasharray": [4, 3]
}
},
{
"id": "story-markers", "type": "symbol",
"source": "owmap-stories",
"layout": {
"icon-image": "owmap-marker-{cultural_circle}",
"icon-size": ["interpolate", ["linear"], ["get", "heat_score"],
0, 0.6, 100, 1.2],
"icon-allow-overlap": false
}
}
]
}
4.2 自定义 Sprite 图集制作
OWMap 的地图标记图标(Marker Icons)以 Sprite 图集形式管理,
每个文化圈对应一个颜色变体。制作流程如下:
1
设计图标 SVG 源文件
图标尺寸:32×32px(@1x),导出 SVG + 64×64 PNG(@2x)。
所有图标使用相同外形,通过填充色区分文化圈。
基础图标:脉冲圆点 + 外圈光环,currentColor 变量化以便批量生成彩色变体。
2
生成各文化圈色彩变体
使用 Node.js 脚本批量替换 SVG currentColor 为对应文化圈色值,
输出 6 组 × 4 种状态(normal / hover / selected / cluster)= 24 个 PNG 文件。
3
使用 spritezero-rs 合并
spritezero owmap-sprite icons/ 生成 owmap-v1.png 和
owmap-v1.json(@2x 输出:owmap-v1@2x.png 和对应 JSON)。
上传至 CDN,路径需与 Style JSON 中 "sprite" 字段一致。
Chapter 05
文化圈配色方案与纹理着色
OWMap 将全球内容组织为六大文化圈,每个文化圈对应一套独特的配色系统,
用于地图纹理着色、标记点位颜色和故事卡片主题色。
5.1 六大文化圈色彩令牌
| 文化圈 |
主色 |
辅色 |
饱和版(选中态) |
设计灵感 |
| 🀄 东亚文化圈 |
#1B3A5C |
#2A5580 |
#4A9FD0 |
青花瓷釉色 · 宋代山水墨色 |
| ☪ 中东 / 伊斯兰文化圈 |
#3A1E0A |
#5A3A1A |
#C87030 |
沙漠琥珀 · 老城香料色 |
| ♛ 欧洲文化圈 |
#1A2A1A |
#2A402A |
#509040 |
森林橡木 · 中世纪羊皮纸 |
| 🌎 美洲文化圈 |
#1E1E3A |
#30305A |
#7070C0 |
安第斯夜空 · 玛雅岩石蓝 |
| 🌍 非洲文化圈 |
#2A1E0A |
#402E10 |
#B07030 |
红土大地 · 夕阳金棕 |
| 🌏 南亚 / 大洋洲圈 |
#1A1E2A |
#28303E |
#5080A0 |
印度洋深蓝 · 珊瑚礁青 |
5.2 文化圈纹理蒙版生成
文化圈着色通过在基础地球纹理上叠加带透明通道的文化圈蒙版实现。
蒙版由 Natural Earth 矢量数据转换而来,透明度 30-40%,确保基础地形仍可辨。
JavaScript · Canvas 2D 蒙版叠加// 在基础地球纹理上叠加文化圈着色蒙版
function applyCulturalOverlay(ctx, w, h) {
const CULTURAL_ZONES = [
{
name: 'east-asia',
color: 'rgba(74, 159, 208, 0.22)',
// 简化轮廓(生产环境替换为 GeoJSON 矢量)
bounds: [{ lon: [70, 145], lat: [-10, 55] }]
},
{
name: 'middle-east',
color: 'rgba(200, 112, 48, 0.20)',
bounds: [{ lon: [24, 70], lat: [12, 42] }]
},
// ...其余四个文化圈
];
for (const zone of CULTURAL_ZONES) {
ctx.fillStyle = zone.color;
for (const b of zone.bounds) {
const x1 = ((b.lon[0] + 180) / 360) * w;
const x2 = ((b.lon[1] + 180) / 360) * w;
const y1 = ((90 - b.lat[1]) / 180) * h;
const y2 = ((90 - b.lat[0]) / 180) * h;
ctx.fillRect(x1, y1, x2 - x1, y2 - y1);
}
}
}
Chapter 06
性能优化最佳实践
地图纹理是影响 LCP(最大内容绘制)的主要因素。本章提供经过 OWMap 生产环境验证的
性能优化策略,目标:LCP < 3.0s,60fps 持续渲染,移动端内存 < 150MB。
6.1 纹理加载策略
| 策略 | 适用场景 | LCP 收益 | 实现复杂度 |
| 渐进式加载(低 → 高分辨率) | 首屏地球仪 | -1.2s | 中 |
| WebP 替代 PNG | 所有纹理 | -0.5s | 低 |
| Service Worker 预缓存 | 回访用户 | -2.0s | 中 |
| OffscreenCanvas Worker 生成 | 程序化纹理 | +0s(不阻塞) | 高 |
| GPU Mipmap 预生成 | Three.js 纹理 | 运行时 FPS +15 | 低 |
| LOD 分层渲染 | 缩放级别切换 | 内存 -40% | 高 |
6.2 渐进式纹理加载实现
JavaScript · 渐进式加载/**
* 渐进式纹理加载:先显示低分辨率占位,异步替换为高分辨率
* 1. 启动时:使用内联 Canvas 生成 256x128 简化纹理(<100ms)
* 2. 背景:异步加载 4096x2048 WebP 纹理(~3MB,CDN 约 1.5s)
* 3. 加载完成:平滑 crossfade 切换(500ms 过渡)
*/
class ProgressiveEarthTexture {
constructor(mesh) {
this.mesh = mesh;
this.placeholderTex = null;
this.hiresTex = null;
}
async init() {
// Phase 1: 立即显示程序化占位纹理
this.placeholderTex = makeProceduralEarthTexture(512, 256);
this.mesh.material.map = this.placeholderTex;
this.mesh.material.needsUpdate = true;
// Phase 2: 后台加载高清 WebP
const loader = new THREE.TextureLoader();
try {
this.hiresTex = await loader.loadAsync('/textures/earth-4k.webp');
this.hiresTex.anisotropy = 16;
await this.crossfade();
} catch {
// CDN 不可用时保留占位纹理,记录错误
console.warn('[OWMap] Hi-res texture unavailable, keeping placeholder');
}
}
crossfade(duration = 500) {
return new Promise(resolve => {
let t = 0;
const step = () => {
t += 16;
const p = Math.min(t / duration, 1);
if (p >= 0.5 && this.mesh.material.map !== this.hiresTex) {
this.mesh.material.map = this.hiresTex;
this.mesh.material.needsUpdate = true;
}
p < 1 ? requestAnimationFrame(step) : resolve();
};
requestAnimationFrame(step);
});
}
}
Chapter 07
GeoCore.RenderKit 集成规范
GeoCore.RenderKit 是 OWMap 平台级地图渲染公共组件,所有内容层(BSL1、CinemaMap、HeritagePath 等)
均应通过 RenderKit API 调用地图纹理能力,而非各自实现。
7.1 RenderKit 纹理配置接口
TypeScript · GeoCore.RenderKit API// GeoCore.RenderKit — 纹理层配置接口
interface GlobeTextureConfig {
// 基础纹理
baseTexture: {
url: string; // CDN URL 或 'procedural'
resolution: '2k' | '4k' | '8k';
fallback: 'procedural'; // 加载失败时的降级策略
};
// 文化圈蒙版
culturalOverlay?: {
enabled: boolean;
opacity: number; // 0.0 - 0.5,推荐 0.22
zones: CulturalZone[]; // 来自 GeoCore.Catalog
};
// 热力图层
heatmapLayer?: {
enabled: boolean;
dataSource: 'realtime' | 'static' | 'synthetic';
updateInterval: number; // ms,0 = 不自动更新
colorScale: [string, string]; // [cold, hot] 颜色对
};
// 大气层效果
atmosphere?: {
enabled: boolean;
color: string; // 默认 '#5B8FD6'
intensity: number; // 0.0 - 1.0,默认 0.4
};
// 性能约束
performance: {
targetFPS: number; // 目标帧率,默认 60
maxTextureSize: number; // px,默认 4096
enableMipmaps: boolean; // 默认 true
deviceProfile: 'auto' | 'low' | 'mid' | 'high';
};
}
// 使用示例(BSL1 配置)
const bsl1Config: GlobeTextureConfig = {
baseTexture: { url: 'https://cdn.owmap.io/textures/earth-4k.webp', resolution: '4k', fallback: 'procedural' },
culturalOverlay: { enabled: true, opacity: 0.22, zones: GeoCore.getCulturalZones() },
heatmapLayer: { enabled: true, dataSource: 'realtime', updateInterval: 3600000, colorScale: ['#1A3050', '#F5C842'] },
atmosphere: { enabled: true, color: '#5B8FD6', intensity: 0.4 },
performance: { targetFPS: 60, maxTextureSize: 4096, enableMipmaps: true, deviceProfile: 'auto' },
};
Chapter 08
素材资产管理规范
规范化的资产管理确保纹理素材在多人协作、跨版本迭代和多平台部署中保持一致性。
以下规范适用于 OWMap 所有团队成员。
8.1 目录结构规范
目录结构/cdn/owmap/textures/
├── earth/
│ ├── base/
│ │ ├── earth-2k.webp # 2048×1024 · 低分辨率
│ │ ├── earth-4k.webp # 4096×2048 · 标准分辨率 ← 主要使用
│ │ ├── earth-8k.webp # 8192×4096 · 高端设备
│ │ └── earth-procedural.js # Canvas 程序化生成(降级用)
│ ├── normal/
│ │ ├── normal-2k.webp # 法线贴图
│ │ └── normal-4k.webp
│ ├── specular/
│ │ └── specular-2k.webp # 高光贴图(海洋反光区域)
│ ├── cultural/
│ │ ├── overlay-east-asia.webp
│ │ ├── overlay-middle-east.webp
│ │ ├── overlay-europe.webp
│ │ ├── overlay-americas.webp
│ │ ├── overlay-africa.webp
│ │ └── overlay-south-asia.webp
│ └── seasonal/
│ ├── earth-jan.webp # 月度纹理(雪盖变化)
│ └── earth-jul.webp
├── sprites/
│ ├── owmap-v1.png # Sprite 图集 @1x
│ ├── owmap-v1@2x.png # Sprite 图集 @2x
│ └── owmap-v1.json # Sprite 索引 JSON
└── styles/
├── owmap-deep-space.json # MapLibre 主题风格
└── owmap-satellite.json # 卫星影像风格
8.2 发布前检查清单
☑
纹理发布检查清单(每次更新纹理素材必须逐项确认)
- □ 所有格式已转换为 WebP(质量 85)并压缩 ≥ 80%
- □ 等经纬度投影确认(宽高比 2:1 或 4:2 等)
- □ 色彩空间为 sRGB(Three.js
texture.colorSpace = SRGBColorSpace)
- □ 纹理尺寸为 2 的幂次(1024/2048/4096/8192)
- □ CDN 缓存策略已设置(
Cache-Control: max-age=31536000, immutable)
- □ 降级链路测试通过(WebGL 不可用 → Canvas 2D → 静态截图)
- □ 移动端内存压力测试(
deviceMemory = 2 模式下 FPS ≥ 30)
- □ 大陆轮廓在深空黑背景上可辨识(亮度差 > 5 灰度值)
- □ 故事金色 #F5C842 标记点在所有文化圈着色区域上对比度 ≥ 4.5:1
- □ 版本号已更新(避免浏览器缓存旧纹理)