中秋节的时候流行拍月亮,几位亲友使用手机拍摄的月亮照片质量之高,令笔者「瘫坐在椅子上、感到眩晕」,「就像看到了原子弹爆炸」一样。
此照片拍摄于 2024 年 8 月,相机 FUJIFILM X-T30 II,镜头 Tamron 18-300mm ƒ3.5-6.3 DiIII-A VC VXD,未使用三脚架,照片经过裁切。鉴定为不如手机拍的。
于是为了向笔者的母亲解释如何拍出这样的照片,决定制作这样一款 App,并在国庆假期期间消耗 Cursor 的套餐。
本 iOS App 支持:
- 在取景预览上叠加当前月相、天平动朝向和视直径
- 当月亮不在画面里时,在边框上给出指向和角距离
- 可开关地平线以下的月面,以及是否按真实月相打光
- 变焦、曝光补偿、点按对焦测光
- 手动校正:把月面拖到眼里的月亮上,偏差写入本机
- 拍照后把月面合成进照片,保存到相册并带上拍摄位置
拍摄效果
此图拍摄于 2026 年 10 月 8 日 10:27:40,背景为我司办公室天花板,关闭了月相模拟(残月几乎拍不到什么)。照片拍摄时大气模拟功能未实装。
本应用,以及下方的原理说明全部由 Cursor(Grok 4.7 High) 完成,App Icon 由 Google Gemini 设计,SVG 图标来自 SVG Repo。
你可以在 silly-astro-camera - GitHub 获取本项目源代码。
坐标系
方位角 从正北起、向东增加;高度角 从地平线起、向天顶增加,向下为负。世界坐标取 向北、 向西、 向天顶,与 Core Motion 的 xTrueNorthZVertical 一致。单位方向是
反解先归一化,再
设备坐标是 向右、 指向手机顶部、 指出屏幕。后置相机的光轴在设备坐标里是 。Core Motion 旋转矩阵的元素记为 。代码把设备向量 乘以该矩阵的转置,得到世界坐标:
画面的右 和上 先在设备坐标里按界面方向选取,再做同一次变换:
| 界面 | ||
|---|---|---|
| 竖屏 | ||
| 倒置 | ||
| 左横 | ||
| 右横 |
得到的光轴 、画面右 、画面上 就是后面投影用的相机姿态。
时间与基本引数
观测时刻用 UTC 拆成年 、月 和日的小数 。 含时分秒:
时令 、。格里高利改正用整数除法 ,
儒略日是
自 J2000.0 起的儒略世纪是
月亮级数用的五个基本引数(度,再折进 ;除升交点外,进入三角函数前换成弧度)是
是月亮平黄经, 是月日距角, 是太阳平近点角, 是月亮平近点角, 是纬度引数。升交点平黄经保留为度:
这里得到的是几何地平高度。大气折射在几何高度算完之后再加。
太阳
太阳另有一套平黄经和平近点角(度):
中心差(度)是
真黄经是 。再用一个只含 一次项的升交点 做光行差和章动的小修正,得到视黄经(度)
日地距离(天文单位)是
太阳黄纬取 0。
月亮的黄道坐标
黄经、黄纬是 的正弦级数,地心距离是对应的余弦级数。太阳偏心率因子
按该项里 的倍数 取值: 时乘 , 时乘 ,否则乘 1。黄经与距离共用同一组角,系数 的单位是 度, 的单位是 :
周期项表在 longitudeTerms 和 latitudeTerms。表外还有三项附加角(度)
黄经和(仍是 度)再加
黄纬和再加
黄经章动取 ,黄赤交角的章动取 。最终地心坐标是
、 以度计后再换成弧度, 以千米计。视半径只用这份地心距离,
是采用的月球半径。站心修正只改方向,不改 ,因此也不改 。
赤道坐标与恒星时
平黄赤交角(度)加上面的交角章动:
黄道 转到赤道 :
再 。太阳走同一公式且 。向量形式用于后面的月面坐标,绕 轴转过 :
格林尼治平恒星时(度)是
站心视差与地平坐标
月亮的地平视差最大约 ,和月面视直径同一量级,所以投影用站心方向。地理纬度 、东经 来自定位,海拔 以米计。地球赤道半径取 ,扁率因子 。归化纬度和观测者的地心坐标是
赤道地平视差角
地心时角
赤经改正
记分母为 ,站心赤纬和改正后的时角是
地平坐标由站心时角和站心赤纬得到。 先夹到 :
方位角公式以正南为零,加上 后变成从正北量起:
相位和月面朝向用下面两节的地心太阳、地心黄道月亮;地平方向用站心赤经赤纬。
大气
几何高度 是上一节的站心高度,单位在公式里用度。气压取观测海拔上的标准大气,气温固定 :
真高度低于 时,折射公式的输入夹到 ;高于 时夹到 。Saemundsson 公式在 附近没有定义,而那里的折射已经小于 。
是角分,参考波长 550 nm。视高度是 。水平半径仍是几何 。视月面不用 当圆心,而用上下边缘的中点,这样压扁后的边缘和折射后的边缘一致:
再夹进 。天顶附近 ;真高度接近 0 时 ,月面沿天顶变扁。方位角不变。
色散用 Peck–Reeder 折射率。, 以 μm 计:
波长取 650、550、450 nm。,其中 是几何月心的折射。红、蓝相对 550 nm 的高度差沿天顶平移,圆心仍是 550 nm。
消光用视圆心的高度。低于 时大气质量按 算,高于 时按 。Kasten–Young 大气质量里,括号外的高度是度,正弦用对应的弧度:
瑞利光学厚度( 以 μm 计)再乘气压:
晴空气溶胶不随气压变化,。付费的 Apple Developer Program 可以用 WeatherKit 获取当前能见度 (千米),再换成
为 。
透过率 。叠到精灵上的颜色是 ,绿通道为 1,亮度留在反照率上。月亮低时大气质量 变大,蓝/红下降,月面偏红。用户校正加在方位角和视高度 上。
月相
先在赤道坐标里求月日角距 。太阳用地心赤道坐标,月亮用站心赤经赤纬,距离仍用地心值。。
相位角 是从月亮看太阳和观测者之间的夹角:
亮面比例是圆盘上被照亮部分的投影面积比
明暗界线亮缘中点的位置角是
盈亏看地心黄经差。令 ,则 为盈。名称按 划分: 为满月, 为新月;盈月在 、 处分成峨眉月、上弦月、盈凸月,亏月对应残月、下弦月、亏凸月。
天平动与轴位置角
月面朝向是几何模型:自转轴相对黄道倾斜 ,指向由升交点决定,本初子午线用平黄经,不展开完整的光学天平动级数。黄道直角坐标里,月理北极和地心指向月亮的单位向量是
平均地球方向取在黄道面内、与平黄经相反:
把它投到垂直于 的平面上并归一化,得到月面本初子午线方向 。月面东方是 。从月心指向地球的方向是 ,于是
轴位置角表示月理北极相对天北极、在月亮处的天空中偏了多少。把 和 用上一节的交角旋转变到赤道坐标,记为 、。天北极是 。两者都减去沿 的分量,投到垂直于视线的平面上:
投影长度小于 时 。
月面怎么画
MoonAlbedo.jpg 是等距圆柱图:经度 在图中央,北极在上。精灵图是 。像素 先变成单位圆盘坐标,原点在图心, 朝上:
的像素透明。圆内
光照在这个朝向观测者的圆盘上计算,天平动只移动纹理,不改法线。亮缘相对月轴的角是 。相位角为 时,视线坐标里的太阳方向是
它已经是单位向量。朗伯亮度是
阳面覆盖 。交界这一小段把透明度收到 0,避免暗面留下黑边。暗面另算一份透明度,地照从观测者方向照来,朗伯项是 。相位角 和太阳高度决定的夜天权重 合成系数
太阳高于 时 ,低于 时 ,中间用同一段 smoothstep。 存在月亮状态里,校正偏差不改它。关掉「月相模拟」时 和 都按 0,整盘被照亮,没有灰光。暗面强度是
是 8 位精灵上的可视增益。物理日地照比大约 ,直接用会舍入成黑。边缘淡出 照旧。透明度是
预乘颜色在阳面是 ,在暗面是 乘暗面那一份透明度。
关闭「月相模拟」时渲染用的 改为 0,于是 ,整盘被照亮;、 和天平动仍参与朝向。
采样前把圆盘点先绕 转 ,再绕 转 :
纹理经纬度是 、。图上的归一化坐标
折进 , 夹到 ,再按图宽高做双线性插值。半径 在 以内 ,最外 线性淡出:
、、、、 各自换成度再乘 2,向零截成整数。 按 一档取整。这六个量和「是否模拟月相」组成缓存键。角度相邻档大约相差 。预览把精灵拆成红、绿、蓝三张通道图,绘制时做竖直缩放、绕天顶旋转,并沿天顶平移色散。
视场
采集格式优先使用几何畸变校正后的 4:3 横向视场角 (度,随即换成弧度),并在设备支持时打开几何畸变校正,使成像接近后面的直线投影。变焦不重新查表。 时
4:3 的短边视场由长边推出:
图像宽不小于高时,横向视场是长边、纵向是短边;竖幅则对调。界面上的变焦倍数是 再乘 displayVideoZoomFactorMultiplier。
投到画面上
月亮单位向量 。深度是
时认为月亮在相机后方或贴近视场边缘之外,这一帧不放月面。否则切平面坐标是
再除以半视场的正切,得到约在 的归一化坐标,最后换成像素。 向下:
像素半径用横向视场。水平半径是几何 ,竖直半径是 :
月心落在画面外、且超出水平半径和竖直半径里较大的那个边距时,这一帧不放月面。色散的像素偏移用同一套比例, 是红或蓝相对 550 nm 的高度差,正方向朝天顶:
精灵旋转让月理北极指向天空中的北。当地天北极在这套地平坐标里是 ,纬度 即其高度角。把它投到垂直于光轴的平面:
长度不小于 时,相对画面上方的顺时针角减去轴位置角:
退化时 。
天顶是世界坐标的 。把它投到垂直于光轴的平面,用和天北极一样的方法得到相对画面上方的顺时针角 。投影长度小于 时 。绘制时先把月理北极转到相对天顶的角 ,再把竖直边乘 ,最后转回 。红、绿、蓝三张通道图沿天顶错开 ,相加后再盖到画面上。
视高度满足 ,并且没有打开「地平线下仍绘制」时,预览不画月面。附近地景物几乎不被折射,仍以几何高度 0 为地平线,所以几何上已在地平线下、视上边缘仍露出的月亮会画出来。校正过程中不受这条约束。
月亮已经能被投影到画面内时不画箭头。否则仍用 求方向:若 ,归一化坐标与上面相同;否则不再除以深度,
屏幕坐标 、( 向下)。方向接近 0 时改成 。箭头锚点是从画面中心沿 走到内缩矩形的第一处。内缩是左 40、右 40、上 48、下 72。对每个朝向外侧的分量取
其中 是该分量指向的那条边, 是中心,只计入分母与行进方向同号的项。锚点是 。箭头旋转 ,角距离是
月亮在身后时 ,。
反投影是投影的逆。像素 先变回归一化坐标 、,再
然后用坐标系那节的反解从 得到 。
校正
校正把误差写成方位角和高度角上的两个常数。松手点反投影得到 ,减去未经校正的历算 :
两个偏差以度存在 UserDefaults。之后每次投影和合成前先改正:
这里的 、 已换成弧度。
拍照
按下快门时冻结已经加过偏差的月亮状态、当时的 ,以及当时的横向视场角。照片转正之后,用成片的像素宽高把这份横向视场重新分成 、,再套用同一组投影公式。预览和成片共享视场角,像素网格不同,所以月面位置是按成片重算的。
合成时 Core Graphics 的原点在左下,投影的 向下,因此
平移到 后,沿天顶(Core Graphics 里 向上)平移色散,旋转 ,竖直缩放 ,再旋转 。精灵画进边长 的正方形,高度取负,用来把图像翻正。红、绿、蓝各画一次。合成像素取红色样本的 R、绿色样本的 G、蓝色样本的 B,乘上消光颜色后夹到该像素的不透明度。没有压扁、色散和偏色时画一次。保存时把拍摄经纬度写入照片。