物理层和显示层严格分开
恒星的位置、星等、颜色、天空背景和观测几何属于物理层;屏幕上的压缩、拉伸、饱和度和 gamma 属于显示层。物理层不为了好看而改恒星,显示层也不把自己的参数伪装成天体物理常数。换一种显示风格时,恒星本身没有变;换一种观测条件时,显示管线也不需要重新发明物理。
渲染原理 / RENDERING PRINCIPLES
这张图里没有银河贴图,没有手绘辉光,没有尘埃素材。每一个像素都能追溯到 Gaia 星表里某一批真实恒星。Gaia 给出的不是一张照片,而是一张极大的恒星清单:每颗星在哪里、亮多少、颜色指标是什么。渲染器做的事情,是把这张清单按物理关系投回天空,再把超出屏幕能力的亮度范围压缩到可观看的图像里。银河由此出现,是因为恒星在银道面附近本来就更密;暗带由此出现,是因为某些方向上可见恒星本来就少。画面可信的基础,不在于它像某张天文照片,而在于它的每一层处理都能说清楚来源。
下面五个机制,就是这两条原则的具体实现。
恒星的位置、星等、颜色、天空背景和观测几何属于物理层;屏幕上的压缩、拉伸、饱和度和 gamma 属于显示层。物理层不为了好看而改恒星,显示层也不把自己的参数伪装成天体物理常数。换一种显示风格时,恒星本身没有变;换一种观测条件时,显示管线也不需要重新发明物理。
星等转亮度用 Pogson 关系,颜色从 Gaia BP-RP 色指数经恒星温度进入黑体光谱,光污染用 Bortle 天空背景和裸眼极限星等约束,银河在城市里消失用 Weber 对比阈值约束。即使某个参数最终服务于屏幕观感,它也要挂在一个可解释的物理量或观测经验上。
Gaia 星表直接给出每颗星的 G 星等和 BP-RP 色指数。星等是对数标度,不是线性亮度;BP-RP 是 Gaia 自己的蓝端与红端测光差值,也不是普通摄影里的 RGB。屏幕最终需要的是线性 sRGB 三通道,所以这里必须经过两次翻译:一次把星等翻成相对光通量,一次把色指数翻成显示颜色。
星等到亮度使用 Pogson 公式:L = 10^(-0.4 × (m - m_ref))。意思是星等差 5 等,亮度差 100 倍;星等数值越小,星越亮。m_ref 只是亮度锚点,决定整张图落在屏幕动态范围的什么位置,不改变恒星之间的相对亮度关系。
颜色的链条更长。BP-RP 先根据 Pecaut & Mamajek 主序星标定转成有效温度,太阳 G2V 对应 BP-RP 约 0.82、温度 5772K。温度再进入黑体辐射谱,乘以 CIE 1931 配色函数积分,得到线性 sRGB。最后用太阳白点校准:5772K 的太阳色温被设为中性白,其他恒星相对这个白点偏蓝或偏红。
这里最容易混淆的是把 Gaia BP-RP 当成 Johnson B-V。二者来自不同滤光系统,数值不能直接互换。若把 BP-RP 当 B-V 使用,银盘里大量中等偏红的恒星会被整体推成暖黄,再被显示层饱和度放大,整条银河就会泛黄。正确标定之后,银盘回到接近中性的灰白,银心方向保留微暖,蓝白星和橙红星的差异仍然存在。
Gaia 星表里的恒星是点源,但屏幕上的点源不能只画成一个像素。真实光学系统里,一颗点光源经过镜头、传感器和显示后,会变成一个有宽度的点扩散函数(PSF)。这个形状是成像系统的属性,不是恒星的属性;暗星和亮星共享同一个核心形状,只是能量不同。
渲染器让所有恒星共享一个窄高斯 PSF,核心宽度为 0.6 像素。这个数值故意偏窄,因为核心 PSF 只负责让点源脱离硬像素感,不负责制造亮星的大光斑。若核心取到 1 像素以上,暗星颗粒和银河纹理会被抹平,画面像隔着一层薄雾。
亮星的大小由饱和溢出产生。线性亮度超过饱和线的部分先被截下,再按两个更宽的高斯翼重新散布回画面,宽度分别是 3 像素和 9 像素。这个过程守恒能量,溢出翼只是把过亮核心的能量摊开,不额外制造光。饱和线也挂在星等阶梯上,而不是固定屏幕亮度上——这样在模拟更高灵敏度时,整套亮度阶梯一起平移,不会把整条银河错误地洗成一片泛光。
视觉结果是:暗星仍是锐利细点,中等星成为小圆点,亮星出现盘面和柔和光晕。恒星的视大小随亮度连续变化,而不是被人为分成几类笔刷。
肉眼看到的银河不是由少数亮星组成的图案,而是大量暗星的积分光。单颗暗星可能不可分辨,几百万、几亿颗叠在银道面上,就形成连续的乳光。Gaia 星表越深,这个积分光越接近真实天空;星表截断越浅,渲染器越需要代理那些没有画进来的暗星。
在浅星表阶段,渲染器用光度函数做补偿。星表中 G≥11 的暗星乘以 3.8 倍增益,用来代理更暗、尚未逐颗纳入的恒星族群。这个数字来自 Gaia 自身的星等分布:最后几档暗星的总光通量变化可以外推,估算截断之后还缺多少积分光。
但增益只能代理总光,代理不了颗粒统计。少量较亮星乘大增益,和大量更暗星逐颗累积,哪怕总光通量相同,视觉质感也不同。前者容易粗糙,后者会形成更细腻的乳光和更稳定的暗带边界。因此主图使用 Flatiron bulk mirror 生成的深星表缓存,广州银心视场约 6.16 亿颗 G<20 恒星。到了这个深度,大裂隙的形态不再靠屏幕遮罩或尘埃贴图强调,而是通过两侧星云变亮、缺星区域相对变暗而变得可读。能用真实恒星,就不用聪明的近似。
同一片银河,在暗空里清楚,在城市里消失,原因不是星表变了,而是观测者面前多了一层天空辉光。城市灯光经大气散射,成为叠加在线性星光上的背景。背景越亮,星光与背景的对比度越低,暗星和弥散银河先被淹没,只剩最亮的恒星。
Bortle 等级提供了这一层背景的观测锚点。Bortle 1 对应顶级暗空,裸眼极限星等约 7.8;Bortle 6 的明亮郊区约 5.3;Bortle 9 的市中心约 4.0。渲染器把每个等级映射到天空背景面亮度,同时用 NELM 决定点源可见的星等边界。
点状恒星和大面积银河带不能用同一条规则判断,因为人眼对点源和弥散光的检测机制不同。弥散光使用 Weber 对比阈值:结构亮度相对背景的增量低到几个百分点以下时,人眼难以察觉。默认阈值是 0.035,施加在低频银河光上,不抹掉高频点源。结果是 Bortle 1 下银河几乎完整,Bortle 7 左右银河从裸眼视野里消失,与真实观测经验一致。
前四个机制都在计算线性光:恒星亮度、颜色、PSF、饱和溢出、天空辉光和人眼阈值。可是屏幕不是线性天文仪器——Gaia 恒星亮度跨度超过百万倍,普通 SDR 屏幕只能显示有限对比度。最后一步 tone mapping 的任务,是把线性物理结果压进屏幕,同时不破坏前面建立的相对关系。
这也是物理层和显示层分离最具体的地方。渲染器先在每个画面里估计 sky floor(天空背景的低百分位亮度),把它锚定到固定深灰值。随后只增强 sky floor 以上的星光信号,避免把背景本身拉成发灰的幕布。多张 Bortle 面板共享同一个 stretch,而不是每张单独归一化——否则城市面板会被强行拉亮,读者会误以为光污染下仍能看到差不多的星空。
颜色在亮度压缩后释放,chroma 参数为 1.8,把 BP-RP 已经提供的色温差异从压缩状态中带回来。高光使用 soft shoulder,让银心和亮星周围的高光平滑滚向显示上限,保留内部层次。最后统一做 gamma 2.2 输出,交付给普通屏幕。显示层承认自己是在为屏幕服务,但它不改写物理层已经算出的星空。
Gaia 查询和渲染完全分开。生产路径不依赖 ADQL 实时查询,而是使用 Flatiron Institute 的 Gaia DR3 bulk mirror。当前广州银心视场下载了 2044 个原始 gzip 分片,压缩后约 412 GiB。原始分片保留在本地,之后按视场和星等过滤,只抽取渲染需要的列,生成 NPZ 缓存。
这条链路的意义在于,昂贵的数据获取只做一次。Flatiron 原始分片进入本地过滤,生成 G<20 的深星表缓存;渲染器读取 NPZ 后,可以反复用不同 Bortle 等级、不同显示参数、不同取景方式生成新图。Bortle 1 到 9 网格、消融序列、主图、飞行视频和高清预览,使用的是同一套数据缓存和同一套渲染机制,只是在观测几何、输出形态和显示参数上分叉。HiPS 高清路径也是同一管线放大后的结果。
这仍然是星表渲染,不是相机 raw 模拟。它解释恒星如何在天空中分布、光污染如何改变可见性、显示管线如何把结果交给屏幕;它不模拟真实镜头的每个光学缺陷,也不追求测光级精度。
Gaia 在最亮恒星上会饱和或漏测。约 G≲6 的亮星需要用 Yale Bright Star Catalog BSC5 补充。当前主缓存是在 6.16 亿颗 Gaia 深星表上补入少量 BSC5 亮星,避免织女、心宿二、河鼓这类视觉锚点缺席。
Gaia 是恒星星表,不包含星云。图中暗带来自可见恒星的减少,星云本身不会被额外画上去。若某个区域在真实天文照片中有明显红色 Hα 发射或蓝色反射云气,这个项目不会凭空补出来。
Gaia 视差只在太阳附近的有限距离内可靠。尺度大约是几千光年内的局部数据球。飞行视频可以展示星座散架和近邻星场重投影,但不能生成一张真实的银河系俯视图——人类至今也没有那样的照片。
这不是把相机长曝光照片反推成裸眼视觉。长曝光、堆栈、去梯度、局部曲线和色彩增强会产生另一种影像语言。这个项目的目标更窄:从 Gaia 恒星出发,在清楚标注的显示规则下,生成一张可追溯、可复现、可调参数的星空图。
想看全部实现细节,仓库里的 docs/rfc.md 是完整的设计文档,docs/working.md 记录了每一次调参和踩坑的过程。