はじめに
農工大 Advent Calendar 2025の19日目の記事です。
農工大OB枠で参加させていただきました。
先月からハンググライダーで空を飛ぶようになったmkc1370です。
↓空を飛ぶmkc1370の図

フライトの振り返りがVRでできたら得られるものが多いのではないかと思い、フライトログからパイロットからの視界を再現するアプリを作ってみました。
制作時間は8時間程度、コードの9割はClaude Codeと荒削りではありますが、それっぽいものができたので紹介します。
↓成果物の動画(音が出ます)
フライトログ(IGCファイル)
スカイスポーツのフライトログの記録にはIGCファイルがよく使用されています。
IGCファイルについては以下の記事を参考にしました。
今回は山形県南陽市にある十分一山でフライトしたときのログを使用しました。
使用した機材はiPhone 15 Pro、アプリはFlyskyhy v9.4です。
今回は軌跡を作りたいだけなので、毎秒記録されるBレコード(時刻・緯度・経度・データの種類・気圧高度・地球楕円体からの高度)のみを使用しています。
↓気合のパース
private bool TryParseBRecord(string line, out TrackPoint p)
{
// 位置・長さはIGC標準に基づく(1-origin想定)。ここでは0-originのsubstringで扱う
// BHHMMSS DDMMmmm N DDDMMmmm E F PPPPP GGGGG
p = default;
try
{
if (line.Length < 36) return false;
var tStr = line.Substring(1, 6); // HHMMSS
var hh = int.Parse(tStr.Substring(0, 2));
var mm = int.Parse(tStr.Substring(2, 2));
var ss = int.Parse(tStr.Substring(4, 2));
// 緯度
var latDegStr = line.Substring(7, 2); // DD
var latMinStr = line.Substring(9, 5); // MMmmm
var ns = line[14];
// 経度
var lonDegStr = line.Substring(15, 3); // DDD
var lonMinStr = line.Substring(18, 5); // MMmmm
var ew = line[23];
var fix = line[24]; // 'A' or 'V'
// 高度(5桁想定)
var pAltStr = SafeSlice(line, 25, 5);
var gAltStr = SafeSlice(line, 30, 5);
var latDeg = ParseDegMin(latDegStr, latMinStr, ns == 'S');
var lonDeg = ParseDegMin(lonDegStr, lonMinStr, ew == 'W');
var pAlt = TryParseInt(pAltStr, 0);
var gAlt = TryParseInt(gAltStr, pAlt);
p = new TrackPoint
{
hour = hh,
minute = mm,
second = ss,
latDeg = latDeg,
lonDeg = lonDeg,
pressureAltM = pAlt,
gpsAltM = gAlt,
fixValid = (fix == 'A')
};
return true;
}
catch (Exception e)
{
Debug.LogWarning($"B行のパースに失敗: {e.Message} {line}");
return false;
}
}
3Dマップのメッシュの作成方法
3Dマップのメッシュの生成方法はかなり悩みました。
一番時間がかかった作業かもしれません。
-
PLATEAU
最初はPLATEAUを使用する予定でした。
ですが、CityGML(3D都市モデルデータ)のない地域のメッシュを作成する方法がすぐに分からず、一旦スキップしました。 -
国土地理院データ
数値標高モデルのダウンロード→ファイルの変換→最適化などを試みました。
ですが、十数km先にある山も描画したいとなると、LODなどをかなり頑張る必要があったためこちらも一旦スキップしました。
↓描画したい山々

-
Cesium for Unity + Photorealistic 3D Tiles
これがめちゃくちゃ便利でした。
3Dの地理空間アプリを作成するためのプラットフォームで、Cesium ion経由でBing MapsやGoogle Mapsなどのソースからいい感じにマップを作成してくれます。
また、3D tilesなども読めるようです。
画像のように、Cesium GeoreferenceのOrigin(緯度・経度・高さ)を指定するだけで、そこをSceneの原点としたマップを表示してくれます。
また、Scene CameraやMain Cameraの描画範囲の設定を元に、いい感じに必要なマップを取得してくれます。

軌跡の補間
BレコードのGPSの情報を線形的に補間するだけでは、再生時にガタガタしてしてしまいました。
そこで、良い感じな補間のアルゴリズムを模索してみました。
- Catmull-Rom Spline
BレコードのGPSの情報を制御点として、Catmull-Rom Splineで補間してみました。
ですが、GPSの誤差のためか、かなり揺れが大きい曲線となってしまい、実際のフライトの動画と大きく異なるものとなってしまいました。 - B-spline
完全なトレースを諦めて、制御点を必ずしも通らないB-splineで補間してみました。
これはかなりそれっぽい見た目になってくれて、実際のフライト動画に近い軌跡を得ることができました。
VR対応
これで合っているんか?と思いながら、Unity OpenXR Metaのプラグインを導入しました。
思いの外すんなりとVR対応ができました。
便利な世の中です。
所感
実際のフライトにかなり近いVR体験を実現することができました。
高度感・距離感が現実に近く、VRならではのメリットを感じました。
ですが、ターンなどプレイヤーの意図しない回転が急に入るとかなり酔います。
軌跡を表示しておくことで多少は軽減される印象でしたが、没入感がかなり減ってしまうため、もうひと工夫あると良いかなと思いました。
使用できそうな用途
初飛び前に動画を見てイメトレをすることがよくあります。
ですが、キールの後ろ(機体後部)などに固定されたカメラからのアングルしか見ることができないため、実際のフライト体験とかなり差があります。
動画を見るよりかは圧倒的に実際のフライト体験と近かったため、初飛び前のイメトレの置き換え先になり得そうです。
課題と展望
今回は、Cesium for Unity + Photorealistic 3D Tilesでマップを作成しました。
この手法ではストリームされたマップしか使用できなかったり、ライセンス周りの制約が多かったりと何かと大変です…
理想的には商用利用もできて欲しいので、この辺りの問題をうまく解決したいです。
IGCファイルを再生できることは確かに面白いですが、ここまで来るとこのままフライトシミュレーターとして完成させたいという気持ちが強くなりました。
そこで課題となってくるのは、既存のフライトシミュレーターとの差別化です。
自分のホームエリアのLDを精細に再現したり、VRChat等のプラットフォームに導入したりと十分に差別化を図れる箇所があるので、開発のモチベが高く保てそうです。


