深色模式
Framebuffer 与 ping-pong
默认绘制结果进入屏幕对应的 framebuffer。换一个输出目标,同样的片元(fragment)计算就能把结果存进纹理,供下一阶段使用。这是许多 WebGL 科学可视化和后处理的基础:GPU 不只产生颜色,也能产生下一步的数据。
输出纹理相当于一张结果表
帧缓冲对象(Framebuffer Object,FBO)本身更像附件配置。颜色纹理、深度缓冲等 attachment 才存放内容。设一个 128×128 状态纹理,每个 texel 存一颗粒子的经纬度和寿命,就能容纳 16,384 个粒子槽位。一次覆盖全部 texel 的绘制,让每个片元计算自己的下一状态。
这不是 CUDA 那种任意地址写入。典型 WebGL 片元路径写向当前片元对应的输出位置,从输入纹理任意采样。算法设计需要适应这种“读旧值,写本格结果”的模型。
为什么必须两张纹理
如果一次 draw 同时把同一张纹理作为采样输入和被写入的颜色附件,就构成纹理反馈循环,不能指望 GPU 按 texel 顺序安全更新。正确做法是准备 A、B:
text
第 n 步:读取 A → 更新 shader → 写入 B → 用 B 绘制粒子
交换角色
第 n+1 步:读取 B → 更新 shader → 写入 An 是从零或一开始的离散步编号(step index),没有时间单位;每一步推进多少模拟秒由 dt 决定。交换的是引用,不是每帧把整个数组从 GPU 下载到 CPU。初始化时上传一次状态;后续计算、绘制都留在 GPU。若还要保留多时刻轨迹,可以增加环形历史缓冲,但历史坐标属于哪个数据时刻也要明确。
格式与清理是算法的一部分
位置可以用浮点保存,但浮点颜色附件需要实际能力支持。创建 framebuffer 后要验证其完整性(framebuffer completeness,附件格式与组合是否合法);尺寸、格式、附件组合不合法时,不能把黑屏归咎于 shader 算法。viewport 还必须匹配输出纹理尺寸,否则只更新部分粒子或写错区域。
Cesium 1.145 的 Framebuffer、ComputeCommand 是内部接口。ComputeCommand 这里利用渲染管线做计算,不代表 WebGL 提供了通用 compute shader。使用引擎适配器能沿用其资源与命令管理,但仍需固定版本并正确释放拥有的 texture、program 和 framebuffer。
当前实验把独立的 U/V 浮点纹理作为速度输入,两张状态纹理保存区域归一化位置、寿命与种子。GPU 更新后直接画点粒子;图中的循环对应实际 ping-pong,历史轨迹属于可继续扩展的存储方式。
风的运动
观察 GPU 更新粒子状态与在地球上的运动。
在浏览器本地运行 · 无需账号 · 鼠标拖动旋转,滚轮缩放
教学合成数据,不代表真实观测或天气预报。规则场用于验证,拟真形态用于观察复杂结构。 数据说明
查看运行源码
此代码直接读取实际运行的实验模块。按 kind 分支定位当前实验,也可查看数据公式和 GLSL。
读取源码…观察任务
运行粒子实验,观察连续运动与重新播种。假设一帧中所有粒子都从旧位置推进一步,为什么不能让后半部分粒子读到前半部分已经更新的位置?若为了调试每帧都 readPixels,会付出什么代价?
展开答案
同一步应共享一致的旧状态,粒子更新不应取决于执行顺序。ping-pong 同时保证读写合法和状态一致。同步回读可能迫使 CPU 等待 GPU 完成,破坏流水并增加传输;应把回读用于小规模诊断,而不是正常动画主循环。
阅读依据:Khronos WebGL 2 规范、Cesium 1.145 ComputeEngine 源码(internal)。