Unreal Engine 5とpythonで星空を作る
blenderで星空を作ったものの、UE5で使用するには使い勝手が悪かったのでUnreal Pythonを使用してUE5で星空を作ってキューブマップにした話
1.目標
- 9000個の星を生成
- CubeMapテクスチャ作成
- 天球用Sphereのマテリアル作成
2.結果
#UE5
— 縦長おじさん (@tatenagaOG3) 2022年11月6日
Unreal Pythonで9000個位のSphereを使った星空を作った。早くて軽くて綺麗・・・ pic.twitter.com/0jRJKOUTI1
3.pythonコード
とりあえず動くレベルの汚ソースです
# BSC5を使用してUE5に星を配置するスクリプト import unreal import math CATALOG_PATH = "D:\\BlenderProjects\\starcatalog\\bsc5.dat" # 見かけの等級下限 6.xxまでの星を収録している。0に近づけるほど表示は少なくなる MAX_VMAG = 7.0 # 天球の半径 18kmに設定してみた CELESTIA_RADIUS = 18000* 100 # 0等級の基準となる星のscale BASE_STAR_SCALE = 100 def get_right_ascension(hour:int, min:int, sec:float): """h,m,sから赤経をradianで返す :param 1hour = 15 :param 1min = 15/60 :param 1sec = 15/3600 :return RA radians """ ra_deg = hour * 15 + (min * 15 / 60) + (sec * 15 / 3600) return math.radians(ra_deg) def get_declination(deg:int, arcmin:int, arcsec:int): """deg,arcmin,arcsecから赤緯をradianで返す :param 1deg = 1 :param 1arcmin = 1/60 :param 1arcsec = 1/3600 :return DEC radians """ dec_deg = deg + (arcmin * 1 / 60) + (arcsec * 1 / 3600) return math.radians(dec_deg) def get_visual_radius(vmag:float): """vmagの値から0等級の球に対するscale比率を返す :param vmag vmag :return scale_ratio 基準とする球に対するスケールを返す """ if (vmag > 0): scale = BASE_STAR_SCALE / math.sqrt(math.pow(10, 2/5 * vmag)) elif(vmag <= 0): scale = BASE_STAR_SCALE * math.sqrt(math.pow(10, 2/5 * (0 - vmag))) elif(vmag == 0): scale = BASE_STAR_SCALE return scale def create_SphereActor(label:str, locationX:float, locationY:float, locationZ:float, scale:float): """Sphereを生成・配置 :label outlinerに表示される名前 :location XYZ root_componentの座標(cm) :scale Sphereの大きさ (直径100cm * scale) """ # EALを使用してベーシックの球体を選択 EAL = unreal.EditorAssetLibrary() asset = EAL.find_asset_data('/Engine/BasicShapes/Sphere.Sphere') sphereObj = asset.get_asset() # EASで新しいアクターをワールドに配置 EAS = unreal.EditorActorSubsystem() locations = unreal.Vector(locationX, locationY, locationZ) newActor = EAS.spawn_actor_from_object(object_to_use=sphereObj, location=locations, transient=False) # 配置したアクター名の変更 newActor.set_actor_label(label) # 配置したアクターのサイズ変更 rootComp = newActor.get_editor_property("root_component") newScale = unreal.Vector(scale, scale, scale) rootComp.set_editor_property("relative_scale3d", newScale) return newActor def set_New_Material(actor:unreal.Actor, specTpye:str): """SMアクターのマテリアルをMK分類に基づいたマテリアルに変更する :actor create_SphereActorで作成したアクター :specType MK分類 [O, B, A, F, G, K, M]のいずれかを指定 """ EAL = unreal.EditorAssetLibrary() assetpath = '' if(specTpye == 'O'): assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_O_Type_000.MI_O_Type_000' elif(specTpye == 'B'): assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_B_Type_000.MI_B_Type_000' elif(specTpye == 'A'): assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_A_Type_000.MI_A_Type_000' elif(specTpye == 'F'): assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_F_Type_000.MI_F_Type_000' elif(specTpye == 'G'): assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_G_Type_000.MI_G_Type_000' elif(specTpye == 'K'): assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_K_Type_000.MI_K_Type_000' elif(specTpye == 'M'): assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_M_Type_000.MI_M_Type_000' else: assetpath = '/Game/Developers/raku5/Collections/Blueprints/Materials/MI_O_Type_000.MI_O_Type_000' newMaterial = EAL.find_asset_data(assetpath).get_asset() actor.get_editor_property("static_mesh_component").set_material(0, newMaterial) def main(): with open(CATALOG_PATH, 'r', encoding='UTF-8') as rf: for line in rf: try: vmag = float(line[102:107]) except: continue if (vmag > MAX_VMAG): continue # 配置に必要な情報をスライス hr = int(line[0:4]) ra_hours = int(line[75:77]) ra_minutes = int(line[77:79]) ra_seconds = float(line[79:83]) dec_sign = line[83] dec_degrees = int(line[84:86]) dec_arcmin = int(line[86:88]) dec_arcsec = int(line[88:90]) spectral_type = line[129] # 配置に必要な情報に加工 star_name = f'HR.{hr}' ra_rad = get_right_ascension(ra_hours, ra_minutes, ra_seconds) dec_rad = get_declination(dec_degrees, dec_arcmin, dec_arcsec) star_location_x = CELESTIA_RADIUS * math.cos(dec_rad) * math.cos(ra_rad) # UEの座標系による -1 star_location_y = CELESTIA_RADIUS * math.cos(dec_rad) * math.sin(ra_rad) * -1.0 star_location_z = CELESTIA_RADIUS * math.sin(dec_rad) if (dec_sign == '-'): star_location_z *= -1.0 star_scale = get_visual_radius(vmag) newStar = create_SphereActor(star_name, star_location_x, star_location_y, star_location_z, star_scale) set_New_Material(newStar, spectral_type) print('Create Success')
4.メモ
blenderで星空を作った時のコードを流用。Unreal Engineで使う際に変更した点
- Y軸の方向が違うので修正
- Sphereの大きさがscaleで表現されているので、それに合わせた修正
マテリアルの作成
unlit
な星用マテリアルを作成してMK分類7種分のマテリアルインスタンスを作成。調整用にパラメータを用意した。
Cubemapの作成
Cube Render Target
を作成してワールドに配置すると周囲をキャプチャしてくれる。
Mip Gen Settings
をNoMipmapsに、Size X
初期値512では解像度が足りなかったので最終的には2048に設定した。
Create Static Texture
でテクスチャ化できる
skydome用マテリアルの作成
[ue4 skydome]で検索してヒットした動画を参考にして、用意したCubemapを使用、地軸の傾き用パラメータと回転用のパラメータを作成した。
5.感想
blenderでは見た目等級を5にして星が2000個を超えたあたりで、処理時間を待てず離席して休憩するレベルだったが、UE5は見た目等級を最大にして約9000個の星を生成しても処理時間は1分も掛からなかったのには驚き。 ポストプロセスで星をキラキラさせたりも軽かった。
星空作るのに約1週間blender と unreal engine のpythonで遊んだが、何かオリジナリティーのある事したいなぁ・・・面倒な作業といえばRig周り作業、リターゲティングとかもpythonでサクッとできたらいいことあるかも?