惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
小众软件
小众软件
MongoDB | Blog
MongoDB | Blog
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Stack Overflow Blog
Stack Overflow Blog
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
量子位
A
About on SuperTechFans
G
Google Developers Blog
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Vercel News
Vercel News
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
U
Unit 42
Apple Machine Learning Research
Apple Machine Learning Research

博客园 - zhang-yd

今日开源[第57期]beautiful-water源码解读 今日开源[第56期]human-atlas源码解读 今日开源[第55期]model-x-studio源码解读 今日开源[第54期]Spark(@sparkjsdev/spark)源码解读 今日开源[第53期]PlayCanvas Engine(playcanvas/engine)源码解读 今日开源[第51期]Pi Agent Harness(pi)源码解读 今日开源[第50期]World Monitor(worldmonitor)游戏项目解读 今日开源[第49期]竹知了(zhuzhiliao)小玩具项目解读 今日开源[第48期]Operation Ironhold 游戏项目解读 今日开源[第47期]Academic Research Skills for Claude Code(ARS)项目skill解读 今日开源[第46期]AI-Research-SKILLs项目skill解读 今日开源[第45期]nature-research-skills项目skill解读 今日开源[第44期]findskills项目skill解读 今日开源[第43期]last30days-skill 今日开源[第42期]Cangjie Skill 今日开源[第41期]MoneyPrinterTurbo 今日开源[第39期]img2threejs 今日开源[第39期]Kronos 今日开源[第38期]Open Code Review (OCR) 今日开源[第37期]Openship 今日开源[第36期]croc 今日开源[第35期] Code-Review-Graph 今日开源[第34期] 《深入理解 AI Agent:设计原理与工程实践》 今日开源[第33期] Home Assistant Core 今日开源[第32期] Vibe-Trading 今日开源[第31期]RuView 今日开源[第30期]Chrome DevTools MCP 今日开源[第29期]RomM (ROM Manager) 今日开源[第28期]Page Agent 今日开源[第27期]video-use
今日开源[第52期]SPEEDBALL GI WebGPU Showcase(speedball-g...
zhang-yd · 2026-08-19 · via 博客园 - zhang-yd

SPEEDBALL GI WebGPU Showcase(speedball-gi)项目源码解读

解读对象:https://github.com/norio/speedball-gi
解读结构:① 名称/作者/作用/背景 ② 安装与使用 ③ 工作流程与执行步骤 ④ 文件逐个解读 ⑤ 优势/不足/创新点
系列序号:本文为仓库源码解读系列第 16 篇。


1. 项目名称、作者及其介绍、作用与背景

1.1 名称与定位

内容
仓库名 speedball-gi(npm 包名 speedball-gi-sample,版本 0.1.0
标题 SPEEDBALL GI WebGPU Showcase
作者 norio(GitHub 用户 norio
许可证 MITLICENSE 文件)
类型 交互式 Three.js WebGPU 演示应用(Vite 构建)
语言 TypeScript/JavaScript(ESM,"type": "module"

1.2 作用

这是一个实时动态漫反射全局光照(DDGI,Dynamic Diffuse Global Illumination) 的 Three.js WebGPU 展示项目。它基于上游 speedball-gi(作者 cl0nazepamm,仓库 cl0nazepamm/speedball)这一 WebGPU DDGI + 局部反射库,搭建了三个互补的演示场景,用来直观呈现"世界空间探针体积(probe volume)如何对世界空间内的几何与光源做实时、可交互的间接光照":

  • Sponza:经典建筑场景(上游 Sponza 演示的 Vite 化改造),含程序化天空、方向太阳光、自动拟合的 DDGI、局部反射、探针调试、动画金属接收体。
  • Afterimage Court(余像庭院):轻量程序化柱廊,红墙绿墙 + 中央天窗步道,专为"方便对比间接颜色传递"而设计。
  • Ball Pool(球池):物理模拟 + 实例化球体,指针控制光源并推开附近小球,几何持续变化,用来观察动态几何下的 GI 实时更新;池宽与球数随视口自适应。

1.3 背景

  • 技术背景:传统实时 GI 要么依赖昂贵的路径追踪,要么依赖屏幕空间(SSGI/SSAO)而缺乏世界空间一致性。DDGI(由 NVIDIA 在 2019 年提出)用稀疏规则探针网格存储辐照度/方向性,可在动态场景下实时重算。speedball-gi 进一步用 BVH 追踪(three-mesh-bvh) 做世界空间探针光线追踪,使其无需预烘焙。
  • 项目背景:上游 cl0nazepamm/speedball 提供了 DDGI 实现与 Sponza 演示;norio 在此基础上用 Vite 重写入口,并新增两个聚焦测试场景 + 一套共享的 GI/后处理辅助模块 + 资源优化管线,作为"官方 Sponza 演示(WebGPU DDGI + Sky)适配 Vite"的样板。
  • 致谢/来源(README Credits):
    • speedball-gi 提供 WebGPU DDGI 与局部反射实现;
    • Sponza 场景改编自上游 SPEEDBALL GI 演示;
    • Ball Pool 概念基于 three.js 官方 WebGPU SSGI Ball Pool 示例,物理用 @perplexdotgg/bounce,世界空间间接光照用 SPEEDBALL GI。

2. 安装与使用教程、依赖的软件/硬件条件

2.1 软件依赖

依赖 版本/要求 说明
Node.js ^20.19.0>=22.12.0 package.jsonengines.node
npm >=11.10.0 engines.npm
浏览器 支持 WebGPU 的浏览器 Chrome/Edge 121+、Firefox Nightly
安全上下文 开发用 localhost,托管用 HTTPS WebGPU 要求 secure context
cwebp / dwebp(libwebp) 可选 npm run optimize:sponza 资源重建时需要
WebGPU storage 特性 必需 speedball-gi 依赖;无 WebGL 回退

2.2 运行时依赖(package.json)

"dependencies": {
  "@perplexdotgg/bounce": "^1.9.0",   // 刚体物理(球池)
  "lil-gui": "^0.21.0",               // 调试 GUI
  "speedball-gi": "^0.6.7",           // WebGPU DDGI + 局部反射核心库
  "stats-gl": "^4.2.3",               // 性能统计面板
  "three": "0.185.1",                 // Three.js(固定版本)
  "three-mesh-bvh": "0.9.14"          // GI 用到的 BVH 追踪
},
"devDependencies": { "vite": "^8.2.1" }

2.3 安装与启动

git clone https://github.com/norio/speedball-gi.git
cd speedball-gi
npm ci                      # 锁定版本安装
npm run dev                 # 启动 Vite 开发服务器(默认端口 5173)

打开 Vite 打印的 URL(通常 http://localhost:5173/speedball-gi/)即进入 Sponza。其余两个场景在同一源下:

  • /speedball-gi/simple.html(Afterimage Court)
  • /speedball-gi/ballpool.html(Ball Pool)

应用内左上角 HUD 也提供了三个场景的互相跳转链接。

2.4 其它可用脚本

命令 说明
npm run dev 启动 Vite 开发服务器(端口 5173)
npm run build 用 Vite 把三个 HTML 入口构建进 dist/
npm run preview 本地预览已构建产物(需先 build
npm test node --test 校验 Sponza WebP 清单与 30 MiB 负载预算
npm run optimize:sponza cwebp/dwebp 把已还原的 JPEG/PNG 源集转 WebP、去重、更新 glTF 清单(需 libwebp)

2.5 部署

每次推送到 main,GitHub Actions(deploy-pages.yml)会 npm ci → npm test → npm run build,把 dist/ 部署到 GitHub Pages(项目路径 /speedball-gi/),在线演示地址:https://norio.github.io/speedball-gi/

2.6 交互说明(来自 README Controls)

  • Sponza:拖拽环绕、滚轮缩放;Sky/Sun 调太阳方向与大气;GI 调探针场/显示探针;Post 配 GTAO/TRAA/Bloom;Display 调色调映射、曝光、纹理可见性、DPR、帧率上限。相机或 GUI 静止后,空闲门控的探针求解才会收敛。
  • Afterimage Court:拖拽/缩放;按 G 对比"仅直接光照 vs GI";Court 文件夹调太阳强度与曝光;共享 GI/Post 文件夹。
  • Ball Pool:指针移动控制灯光并推开附近小球;按住鼠标把球送回池顶(触屏用双指);按 G 对比;"drop 20 balls" 立即重置一批。

3. 工作流程与具体执行步骤

3.1 核心集成约定(各场景一致)

README 与三套入口代码都强调:installSpeedballGI() 必须在首次渲染或 renderer.setAnimationLoop() 之前调用。原因是 SPEEDBALL GI 会在 lit 材质编译前注入一个"GI 感知的 Three.js lights 节点",若渲染循环启动后才安装,缓存的非 GI lights 节点无法被替换,GI 永远接不进来。

每个场景的运行时流程:

  1. 创建/加载几何与光照(Sponza 用 GLTFLoader 加载 public/Sponza/glTF/Sponza.gltf;Court 用 simple_scene.js 程序化生成;Ball Pool 用 ballpool_scene.js 物理世界)。
  2. 场景就绪后调用 gi.requestRebuild() 请求探针场重建(自动拟合包围盒)。
  3. 每帧调用 gi.update({ playing: false })
  4. 通过共享后处理管线 renderPipeline.render() 渲染。

playing: false 并非关闭 GI 或暂停球池物理,而是标记"场景不在播放时间线",让 SPEEDBALL GI 的空闲门控(idle gate)把繁重的 BVH 构建与 GPU 求解推迟到相机/GUI 交互静止后再进行——从而绝不会在交互中途出现帧卡顿

天空网格、探针辅助体、诊断反射体等都通过 excludeFromGI() 排除在 GI 追踪之外,避免它们撑大自动包围盒或污染自身光照。

3.2 统一预色调映射渲染管线(五个阶段)

  1. WebGPU 场景 Pass:用 MRT(Multiple Render Targets)写入 colordepthvelocityview-space normals
  2. GTAO:调制场景颜色(环境光遮蔽)。
  3. TRAA:解析相机抖动(jitter)与时间历史(temporal history)。
  4. Bloom:叠加在解析后的图像上。
  5. 色调映射 + 曝光:由 renderer 应用所选 tone mapping(ACES Filmic 等)与曝光。

因 TRAA 已提供抗锯齿阶段,画布 MSAA 被显式关闭new THREE.WebGPURenderer({ canvas, antialias: false }))。

3.3 Sponza 场景具体执行步骤(以 src/main.js 为例)

  1. #c 画布与 #status 状态元素,注册 error/unhandledrejection 全局监听以显示错误浮层。
  2. 创建 WebGPURendererantialias:false),设 DPR、size、ACES Filmic 色调映射,await renderer.init();随后检查 renderer.backend.isWebGPUBackend !== true → 非 WebGPU 直接报错退出。
  3. 创建 ScenePerspectiveCamera(62°, …)OrbitControls(带阻尼)。
  4. 天空与太阳SkyMeshexcludeFromGI)+ DirectionalLight(带阴影)+ 微弱 HemisphereLight 兜底。关键updateGiSky() 把无太阳的 SkyMesh 穹顶近似为 CPU 端 9 系数球谐(SH-9),只给探针核 9 个 vec3 uniform,避免纹理采样/导数/探针内存;方向太阳光保持 direct-only。
  5. 安装 GIgi = installSpeedballGI({ renderer, scene, camera, enabled, intensity, divisions, hysteresis, roughReflections:true, reflectionIntensity, reflectionSkyFallback:true }),随后 gi.setSmoothness(1)gi.setFilterStrength(1)applyGiSettings(gi, params)updateGiSky()refreshSunConsumers()
  6. createProbeHelpers(scene, gi, params) 拿到 updateProbeHelpers 回调;addGiPanel(...) 注入共享 GI 面板(不持久化到 Sponza);addPostPipeline(...) 注入 GTAO/TRAA/Bloom 并返回 { renderPipeline, aoPass, traaPass }
  7. Display 面板:色调映射、曝光、纹理可见性、DPR、帧率上限。
  8. 加载 SponzaGLTFLoader.load(sponzaUrl, …),遍历 mesh 设阴影并调用 prepareSponzaMaterialForGi()(把金属度 >0.5 归一到介电 metalness=0、粗糙度 <0.85 提到 0.85,让漫反射反弹可读);加入场景后按包围盒调整阴影相机与天空缩放;添加一个纯金属诊断球(excludeFromGI,便于观察局部反射);最后 gi.requestRebuild()
  9. 渲染循环setAnimationLoop):可选帧率上限 → controls.update() → 金属球上下浮动 → 若 sunGiDirtyflushSunGiRefresh()gi.update({ playing:false })updateProbeHelpers()renderPipeline.render()stats.update()

3.4 Ball Pool 场景关键流程(动态 GI)

  • ballpool_scene.js@perplexdotgg/bounce 建立 World(重力 -9.81、速度/位置求解迭代、linearDamping/angularDamping),创建六面墙(白墙 + 左红墙 + 右绿墙)与按视口宽算出的实例化球体(InstancedMesh,按颜色分组,DynamicDrawUsage)。
  • 每帧:step(deltaSeconds)(推进物理)→ syncMeshes()(把刚体位置/朝向写回实例矩阵,睡眠或微小位移则跳过以省开销)→ gi.update({ playing:false }) → 渲染。
  • 指针交互pointermoveRaycasterfrontPlane 求交得到灯光目标位置;移动时 pushAlongRay() 沿射线把半径内小球施加线性冲量;按住指针(触屏双指)则持续 respawnBalls() 把球送回顶部。灯光位置做指数缓动跟随指针。
  • 视口 resize 时(去抖 120ms)ballPool.rebuild(camera.aspect) + gi.requestRebuild() 重建。

3.5 资源优化与校验流程

  • scripts/optimize-sponza-textures.mjs:把已还原的 JPEG/PNG 源集用 cwebp 转 WebP(基础色 quality 82、法线/金属粗糙度 quality 95、alpha 掩码无损),用 SHA-256 对逐字节相同的法线贴图去重,更新 glTF 的 EXT_texture_webp 扩展与 textures[].extensions.source,删旧 JPEG/PNG,并统计引用负载。
  • tests/sponza-assets.test.mjsnode --test 跑两组断言——(1)Sponza 仅引用 WebP(校验 EXT_texture_webp、纹理映射 SHA-256、alpha MASK 图像数=3、各 WebP 尺寸 1024²/white.webp 4²、WebP 资产总 SHA-256、无遗留 legacy 纹理);(2)引用负载 ≤ 30 MiB(校验单一 buffer 的 SHA-256 与大小、累加 gltf + buffer + 全部图片字节数)。预期哈希硬编码进测试,作为"已验证内容"的契约。

4. 文件逐个解读(按模块)

4.1 根级配置与文档

文件 作用 模块
package.json 项目元数据、engines、5 个 scripts、依赖(speedball-gi/three/@perplexdotgg/bounce/lil-gui/stats-gl/three-mesh-bvh)、devDep vite 构建/依赖
package-lock.json 锁定依赖树(CI 用 npm ci 构建
vite.config.js base: '/speedball-gi/'build.rollupOptions.inputindex.html/simple.html/ballpool.html 三个入口都打进 dist/resolve.dedupe:['three']alias 'three/addons'optimizeDeps.include 预打包 WebGPU/物理/GI 相关包 构建(多页)
.npmrc npm 配置(如 engine-strict 等,随仓库) 构建
.gitignore 忽略 node_modulesdist 工程
LICENSE MIT 许可证 法律
README.md 项目总览、Highlights、三场景说明、Requirements、Getting started、Controls、集成原理、渲染管线、限制、脚本、部署、Sponza 资产、致谢 文档
dependabot.yml 依赖自动升级(如把 @perplexdotgg/bounce 1.8.1→1.9.0 的 PR) 工程
assets/sponza-ballpool.webp README 用的 Sponza + Ball Pool 合照 文档资产

4.2 三场景入口(HTML + 主 JS)

文件 作用 模块
index.html Sponza 入口页:<canvas id="c">、左上 #hud(标题 + 场景导航到 simple.html/ballpool.html)、#status 状态层、<script type=module src=/src/main.js> Sponza 入口
src/main.js Sponza 主程序(见 §3.3):渲染器/WebGPU 检查、SkyMesh+太阳、Sponza glTF 加载与材质归一化、动画金属球、installSpeedballGI、共享 GI/Post 面板、Display 面板、空闲门控、每帧 gi.update({playing:false}) Sponza 核心
simple.html Afterimage Court 入口页(结构与 index.html 类似,引 src/simple.js Court 入口
src/simple.js Court 主程序:渲染器/WebGPU 检查、installSpeedballGIcreateSimpleScene()、OrbitControls、Court 文件夹(太阳强度/曝光)、G 键切换 GI 开关(gi.setEnabled)、每帧 gi.update({playing:false})、共享 GI/Post 面板、HUD 显示 LIVE/BUILDING/OFF 状态 Court 核心
src/simple_scene.js 程序化"余像庭院"几何:standard() 材质工厂、box() 造盒、addColonnade() 十柱、addRoofBeams() 横梁、addSculptures()(漫反射弹力球、钴蓝环面、局部反射金属球 excludeFromGI);红墙(oxide)/绿墙(viridian) + 石膏柱 + 中央步道 + 天窗方向光 + 半球光。createSimpleScene() 返回 { root, sun } Court 场景生成
ballpool.html Ball Pool 入口页(引 src/ballpool.js 球池入口
src/ballpool.js 球池主程序:渲染器/WebGPU 检查、installSpeedballGI、黑底场景、fitCameraToBox()createBallPoolScene(scene).rebuild(aspect)、跟随指针的 PointLight(投影)、共享 GI/Post 面板、Ball Pool 文件夹(灯光强度/"drop 20 balls")、G 键切换、pointerdown/move/up 交互、resize 时重建、setAnimationLoop 中缓动灯光 + respawnBalls/pushAlongRay/step/syncMeshes/gi.update/渲染 球池核心
src/ballpool_scene.js 物理与实例化渲染世界(见 §3.4):World(bounce)、六面墙(addWall 同时建碰撞体 + 网格)、createBalls()(按 getBallCount(boxWidth) 体积算球数、InstancedMesh 按颜色分组)、rebuild(aspect)(按视口宽 getBallPoolWidth 重建)、step()syncMeshes()(睡眠/ε 跳过优化)、respawnBalls(count)pushAlongRay(origin,dir)(射线最近点 + 半径衰减冲量)、dispose()。导出常量 BALL_RADIUS/BOX_HEIGHT/BOX_DEPTH/CAM_FOV 球池物理/渲染

4.3 共享辅助模块(三个场景共用)

文件 作用 模块
src/gi_settings.js 共享 GI 控制面板的 Demo 辅助(非库的一部分)。GI_DEFAULTS(Sponza 调参 = 规范值)、localStorage 持久化(KEY='speedball-gi-settings-v2',类型安全合并,自适应时间混合键不恢复)、addGiPanel(gui, gi, params, {onInteract, onStructure})(为每个页面生成完全一致的 GI 控件:enabled/intensity/sky/reflections/showProbes、Grid 结构旋钮 divisions/rays/cascades、Temporal、Quality)、applyGiSettings(gi, s) 一次性把全部共享设置推入探针场 GI 设置
src/post_settings.js 共享后处理管线(GTAO→TRAA→Bloom,均在色调映射前)。POST_DEFAULTSaddPostPipeline(gui, renderer, scene, camera, params):建 RenderPipeline + MRT pass(写 output/velocity/normal)、ao()(GTAO,可 useTemporalFiltering)、traa()(TRAA)、bloom(),用 uniform 控制 GTAO 混合与开关,applyPostOutput() 重组输出节点;返回 { renderPipeline, aoPass, traaPass, applyPostOutput } 后处理
src/TRAANode.js 本地 TRAA(时间重投影抗锯齿)实现class TRAANode extends TempNode(导出 traa 工厂与默认类)。双 RenderTarget(history/resolve)、Halton 序列抖动(setViewOffset/clearViewOffsetrenderPipelineonBeforeRenderPipeline/onAfterRenderPipeline 钩子同步)、updateBefore() 每帧累加历史、setup() 用 TSL 实现 resolve:3×3 深度采样(最近/最远)、速度重投影、varianceClipping(方差裁剪,NVIDIA GDC2016)、subpixelCorrectionflickerReduction(Lottes max3 压缩 RGB 防闪烁)、AABB 裁剪(Playdead temporal)。尺寸变化时用 beauty 纹理重置历史避免黑屏渐变 抗锯齿(TRAA)
src/probe_helpers.js 可选探针网格可视化createProbeHelpers(scene, gi, params) 返回 updateProbeHelpers()。当 params.showProbesgi.hasData() 时,按 gi.getResolution()/getBounds()InstancedMesh 在每格中心放小球;lit 模式下用 gi.node.sampleIrradiance(...) 把球染成实际辐照度颜色(直接看到探针采到的 GI),否则染灰;签名缓存避免每帧重建 探针调试
src/style.css HUD(左上无面板纯文字叠加 + 场景导航 + GI 状态 LIVE/BUILDING/OFF)、#status 加载/错误浮层、stats-gl 定位、移动端响应式(≤700px/≤560px 时 GUI 改为底部抽屉、隐藏副标题) 样式

4.4 资源与构建/质量保障

文件/目录 作用 模块
scripts/optimize-sponza-textures.mjs Sponza 纹理 WebP 化脚本(见 §3.5):检测现有 WebP 清单完整性、cwebp 转换(按角色分 quality)、SHA-256 去重、EXT_texture_webp 扩展注入、更新 Sponza.gltf、删旧图、统计负载 资源管线
tests/sponza-assets.test.mjs node --test 校验:① 仅 WebP 引用 + 纹理映射 SHA-256 + alpha MASK 数=3 + 尺寸 + WebP 资产总 SHA-256 + 无 legacy;② 单 buffer SHA-256 + 引用负载 ≤ 30 MiB。硬编码预期哈希作契约 质量门禁
public/Sponza/glTF/Sponza.gltf + Sponza.bin + 64×.webp 打包的 Sponza 模型(Crytek→Frank Meinl/Marko Dabrovic,PBR 贴图来自 alexandre-pestana.com,MikkTSpace 切线、法线/金属粗糙度重打包、顶点/索引网格优化) 3D 资产
public/Sponza/README.md 模型来源、处理笔记(切线、alpha 掩码重采样、金属粗糙度合并、恒定漫反射系数 0.58、网格优化)、Web 交付优化说明、许可声明 资产文档
public/Sponza/screenshot/large.jpgscreenshot.jpg 截图 资产
.github/workflows/deploy-pages.yml GitHub Pages 部署:push main 触发 → checkout → setup-node 24 → npm cinpm testnpm run build → configure-pages → upload-pages-artifact(dist) → deploy-pages;含 concurrency 取消旧运行、permissions 最小集 CI/CD

5. 优势、不足、创新点与亮点

5.1 优势

  1. 三场景互补:建筑(Sponza)、程序化(Court)、动态(Ball Pool)分别覆盖"静态复杂几何 / 间接颜色对比 / 移动实例化几何下的实时 GI",展示维度完整。
  2. 世界空间 DDGI + 自动拟合:探针体积自动包围场景,无需预烘焙;可选探针可视化(含 lit 模式显示真实辐照度),便于教学与调试。
  3. 粗糙/光滑局部探针反射:Sponza 与 Court 提供 local probe reflections,比纯环境光更有质感。
  4. 实时可调 + 空闲门控:GI 探针布局、射线数、时间响应、漏光抑制、天空贡献、显示设置全部可直播控;空闲门控确保"交互时不卡、静止才收敛",体验顺滑。
  5. 统一后处理栈:GTAO + TRAA + Bloom 在色调映射前组合,且 TRAA 自带抖动与历史管理。
  6. 共享控制面 + 持久化gi_settings.js 让三页共享完全一致的 GI 控件,且 GI 键 localStorage 持久化(类型安全合并,避免陈旧/手改数据注入 NaN)。
  7. 资源严格管控:WebP(EXT_texture_webp)分级质量 + 逐字节去重 + node:test 用硬编码 SHA-256 与 30 MiB 预算做"内容契约"校验,保证线上资产可信且体积小。
  8. 响应式 HUD/GUI:移动端 GUI 变底部抽屉,触屏双指交互球池,开箱可用。

5.2 不足 / 限制(README 明确列出 + 代码观察)

  1. 收敛需要时间:探针光照随帧累积收敛,初始加载及灯/几何/网格结构变更后需若干帧才稳定。
  2. 局部反射是近似:属"离屏稳定光照层",非逐像素镜面反射或路径追踪。
  3. Sponza 材质被归一化:加载器把强金属度归一到介电、提升低粗糙度,使漫反射反弹可读——因此不是源 glTF 的原始材质参考查看器
  4. WebP 强依赖:打包 Sponza 要求 WebP(WebGPU 浏览器已支持,故无回退纹理集)。
  5. GUI 不持久化:重载/重访恢复该场景默认值(Sponza 故意不持久化以保证"每次回到发布调参")。
  6. 无 WebGL 回退:硬性要求 WebGPU storage 特性,旧浏览器不可用。
  7. 依赖上游库演进:GI 行为基本由 speedball-gi(npm ^0.6.7)与 three@0.185.1 决定,本仓库主要是"展示/适配/测试场景",核心算法不在本仓。

5.3 创新点 / 亮点

  1. 本地 TRAA 实现(TRAANode.js:把 three.js WebGPU 的 TAA 思路落地为本仓可控节点——方差裁剪(NVIDIA GDC2016)、子像素校正、Lottes 闪烁抑制、AABB 裁剪(Playdead temporal),并用 Halton 抖动 + renderPipeline 前后钩子做相机偏移同步;尺寸变化用 beauty 纹理重置历史防黑屏。这是本仓最"自研"的亮点代码
  2. 空闲门控的 GI 求解(playing:false:把繁重 BVH 构建/GPU 求解推迟到交互静止,从架构上消除"交互中途帧卡顿",是实时 GI 演示中很关键的体验设计。
  3. 共享 Demo 辅助模块驱动多页一致体验gi_settings.js/post_settings.js 抽离出"单一控制面",一处增减滑块即在三页同时生效;addGiPanelonInteract/onStructure 回调把各页的交互/结构事件统一回灌 GI。
  4. 探针可视化即"GI 读数"probe_helpers.js 的 lit 模式直接采样 sampleIrradiance 把探针染成实际辐照度颜色,把抽象的探针场变成肉眼可读的调试信息。
  5. 资产内容契约(content-contract)测试tests/sponza-assets.test.mjs 用硬编码 SHA-256 + 30 MiB 上限,把"资产正确性"变成可回归的 CI 门禁,配合 optimize-sponza-textures.mjs 形成可重建、可验证的资源闭环。
  6. 多页 Vite 工程化vite.config.jsrollupOptions.input 把三个 HTML 同时打包、base:'/speedball-gi/' 适配 GitHub Pages 项目路径、dedupe:['three'] 避免多副本,体现成熟的前端工程实践。

附:关键技术栈速查

维度 选型
渲染 Three.js 0.185.1three/webgpu,WebGPURenderer,TSL 节点)
GI speedball-gi ^0.6.7(WebGPU DDGI + local reflections,BVH 追踪 three-mesh-bvh 0.9.14
物理 @perplexdotgg/bounce ^1.9.0(球池刚体)
GUI lil-gui ^0.21.0
后处理 自研 TRAA(TRAANode.js)+ GTAONode + BloomNode(three/addons)
构建 Vite ^8.2.1(多页输入,base /speedball-gi/
测试 Node.js 原生 node:testtests/sponza-assets.test.mjs
资源 libwebp cwebp/dwebp(WebP 化 + 去重)
部署 GitHub Actions → GitHub Pages