はじめに
本記事は QualiArts Advent Calender 2019 24日目の記事になります。
昨日は @ogrszk の 「Spineでできること〜エディタのTipsとAPI使用例〜」でした
Unity2019になり、UniversalRenderPipelineなど新機能が追加されましたが、その中の一つであるVFXGraphを使用して星空を描画してみました
注意:
- 天文学全くわからない素人なので厳密な計算とかは分からないです
- 新機能の基本的な使用方法は割愛しています
環境
- Unity2019.3.0b11
- UniversalRP 7.1.5
目次
星のデータを用意
NASAに様々な観測データが用意されているのでそちらをお借りしました
https://heasarc.gsfc.nasa.gov/cgi-bin/W3Browse/w3catindex.pl#STAR%20CATALOG
PriorityがHIGHのものからデータ量の多い(約12万レコード) "Hipparcos Main Catalog" を使用しました
今回使用するのは等級 (vmag)、経度 (ra_deg)、緯度 (dec_deg)、色温度 (bv_color)なのでチェックを入れて出力します
※上記のサイトでは赤緯(dec)、赤経(ra)の代わりにパースしやすい(ra_dec, dec_deg)が用意されているのでそちらを使用しています
出力されたテキストファイルはこんな感じになります
後ほどUnity上で扱いやすいように変換を行います
Results from heasarc_hipparcos: Hipparcos Main Catalog Coordinate system: Equatorial |vmag |ra_deg |dec_deg |bv_color| | 7.84|149.16726051|-89.78245385| 0.097| | 6.82|218.87831588|-89.77169600| 1.698| ...
プロジェクトの作成
作成時にUniversalRPのテンプレートが用意されているのでそちらを選びます。
今回は大量の星を描画するためにVisualEffectも導入します
星は特に動かないので自前で生成するのもいいですが、新機能を使いたい+メッシュ制御面倒なので利用します。
星描画に使うPointCacheを生成
VisualEffectで座標を指定する方法は32bitテクスチャを直接使う方法もありますが、
PointCacheという仕組みを使用すれば内部で自動的にテクスチャ化してくれるのでそちらを利用します
テキストからMesh化するプログラム
ダウンロードしたテキストファイルは "|" で区切られているのでパースしながら任意の型に成形します
※StarCatalog内には一部情報が不足しているデータも存在しているので除外の必要あり
明るさ
明るさはvmagパラメータを利用します。
astro-dic.jp
人間の目は6,7等星程度しか見えないらしいので6等星の出力値を仮に1と置いてスケール値を計算します
色
色はbv_colorパラメータを利用します
astro-dic.jp
このままではRGBとして扱えないため以下の変換を参考にしました
stackoverflow.com
上記の変換を踏まえソースコードを載せておきます 例外処理は無いです
public struct StarData { public Vector3 position; public Color color; public float magnitude; public float intensity; } [MenuItem("Tools/Convert Star Mesh")] public static void ConvertMesh() { var path = EditorUtility.OpenFilePanel("Star Catalog", Application.dataPath, ""); if (string.IsNullOrEmpty(path)) return; var meshPath = EditorUtility.SaveFilePanelInProject("Save Mesh", "StarMesh", "asset", "Save"); if (string.IsNullOrEmpty(meshPath)) return; using (var reader = new System.IO.StreamReader(path)) { bool initialized = false; var indexTable = new Dictionary<string, int>(); List<string>[] data = null; while (reader.EndOfStream == false) { var str = reader.ReadLine(); if (string.IsNullOrEmpty(str)) continue; // | が先頭に来るまでスキップ if (str[0] != '|') continue; var param = str.Split('|'); if (initialized) { for (int i = 1; i < param.Length - 1; i++) { var value = param[i].Trim(); data[i - 1].Add(value); } } else { // 初回 // 両端は無視 data = new List<string>[param.Length - 2]; for (int i = 1; i < param.Length - 1; i++) { var key = param[i].Trim(); indexTable.Add(key, i - 1); data[i - 1] = new List<string>(); } initialized = true; } } if (data == null) return; var vmagIndex = indexTable["vmag"]; var ra_degIndex = indexTable["ra_deg"]; var dec_degIndex = indexTable["dec_deg"]; var bv_colorIndex = indexTable["bv_color"]; List<StarData> stars = new List<StarData>(data[vmagIndex].Count); for (int i = 0; i < data[vmagIndex].Count; i++) { if (float.TryParse(data[ra_degIndex][i], out var ra_deg) && float.TryParse(data[dec_degIndex][i], out var dec_deg) && float.TryParse(data[vmagIndex][i], out var vmag) && float.TryParse(data[bv_colorIndex][i], out var bv_color)) { var intensity = Mathf.Pow(10f, (6f - vmag) / 2.5f); // 全パラメータ存在する stars.Add(new StarData { position = Quaternion.Euler(dec_deg, ra_deg, 0f) * Vector3.forward, magnitude = vmag, intensity = intensity, // ソート用 color = bv2rgb(bv_color) * intensity // 最終的な色を計算 }); } } // 明るさ順にソート stars.Sort((x, y) => (y.intensity - x.intensity) > 0f ? 1 : -1); var mesh = new Mesh(); mesh.name = Path.GetFileNameWithoutExtension(path); mesh.SetVertices(stars.Select(x => x.position).ToArray()); mesh.SetColors(stars.Select(x => x.color).ToArray()); AssetDatabase.CreateAsset(mesh, meshPath); AssetDatabase.SaveAssets(); } }
明るい星から順に並べ替えることで描画量を制御できるようにしておく
MeshからPointCacheAssetを作成
Window->VisualEffects->Utilities->PointCacheBakeTool
標準で作成するためのツールが用意されているのでこちらを利用
星のテクスチャを作成
適当に丸を書いて保存
VFXGraph
VFXGraphはUnityが開発したエフェクトシステムで更新処理をComputeShaderに任せることにより、大量のパーティクルを高速に実行することが可能
モバイルでもComputeShader自体は動く端末が増えたが、動かない処理があったりするので慎重に使う必要あり
unity.com
Spawn
パーティクルを発生させるためにSpawn内にBlockを作成する必要がある
星は少しずつ出すのではなく最初に大量に1度だけ出したいのでSingleBurstBlockを使う
生成量を頂点数分(116812個)指定しているが1000等にすれば明るい星から順に生成される
PointCacheNode
先ほど作成したPointCacheAssetを指定するNode
使い方は簡単で先程頂点と頂点カラーを出力しているのでそのまま
- SetPosition from Map
- SetColor from Map
のAttribute Mapに接続する
VFXGraphでは〜 from Map というNode名で様々なパラメータに適用することができる
今回はIndex0から順に描画したいのでSequentialを選択
AttributeMapをよく見るとテクスチャが表示されていて上から順に明るい色が格納されていることが確認できるので中身は普通のテクスチャだということが分かります
GetAttributeNode: color
GetAttributeNodeはパーティクル毎の各種数値を取得することができるNode
色の輝度に合わせてサイズを変えたほうが綺麗だったので適用
以下の画像のようにNodeを接続してSetSizeBlockに接続するだけ
Output Particle Quad
ポリゴンとして出力するNode
作成したテクスチャを指定してカメラに向けるBlockだけ追加
VFXGraph全体
特に動かしたりとかはしていないのでとてもシンプルになっています
結果
全体像
ポストエフェクトで輝度を上げたときの見た目
レンダリング
UnityRecorderはめちゃくちゃ便利なので綺麗に画を出したいときは必ず使ったほうがいいです
星が小さいため高解像で描画しないとブルームが綺麗に乗らないので8Kレンダリングしました
EXRファイルの容量が200MBもありましたが、、
まとめ
- 極力プログラムを書かずに描画までうまくいったかと思います
- 宇宙ヤバイ
- 用語が全部専門すぎる
- 正直ランダムに配置しても誰も気づかなそう