C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

Blender Scripting: head と tail の座標と適当なフィルター条件でボーンの親子関係を自動構築する `connect-bones` の作り方と Add-on 化の方法

こんにちは、Python初心者∧Blender初心者です。フィーリングやマウスぽちぽちよりパキッとコードで処理する方が得意なので、 un-h002; Atroposcluster 対応で私自身に追加実装された Blener Scripting を入門講座を兼ねて整理します💪

Note: 読者の想定=PythonまたはBlenderは初心者Lv1くらいだけど、他に何かしらのプログラミング言語のLv25くらいの使い手の方(但しLv値はよくある1..99を基本想定とした古典的JRPGくらいの感覚値)で、必要な予備知識はAPI Referenceがあれば読みに行ける方。

準備するもの: bone が親子関係なしでたくさん入っていて、一部の bone は head と tail が同じ座標にあって親子関係で connected にできる Blender の作業状態。またはこのメモの学習用に Connected な親子関係のボーンを含んだデータを Blender ですべての armature を選択して親子関係を clear した作業状態。例えば セシル変身アプリ で作った VRM を前回のメモのように Blender へ Import するとそういう状態になるかもしれません。

もくじ

  1. はじまりの Blender Scripting
  2. すべての bone を列挙
  3. 列挙された bone 群をフィルター
  4. head と tail が同じ座標の bone 組の抽出
  5. 2つの bone 組の「選択」
  6. Blender 組み込み機能の Armature: Make Parent を呼ぶ
  7. かんせいの connect-bones Blender Scripting → Add-on 化

1. はじまりの Blender Scripting

  1. Blender上部のタブを Scripting へ切り替え、
  2. New Text して、
  3. 適当な名前を付けて
  4. 動作確認程度のコードを書いて
  5. Armature を Edit Mode で選択して (†後でここもコードにできます✨)
  6. Run Script

f:id:USAGI-WRP:20200412104746p:plain

import bpy # Blender API

print( bpy ) # You can output to STDOUT anything!
print( "Let's start", 123.45, 'Blender Scripting!' ) # You can concatenate a string and any object with a comma.
# In this world, you can use the `#` for a line commenting. `//` is not work.

Note: Blender-2.82@Windows 現在の Scripting のテキストエディターやSTDOUTの出力先の System Console Window は UNICODE 表示にデフォルトでは対応していないので非ASCIIをコードに使わないようにしました。

print による STDOUT の結果は F3 ->Wm: Toggle System Console` などすると表示されるシステムコンソール窓で確認できます。

<module 'bpy' from 'C:\\Program Files\\Blender Foundation\\Blender 2.82\\2.82\\scripts\\modules\\bpy\\__init__.py'>
Let's start 123.45 Blender Scripting!

こんな表示が Run Script で出れば Blender Scripting の Lv1 はクリアーです。おめでとうございます🎉

Lv1 をクリアーした報酬として BlenderAPI Documentation を進呈します:

今後の Blender Scripting ライフにお役立て下さい💪

2. すべての bone を列挙

bpy (たぶん Blender-PYthon とかそういう命名由来でしょう…) オブジェクトのメンバーを読んだり呼んだりすると、たいていの Blender の管理オブジェクトや組み込み機能の呼び出しを行えます。

例えば:

import bpy
from itertools import chain

armatures = bpy.data.armatures
bones     = list( chain( *[ a.edit_bones for a in armatures ] ) )

for b in bones:
 print( b, ' { head=', b.head, ' tail=', b.tail, '}' )

こう↑して Run Script すると↓

<bpy_struct, Bone("NekoMimiBone_L")>  { head= <Vector (0.0699, 0.2111, 0.0399)>  tail= <Vector (0.0825, 0.2277, 0.0401)> }
<bpy_struct, Bone("NekoMimiBone_L_001")>  { head= <Vector (0.0825, 0.2277, 0.0401)>  tail= <Vector (0.1012, 0.2548, 0.0416)> }
<bpy_struct, Bone("NekoMimiBone_L_002")>  { head= <Vector (0.1012, 0.2548, 0.0416)>  tail= <Vector (0.1210, 0.2814, 0.0430)> }
<bpy_struct, Bone("NekoMimiBone_L_003")>  { head= <Vector (0.1210, 0.2814, 0.0430)>  tail= <Vector (0.1408, 0.3081, 0.0444)> }
<bpy_struct, Bone("Hair")>  { head= <Vector (-0.0000, 0.1213, 0.0039)>  tail= <Vector (-0.0000, 0.0988, 0.0286)> }
<bpy_struct, Bone("Hoho_R")>  { head= <Vector (-0.0817, 0.0463, 0.0867)>  tail= <Vector (-0.1030, 0.0584, 0.1093)> }
<bpy_struct, Bone("Hoho_L")>  { head= <Vector (0.0817, 0.0463, 0.0867)>  tail= <Vector (0.1030, 0.0584, 0.1093)> }
<bpy_struct, Bone("Ago")>  { head= <Vector (-0.0000, 0.0127, 0.0660)>  tail= <Vector (-0.0000, 0.0190, 0.0987)> }
<bpy_struct, Bone("Chest")>  { head= <Vector (-0.0000, -0.1039, 0.0311)>  tail= <Vector (0.0000, 0.0702, -0.0152)> }
<bpy_struct, Bone("Mune_R")>  { head= <Vector (-0.0940, -0.1429, 0.1315)>  tail= <Vector (-0.0521, -0.1654, 0.0071)> }
<bpy_struct, Bone("Mune_R_001")>  { head= <Vector (0.0000, 0.0000, 0.0000)>  tail= <Vector (-0.0000, 0.0333, -0.0000)> }
<bpy_struct, Bone("Mune_L")>  { head= <Vector (0.0940, -0.1429, 0.1315)>  tail= <Vector (0.0521, -0.1654, 0.0071)> }
<bpy_struct, Bone("Mune_L_001")>  { head= <Vector (0.0000, 0.0000, 0.0000)>  tail= <Vector (0.0000, 0.0333, -0.0000)> }
<bpy_struct, Bone("Koshi")>  { head= <Vector (-0.0000, 0.0000, 0.0000)>  tail= <Vector (-0.0000, 0.0100, 0.0000)> }

↑こんな具合の出力を得られます。

例を読んだ時点で既に本項の目的を達成していましましたね!🎉

素晴らしい学習能力を発揮中で知識を吸い込みたい盛りのあなたがLv2をクリアーした記念に:

この↑ Bone 型のリファレンスマニュアルを進呈します。 headtail が何かも書いてあるよ✨

3. 列挙された bone 群をフィルター

ところで、先程のコードに [ ] で囲まれたリストをコードで生成している部分がありました。あれが Python 言語の特徴的な言語機能の1つとして有名な "リスト内包表記" でした✨

リスト内包表記ではリストへ入れたい何かを、

  • 他のリストを for で列挙しつつ
  • if で条件を付けたり、
  • 取り出した結果そのものではなく加工を加えたオブジェクトにして

新たなリストを作成する、そんなような事ができる事を既に知りました。そこで、例えば:

import bpy
from itertools import chain

armatures = bpy.data.armatures
bones     = list( chain( *[ a.edit_bones for a in armatures ] ) )

filtered_bones = [ b for b in bones if b.name.startswith( 'NekoMimiBone' )  ]

for b in filtered_bones:
 print( b, ' { head=', b.head, ' tail=', b.tail, '}' )

こんな↑具合で前項の例に1行 filtered_bones を "NekoMimiBone" で名前が始まる bone だけを抽出して作るように追加して、bones に替えて filtered_bonesprint してみると:

<bpy_struct, Bone("NekoMimiBone")>  { head= <Vector (-0.0000, 0.1213, 0.0039)>  tail= <Vector (-0.0000, 0.2111, 0.0399)> }
<bpy_struct, Bone("NekoMimiBone_R")>  { head= <Vector (-0.0699, 0.2111, 0.0399)>  tail= <Vector (-0.0825, 0.2277, 0.0401)> }
<bpy_struct, Bone("NekoMimiBone_R_001")>  { head= <Vector (-0.0825, 0.2277, 0.0401)>  tail= <Vector (-0.1012, 0.2548, 0.0416)> }
<bpy_struct, Bone("NekoMimiBone_R_002")>  { head= <Vector (-0.1012, 0.2548, 0.0416)>  tail= <Vector (-0.1210, 0.2814, 0.0430)> }
<bpy_struct, Bone("NekoMimiBone_R_003")>  { head= <Vector (-0.1210, 0.2814, 0.0430)>  tail= <Vector (-0.1408, 0.3081, 0.0444)> }
<bpy_struct, Bone("NekoMimiBone_L")>  { head= <Vector (0.0699, 0.2111, 0.0399)>  tail= <Vector (0.0825, 0.2277, 0.0401)> }
<bpy_struct, Bone("NekoMimiBone_L_001")>  { head= <Vector (0.0825, 0.2277, 0.0401)>  tail= <Vector (0.1012, 0.2548, 0.0416)> }
<bpy_struct, Bone("NekoMimiBone_L_002")>  { head= <Vector (0.1012, 0.2548, 0.0416)>  tail= <Vector (0.1210, 0.2814, 0.0430)> }
<bpy_struct, Bone("NekoMimiBone_L_003")>  { head= <Vector (0.1210, 0.2814, 0.0430)>  tail= <Vector (0.1408, 0.3081, 0.0444)> }

おめでとうございます🎉 今回も例を読んだだけで目的を達成してしまいましたね。Lv3のクリアーの記念に:

"Python-3.7" の公式チュートリアルの "§5.1.3. リスト内包表記" を進呈します💪 執筆現在の Python そのものは 3.8 が最新版ですが、 Blender-2.82 に組み込まれた Python は 3.7.4 です。リファレンスを参照する時は自分が扱う言語処理系のバージョンにも注意しましょう。

Note: ちなみに、ぐぐるとリスト内包表記は何なのか程度の"入門情報"はたくさんの野良解説が出てくると思います。Pythonの言語の入門書籍でも紹介されているでしょう。"Must"や"Should"と言うほどお節介ではありませんが、たいていのプログラミング言語の"基礎的な何か"は言語公式の資料を第一に参照すると書き間違いも滅多にありませんし、必要な事は必ず書いてありますから合理的でいいですよ👍

4. head と tail が同じ座標の bone 組の抽出

少し複雑さが増加しそうなので、リスト内包表記やワンライナーを無理に作ろうとせず、そろそろ関数を定義して数年後の自分でも見た瞬間に読めるように書きます:

import bpy
from itertools import chain

armatures = bpy.data.armatures
bones     = list( chain( *[ a.edit_bones for a in armatures ] ) )

filtered_bones = [ b for b in bones if b.name.startswith( 'NekoMimiBone' )  ]

def find_connectable_pair( parent ):
 for candidate in filtered_bones:
  if parent.tail == candidate.head:
   return ( parent, candidate )

connectable_pairs = [ p for p in map( find_connectable_pair, filtered_bones ) if p != None ]

for p in connectable_pairs:
 print( 'Parent:', p[0], ' <==/connectable/==< Child:', p[1] )

map は言語組み込みの元になるリストの要素群を任意のファンクターで射影変換した要素群から作成したリストを得る Python 言語のリスト処理用に組み込まれた関数です。また、 Python では [ ] はリスト、 ( ) はタプルになります。💪

Note: Python では def で関数を定義できます。いままで触れていませんでしたが、 Python の「らしい」言語機能のもう1つの大きな特徴のインデント(indent)/デデント(dedent)によるブロック構造の生成を前項までの最後の forprint の組み合わせでも書いてありました。 defif も同様に : の後の行で indent = { から dedent = } まで同じインデントレベルの連続した複数の行が1つのブロックとして処理されます。また、 Python では関数の return が未定義の場合は None が還ります。

↑これを実行すると↓

Parent: <bpy_struct, Bone("NekoMimiBone_R")>  <==/connectable/==< Child: <bpy_struct, Bone("NekoMimiBone_R_001")>
Parent: <bpy_struct, Bone("NekoMimiBone_R_001")>  <==/connectable/==< Child: <bpy_struct, Bone("NekoMimiBone_R_002")>
Parent: <bpy_struct, Bone("NekoMimiBone_R_002")>  <==/connectable/==< Child: <bpy_struct, Bone("NekoMimiBone_R_003")>
Parent: <bpy_struct, Bone("NekoMimiBone_L")>  <==/connectable/==< Child: <bpy_struct, Bone("NekoMimiBone_L_001")>
Parent: <bpy_struct, Bone("NekoMimiBone_L_001")>  <==/connectable/==< Child: <bpy_struct, Bone("NekoMimiBone_L_002")>
Parent: <bpy_struct, Bone("NekoMimiBone_L_002")>  <==/connectable/==< Child: <bpy_struct, Bone("NekoMimiBone_L_003")>

今回も簡単に目的を達成できました✨ 余力がみなぎっていかもしれないので、こちら↓をLv4のクリアーの記念に進呈します。ご活用下さい:

↑は "Python-3.7" 言語リファレンスの "§2.1.8. インデント" です💪 言語リファレンスはチュートリアルよりもカチッと規格の解説らしい文書なので、じっくり整理しながら読む必要のある部分も少なくありませんが、言語機能の中枢を成す基礎的な部分、例えばインデントとデデントによるコードブロックの生成の正確な情報、空白文字なら何でも良いのか、タブとスペースが混ざっているとどうなるのか、そういった情報について言語規格に基づいた間違いの(たいていは)無い正確な情報を確認できます。言語機能の基礎的な要素について気になる事があれば、はじめに言語リファレンスを参照すると不安の無い確実な情報が(たぶん)取得できて便利です✨

5. 2つの bone 組の「選択」

ここまでは Blender API を通して Blender が管理中のオブジェクトについて read 的なアクセスでした。そろそろ write 的な事をしてみましょう。世界は"副作用"で"進んで"います:

import bpy
from itertools import chain

armatures = bpy.data.armatures
bones     = list( chain( *[ a.edit_bones for a in armatures ] ) )

filtered_bones = [ b for b in bones if b.name.startswith( 'NekoMimiBone' )  ]

def find_connectable_pair( parent ):
 for candidate in filtered_bones:
  if parent.tail == candidate.head:
   return ( parent, candidate )

connectable_pairs = [ p for p in map( find_connectable_pair, filtered_bones ) if p != None ]

for p in connectable_pairs:
 print( 'Parent:', p[0], ' <==/connectable/==< Child:', p[1] )
 p[0].select = True
 p[1].select = True

↑最後の2行が増えただけですが、これまでにない大きな違いがあります。このコードでは最後の2行でスクリプトの外の Blender の世界の値を書き換えています。実行後に Edit Mode で今回操作対象のネコミミのあたりを見ると:

f:id:USAGI-WRP:20200412104948p:plain

ネコミミ部分の Connected な親子関係を見るからに構築できそうなボーン群が選択状態になりましたね!このスクリプトは実行によりスクリプトの外の Blender の世界に副作用を引き起こす事に成功しました。おめでとうございます🎉

ちなみに↑で EditBoneselect プロパティーの仕様を確認できます✨ Blender API には多くの "操作" が bpy.ops にメソッドとして提供されていて、例えば bpy.ops.armature.select_all のような使い所次第では便利な機能もあります。一方で、今回使った EditBoneselect プロパティーのように、直接変数の値を書き換えればよい場合もあります💪

6. Blender 組み込み機能の Armature: Make Parent を呼ぶ

既に bpy.ops.armature.parent_set を呼ぶだけでしょ、と勘付いているかもしれません。その通りです💪 その通りではあるのですが、きちんと API Reference を見てから使わないと使えません。

ついで、そろそろ仕上げも近づいているので、スクリプトの実行前に手作業で Edit Mode へ切り替えておく必要も無いように bpy.ops.object.mode_set も組み込んでしましょう:

import bpy
from itertools import chain

bpy.ops.object.mode_set(mode='EDIT')

armatures = bpy.data.armatures
bones     = list( chain( *[ a.edit_bones for a in armatures ] ) )

filtered_bones = [ b for b in bones if b.name.startswith( 'NekoMimiBone' )  ]

def find_connectable_pair( parent ):
 for candidate in filtered_bones:
  if parent.tail == candidate.head:
   return ( parent, candidate )

connectable_pairs = [ p for p in map( find_connectable_pair, filtered_bones ) if p != None ]

for p in connectable_pairs:
 print( 'connect-bones:', p[0].name, '<-', p[1].name )
 p[1].select = True
 p[0].select = True
 armatures[0].edit_bones.active = p[0]
 bpy.ops.armature.parent_set()
 p[0].select = False
 p[1].select = False

for p in connectable_pairs:
 p[1].select = True
 p[0].select = True

GUIで操作する場合には「先っぽボーン」→「根本ボーン」の順序で「選択」して Armature: Make Parent すれば期待動作するこの機能ですが、 bpy.ops.armature.parent_setスクリプトで使う場合は「選択」だけでなく「アクティブ状態」も意識的に操作を記述する必要があります。GUI操作では最後にぽちっと選択したボーンが「選択状態」かつ「アクティブ状態」になりますが、スクリプトでは「選択」と「アクティブ化」のコードはそれぞれ別に記述する機能として API が提供されています💪

加えて、GUI操作と同様の点として、複数の親子関係の構築をする場合には、先に親子関係を構築したボーンは選択を解除して、それから次の親子関係の選択を行う必要があります。

なお、今回は bpy.ops.object.mode_set(mode='EDIT') も加えたので、例えば Pose ModeRun Script しても自動的に Edit Mode になり、スクリプトが期待動作するようになっています💪

さらに、おまけ機能として親子関係の構築をすべて行った後、もう一度、親子関係のタプルをトラバースして親子関係を構築した(はず)のボーン、つまり変更があったボーン群を「選択」した状態にしています。システムコンソールのログを読まなくてもGUIで視覚的にどこがくっついたのかわかりやすくする工夫です。

f:id:USAGI-WRP:20200412105016p:plain

実行すると "NekoMimiBone" で始まるボーン群について、いまは互いにばらばらで親子関係は無いけれど tail と head が同じ座標で重なっている bone の組をすべて見つけて Connected な親子関係を構築してくれます✨ Connected なので、繋がったボーンの1つを適当に選択して G で動かすなどすると繋がったボーンも影響を受けて回転したり拡縮したり追従するようになっています:

f:id:USAGI-WRP:20200412105547p:plain

7. かんせいの connect-bones Blender Scripting -> Add-on 化✨

↑までで目的を達成するスクリプトを手書きで実行できるようになりました✨ せっかくなので Add-on にしてみます:

チュートリアルを参考にもにょもにょ書いて試して修正して:

### The code for add-on ecosystem ###

bl_info = {
 'name'    : 'connect-bones',
 'blender' : ( 2, 80, 0 ),
 'category': 'Armature',
 'author'  : 'USAGI.NETWORK / Usagi Ito',
 'version' : ( 0, 0, 0 ),
 'support' : 'TESTING',
}

addon_keymaps = []

import bpy
from itertools import chain
from re import compile

class ConnectBones( bpy.types.Operator ):
 '''Connect bones if it find a bone pair that has a same position of a head and a tail'''
 bl_idname  = 'armature.connect_bones'
 bl_label   = 'Connect bones'
 bl_options = { 'REGISTER', 'UNDO' }
 
 name_regex: bpy.props.StringProperty( name = 'name_regex', default = '.*' )
 
 def execute( self, context ):
  return connect_bones( self.name_regex )

def register():
 bpy.utils.register_class( ConnectBones )
 bpy.types.VIEW3D_MT_object.append( menu )
 wm = bpy.context.window_manager
 kc = wm.keyconfigs.addon
 if kc:
  km = wm.keyconfigs.addon.keymaps.new( name = '3D View', space_type = 'VIEW_3D' )
  kmi = km.keymap_items.new( idname = ConnectBones.bl_idname, type = 'C', value = 'PRESS', ctrl = True, shift = True, alt = True )
  kmi.properties.name_regex = '(?!)'
  addon_keymaps.append( ( km, kmi ) )

def unregister():
 bpy.utils.unregister_class( ConnectBones )
 for km, kmi in addon_keymaps:
  km.keymap_items.remove(kmi)

def menu( self, context ):
 self.layout.separator()
 self.layout.operator( ConnectBones.bl_idname )
 addon_keymaps.clear()

if __name__ == '__main__':
 register()

### The main code of the add-on ###

def connect_bones( name_regex ):
 bpy.ops.object.mode_set(mode='EDIT')
 
 armatures = bpy.data.armatures
 bones     = list( chain( *[ a.edit_bones for a in armatures ] ) )
 
 regex = compile( name_regex )
 
 filtered_bones = [ b for b in bones if regex.match( b.name ) ]
 
 def find_connectable_pair( parent ):
  for candidate in filtered_bones:
   if parent.tail == candidate.head:
    return ( parent, candidate )
 
 connectable_pairs = [ p for p in map( find_connectable_pair, filtered_bones ) if p != None ]
 
 for p in connectable_pairs:
  print( 'connect-bones:', p[0].name, '<-', p[1].name )
  p[1].select = True
  p[0].select = True
  armatures[0].edit_bones.active = p[0]
  bpy.ops.armature.parent_set()
  p[0].select = False
  p[1].select = False
 
 for p in connectable_pairs:
  p[0].select = True
  p[1].select = True
 
 return {'FINISHED'}

こんな具合でできました✨

add-on 化にあたり、

  1. add-on として機能を汎用的にするため前項までの試作では startwith でフィルターしていた bone の名前を正規表現でフィルターする方式に変更
  2. CTRL + SHIFT + ALT + C ショートカットキーで正規表現のパターンを指定するダイアログ付きで発動可能
  3. Python Console を使う場合は bpy.ops.armature.connect_bones( name_regex= 'Neko' ) のように発動可能

にしました💪

f:id:USAGI-WRP:20200412110456p:plain

f:id:USAGI-WRP:20200412110450p:plain

この add-on を使うと、名前を正規表現で狙ったボーン群に Connected な親子関係を構築できるので、例えばネコミミだけ、髪の毛だけなどある程度簡単に狙って Connected な親子関係をばらばらのボーンから構築できます。こうして Connected な親子関係を作ると Blender の内臓機能で Connected な親子ボーン群は任意に Armature: Merge Bones でき、そうするとマージ元のボーンそれぞれに割り当てられていた頂点ブレンディングのウェイトを合成して引き継いだボーンと頂点ウェイトの関係が自動的にできるので便利、という add-on です。

f:id:USAGI-WRP:20200412110246p:plain

↑見えるボーンのほとんどが Connected な親子関係にできるデータですが、手で作業するには多すぎて多すぎます…。でも、この add-on があれば↓

f:id:USAGI-WRP:20200412110255p:plain

めでたしめでたし。おしまい💪

3D-Avatar: VRM を作って → detune して → cluster へアップロード成功するメモ; 2020-04-10版

経緯などはさておき、件について少し複雑ですので整理するメモです。時間がないけど結論だけ気になる人は最後のまとめだけどうぞ。

目次

  • (1) VRoid Studioセシル変身アプリVRM を作って
  • (2) cluster へアップロード -> 「アップロードに失敗しました。」
  • (3) Blender に add-on 群を導入
  • (E) 何かエラーが起きてしまった場合の対処療法メモ
    • (E-1) Blender + CATSFix Model しようとしたら "No mesh inside the armature found!" エラー
    • (E-2) Unity でいい感じの VRM 系シェーダーを設定しても髪の毛の内側が消えちゃう
    • (E-3) Unity で Rig を Humanoid にして Apply しようとしたら 'Error(s) found while importing rig in this animation file. Open "Import Messages" fold out below for more details' エラー
    • (E-4) cluster で "アップロードに失敗しました。ファイルサイズを25MB以下にして下さい。」エラー
    • (E-その後) cluster へ セシル変身アプリ で作成した un-h002; Atropos をアップロード -> 成功✨
  • まとめ: VRoid Studioセシル変身アプリ で作成した VRM を cluster へアップロードして使う方法

(1) VRoid Studioセシル変身アプリVRM を作って

今日までに著者が実際に VRM 作成に使用してみたツールと成果:

  1. VRoid Studio -> un-h001; Lachesis
  2. セシル変身アプリ ->un-h002; Atropos

どちらも近年のゲームにしばしば搭載されている高機能なプレイヤーキャラクターエディターのような使用感で VRM を作れるすぐれものです。標準搭載されているパーツの選択だけでも"ことはじめ"には十二分な見栄えのするキャラクターを造形できてしまいます。手間暇次第でテクスチャー、小物、衣装、素体を他の専用ツールで編集、作成もできます。使い方は使えばわかる、みたいないいツールです。

つまり、ここの部分の説明はメモないです😜 だいじょうぶです。使えばわかります…たぶん。

(2) cluster へアップロード -> 「アップロードに失敗しました。」

さて、↑のツールで作成した VRM を cluster へアップロードしてさっそく楽しもう!とするとつらい現実に叩きのめされます:

un-h001; Lachesisの場合:

f:id:USAGI-WRP:20200409061624p:plain

un-h002; Atroposの場合:

f:id:USAGI-WRP:20200409061718p:plain

つらい😭 (要件的にも、できたーからのオチ的なお気持ち的にも)

手作業でデータをごりごりいじって対処するのはわりと手間暇かかりそうな予感がするので、1ヶ月ほど cluster はほぼ棚上げ状態になっていました。今回は棚卸ししたのでメモを残しました💪

(3) VRM の detune (≃軽量化)

cluster の厳しい要件を満たすため、 VRoid Studioセシル変身アプリ で思いのままに作ったアバター VRM たちを cluster 用に detune する必要があります。素体のモデリングから完全にフルスクラッチで自作する場合も cluster の要件の範囲内で人型のかわいいモデルを作るのはなかなか難しいと思うので、それなりモデルを作ろうとするとフルスクラッチでも同様の工程を経る事になるかもしれません。

基本の手順:

VRoid Studio で出力した VRM はたぶんこの基本ばーじょんでほぼうまく cluster 対応できます✨ セシル変身アプリ で出力した VRM はいまのところ少し小細工が必要だったので、末尾の小細工必要ばーじょんに対処療法のメモを残します。

(3-1) モデルデータごとに作業用ディレクトリーを別けられるようにディレクトリーを1階層堀り、作業対象の VRM ファイルだけを入れておく

f:id:USAGI-WRP:20200409211810p:plain

はじめは VRM ファイル1個を放り込むだけですが、この後いろいろとファイルが増えます。複数のVRMを同じディレクトリーに置いて作業するとたいへんです。ケチって他のモデルデータと作業ディレクトリーやプロジェクトを共有するのは事故の元なので別けて管理できるディレクトリー構造を用意して作業をはじめます。

(3-2) Blender を導入

手操作または choco install blender など。

(3-3) Blender に add-on 群を導入

  1. VRM_IMPORTER_for_Blender2.8 ; 執筆時点では Testing 扱い
  2. Cats Blender Plugin ; Community 扱い
  3. material-combiner-addon ; Community 扱い

(3-4) BlenderVRM を読んで detune して FBX を出す

(3-4-1) File -> New -> General ( または起動初期状態 )

f:id:USAGI-WRP:20200409212545p:plain

(3-4-2) Cats Blender PluginVRM を Import

f:id:USAGI-WRP:20200409213054p:plain

↑引っ張り出したら↓縦タブから CATS を選び、 Import Model します。

f:id:USAGI-WRP:20200409213144p:plain

↓ファイルオープンダイアログが出たら、モデルデータのある場所へ初回は頑張って辿り着いて下さい。辿り着いたら、ファイルを選ぶ前に、Favorites に現在のディレクトリーを + で覚えさせて置くと、これからファイルオープンダイアログが出るたびに疲弊せずに使えるようになります💪 不要になったら - で消せばよいのでよく使う場所は気軽に Favorites へ入れるといいです。

f:id:USAGI-WRP:20200409213304p:plain

↑から .vrm ファイルを選択して開くとしばらく Blenderプチフリーズ状態になりますが生暖かい目で待ちます。たぶん今どきの3DCGやろうとする人が使っているPCなら10秒か20秒くらい待っていれば読み込みが終わります💪

f:id:USAGI-WRP:20200409213958p:plain

↑こんな感じ。画面中央はカメラが遠いだけなので、近づけてみたい時は操作方法いろいろありますが、マウスでハンドルを操作する方法がいちばん直感的に見ればワカル方法です。

f:id:USAGI-WRP:20200409214646p:plain

XYZがRGBの棒とぽんぽんでつながっているハンドルを Drag&Drop すると原点中心で視点をオービタルに回転。同様に、+印のルーペっぽい🔎ハンドルは同様に拡縮、てのひら印✋のハンドルは同様に視線系での平行移動です。基本の視点操作のほか透視投影と正射影の切り替えも↑の縦に並んだツールの最下にあります。お好みの使い勝手の良い、作業しやすそうな視界に調整します↓

f:id:USAGI-WRP:20200409215207p:plain

↑はマテリアル(材質の色や質感など)が乗っていないので3DCG入門者はやや警戒してしまうかと思います。シェーディング技術の一般的な表現分離の段階として表示設定上マテリアルが乗っていないだけなので作業中はお好みで↓を切り替えてマテリアルを乗せた絵にもできます。

f:id:USAGI-WRP:20200409220344p:plain

↑から Rendered の玉を選ぶと空間のライティングを考慮した仕上げの色味を確認できます。 VRM の detune 作業のようにマテリアルの発色を確認できれば十分な場合は Material Preview の玉を選択するのがよいかもしれません。↓のようになります

f:id:USAGI-WRP:20200409221412p:plain

(3-4-3) Cats Blender PluginFix Model して Mesh, Bone, Material を軽量化

CATS でのお手軽全自動 detune 作業を続ける前に Import したモデルの Object 群 (≃3DCG一般としての扱い上はほぼ Mesh 群の事ですが、 Blender の中では Blender 独自の階層構造と用語として橙の▽の 'Object' という単位で扱わる部分です) の名前の末尾が .baked または .baked0 になっている事を確認します↓

f:id:USAGI-WRP:20200409222030p:plain

VRoid Studio で作成した un-h001; LachesisVRM は↑の様にすべての Object の名前は .baked になっているのでこのまま進めました。 セシル変身アプリ で作成した un-h002; Atropos はそうなっていなかったのでこのままでは進めません。なぜ進めないのか、応急措置できるかは対処療法メモへどうぞ。( VRoid Studio でも素体選択その他の状況次第で対応が必要な事もあるかもしれません。)

.baked/.baked0 の確認ができたら CATS での detune 作業を進めます。次は Fix Model で Mesh, Bone, Material を最適化します。ざくっとお手軽全自動ですが、具体的に Fix Model が何をするのか、あるいは一部最適化を on/off したい場合はスパナー🔧印のところからにゃんにゃんできます↓

f:id:USAGI-WRP:20200409222830p:plain

Fix Model を押すとマウスカーソルが怪しくバグった感になりつつ例によって Blenderプチフリーズしている感じになりますが仕事してくれています💪 そっと見守りましょう。今回も10秒か20秒くらいで終わると思います。終わると↓

f:id:USAGI-WRP:20200409223113p:plain

↑すっきりざっくり detune されています。もし、Fix Model でエラーが出た場合はスケルタルアニメーション関連の設定値がどこかしらよくないかもしれません。その時は諦めて対処療法メモへ。

(3-4-4) material-combiner-addon でマテリアルを Atlas 化して軽量化

次に3DCGモデルデータとしてのテクスチャーの次元(個別割り当てされる画像の枚数の意図)と空間(1枚の画像の中でUV座標を詰め込んでデータの疎部分を少なくしたりの意図)を圧縮します。主に商業用ゲーム向けのレンダリングリソースの制限が厳しい3DCG業界で頻繁に使われるテクスチャーの工夫技術の1つで Atlas と呼ばれています。

CATS -> Optimization -> Atlas を開きます↓ または、 CATS 縦タブではなく MatCombiner 縦タブで操作しても同様です。(CATSMatCombinerの機能を取り込んでUIを出しているだけなので)

f:id:USAGI-WRP:20200409224256p:plain

↑ "If this saved you time:" とパトロンの案内が出ている通り、これを手でやるのも面倒だにゃー億劫だにゃーと思い1ヶ月くらい調査時間も取れないまま放置してました。アバターで何か楽しむ実用に繋がった際にはお気持ちパトロンまたは Commit しようかな、と思います。

もし、画面が↑のように Generate Material List, Save Atlas to.. が出ていない場合は、代わりに機能を使うために不足の add-on が案内されていると思います。その場合は導入すると Blender の再起動を促される事になるので、特に慣れないうちは作業途中の状態を File メニューから事前に名前をつけて保存しておくと安心かもしれません。

Generate Material List を押すと個別に作られているマテリアルが列挙されます。マテリアルの発色は主に diffuse color (=拡散反射光の色) で決まります。マテリアルが最終的に発色する色は最も単純には単色のベタ塗り、次いでテクスチャーマッピングによる画像のベタ貼り(≃現代では一般的にUVマッピング)、あるいはその組み合わせ(=シェーダーで乗算合成したり加算合成したり)が基本になります。

後の Unity 工程で登場する VRM 規格向けの推奨シェーダー群でもマテリアルの対応としては単色でベタ塗り、テクスチャーでベタ塗り、テクスチャーに単色ベタを乗算合成、そんなような表現が基本になっています。工程を進めて色が抜けてしまったり、あるいは期待していない色が塗られてしまっている事に気づいたら、この工程へ戻り(古典的な対処方法ですが念の為 .blend を別名で保存しておくと楽です) マテリアルごとの歯車⚙で Multiply image with diffuse color (≃乗算合成結果の焼き込み設定)を調整するだけで解決するかもしれません↓

f:id:USAGI-WRP:20200409230827p:plain

最終的にはこの工程で Atlas にしたテクスチャーを2D画像編集アプリで直接ごりごりと塗り替えてしまうという方法もあります。細かい事を考えるよりその方が"はやい"こともあります😅 わりと柔軟にどうとでもできるのでわからんかったらとりあえず進みましょう。

Save Atlas to.. を押下するとマテリアル最適化によって結合された1枚のテクスチャー画像ファイル(≃Atlas)の保存先ディレクトリーを聞かれます。この工程を進めるとインポートしているモデルデータそのもののマテリアル構造がある意味でぐちゃっと、ある意味ではすっきりと変更されます。最適化前の状態へ戻りたくなる予感がする場合は進める前に Blender の作業状態を .blend へ保存しておくと安心です。問題なければ進めましょう↓

f:id:USAGI-WRP:20200409232157p:plain

↑ぽちると一瞬マテリアルが全て剥がれた後、数秒待っていると Atlas 化されたマテリアル&テクスチャーが読み込まれて色が戻ります…が…ここで現行オリジナル版の 色が…↓

f:id:USAGI-WRP:20200409232332p:plain

↑と、こんな感じで diffuse color が抜けます。塗りが複雑なテクスチャー部分は Atlas に合成成功しているので、この程度の微調整なら先に触れたように出力された Atlas テクスチャー画像ファイルを画像編集アプリで編集し、透明部分を選択 -> 選択反転 -> レイヤー追加 -> diffuse color でベタ塗り -> レイヤーを乗算合成 -> 統合して手修正版 Atlas 画像を出力、とかすれば大丈夫です。

あるいは、 add-on の導入の項で書いたように、暫定的に VRM の diffuse の全自動合成に対応する機能を追加した実験版の add-on を導入しておけば↓のように texture に diffuse color も合成した Atlas を出せるかもしれません↓

f:id:USAGI-WRP:20200410071326p:plain

実験版を作るまでに原因、BlenderAPI、CATS で Import したデータの内部構造…などの調査に数時間かかっています。脳筋対応なら3分でできるし、実験版も結果的な diff は実はたったの2行なのだけど。ahaha... 😅

ともかく、無事に Atlas 化もできてマテリアルも detune というか軽量化できたら、.fbx に Export します。 CATSExport Model をぽち↓

f:id:USAGI-WRP:20200410082912p:plain

(3-5) Unity で FBX を読んで detune 版の VRM を出す

(3-5-1) Unity Hub を導入

https://unity3d.com/get-unity/download または https://chocolatey.org/packages/unity-hub あたりで入れます。

(3-5-2) Unity (特定リリースのエンジン本体) を導入

  • Unity Hub を起動し、 Installs 縦タブの ADD から Unity を導入
    • もし、 cluster SDK を使いたい用途がありそうな場合は cluster SDK の現行版で指定されている特定リリースの Unity を導入します
    • Note: FBX から VRM を出力するだけの場合は動けばなんでもいいです(たぶん)

(3-5-3) Unity の作業用プロジェクトを作成

Unity Hub の縦タブを Projects へ切り替えて NEW -> VRM 作業用ディレクトリーまたはお好みのどこかに Unity プロジェクトを作成

(3-5-4) UniVRM で軽量化版の VRM を書き出し

作成した作業用 Unity プロジェクトを起動し:

  1. プロジェクトへ UniVRM パッケージを導入
    • 適当に Explorer からファイルを Drag & Drop してファイルリストを確認して Import ぽち
  2. プロジェクトの Assets へ Atlas 化して保存しておいた .png と Expoert した .fbx を Import
    • 適当に Explorer からファイル群を Drag & Drop で大丈夫
    • Note: Assets へ取り込む=ファイルを複製です。もし取り込んだファイルを外部から編集したくなった場合は取り込み元ではなく、Unityのプロジェクトを配置したディレクトリーの中の Assets ディレクトリーの中に取り込まれた複製を編集します。
  3. Create -> Material -> 名前はなんでもよいので適当につける
    • 画面下側の Project タブの直下にあります
  4. インポートしたモデルデータの Animation Type を Humanoid へ変更して Apply
    • モデルを選択すると画面右側の Inspector の Rig タブの上部に Animation Type が出ます
    • Note: ここで Apply を押した時に Error(s) found ... などエラーが出たら画面下側の Console タブを確認して対処し始める必要があります。残念ながら小細工必要ばーじょん対処療法へどうぞ。
  5. インポートしたモデルデータを原点へ配置
    • Drag & Drop -> Inspector で座標を 0,0,0 にします
  6. 配置したモデルデータに先に作成した Material を設定
    • Drag & Drop で Assets のマテリアルを画面中央上部のプレビューに配置されたキャラクターへ
  7. Material の Shader を VRM 系のどれかもっともそれっぽくなるものへ設定
    • Material を選択 -> Inspector の Shader をぽち
    • UnlitCutout または、 MToon で Rendering Type を Cutout にして Cull Mode を Off にすると期待に近い再現になるかもしれません
  8. 画面左側のシーンのツリー表示から VRM 出力したいキャラクターのオブジェクトを選択した状態で、メニュー: VRM -> UniVRM-xx.x -> Export Humanoid
    • 諸元を書いて Export -> 出力先ファイルを設定して完了!

↓ファイルを取り込んだ程度の状態。やや怖い。

f:id:USAGI-WRP:20200410091246p:plain

↑の状態にマテリアルを適用(Drag&Drop)した状態↓

f:id:USAGI-WRP:20200410091347p:plain

よい具合👍

f:id:USAGI-WRP:20200410091533p:plain

Export Humanoidぽち

f:id:USAGI-WRP:20200410092124p:plain

↑書き出す VRM の諸元を書いて Export

(6) cluster へアップロード -> 成功✨

Atlasの最適化が甘いか余計な構造が入り込んでしまっているのかファイルサイズは太りましたが…↓

f:id:USAGI-WRP:20200410092521p:plain

f:id:USAGI-WRP:20200410092726p:plain

↑ここまでは失敗する場合でも進みます…

↓こうなったら全ての構成が完了し成功です!!

f:id:USAGI-WRP:20200410092826p:plain

💪🎉💪🎉💪🎉💪🎉

(3-おまけ)

アバターのアップロードに成功した状態で cluster を起動すると↓

f:id:USAGI-WRP:20200410093215p:plain

アバターを選択して cluster のワールドへ入ってみます↓

f:id:USAGI-WRP:20200410093341p:plain

↓detuned ばーじょんと言ってもさほど劣化した感じもなく、綺麗です👍

f:id:USAGI-WRP:20200410093533p:plain

アバターを使えると気分的な没入感がとても高まって嬉しい&楽しいですね🎉

(E) 何かエラーが起きてしまった場合の対処療法メモ

セシル変身アプリ で作成した un-h002; Atropos で↑の基本ばーじょんをやろうとすると幾つかの問題が発生しました。本項ではそのトラブルシューティングを例に、何かエラーが起きてしまった場合の対処療法としてメモを残します。

(E-1) Blender + CATSFix Model しようとしたら "No mesh inside the armature found!" エラー

f:id:USAGI-WRP:20200409070350p:plain

↑これが出て Fix Model できない VRM データがあります。"armature" はスケルタルアニメーションのボーン群の事です。 VRM にボーンはきちんと入っていても出ます。VRM の規格的には問題無くても、 CATS の実装がまだ甘い部分があり弾かれてしまうのが原因でした。原因はどうでもいいので対処療法をすぐに確認したい場合は少し読み飛ばして下さい:

↓ここから原因について↓

Cats Blender Plugin のソースを確認すると:

直前に VRM フォーマットの確認機構が実装されていて、その確認が false に終わるとこのエラーへ繋がります。確認機構では:

mode=2 で呼び、すべての mesh を走査して、名前の末尾が .baked または .baked0mesh があれば true と判定しています。この確認機構には詳細不明ながら TODO のコメントもあり、

など VRM, gltf, Unity/humanoid など関連の規格で定義されている確実な方法でも無さそうな事から、 Cats Blender Plugin の Author による暫定仕様の判定方法なのだろうと思います。

とりあえず Issue は立ててみました。

↑ここまで原因について↑

この問題が起きた場合は応急措置として、BlenderObject と認識される頂点データの入った部品すべての名前の末尾に .baked を追加します。すべてです↓

f:id:USAGI-WRP:20200409191700p:plain

↑名前の変更は、変更したい Object をぽちって F2 キー押下です。

こうすると Cats Blender Plugin の現状の暫定 VRM 判定をクリアーして Fix Model できるようになります💪

f:id:USAGI-WRP:20200409191933p:plain

(E-2) Unity でいい感じの VRM 系シェーダーを設定しても髪の毛の内側が消えちゃう

Blender では期待表示されていたのに…

f:id:USAGI-WRP:20200410105143p:plain

↓Unityでは髪の毛を内側から見ると完全に見えない場合

f:id:USAGI-WRP:20200410104712p:plain

f:id:USAGI-WRP:20200410104956p:plain

Unity のマテリアルの Shader を VRM/MToon に設定して、 Cull Mode を Off にします:

f:id:USAGI-WRP:20200410105414p:plain

↑こうすると内側からも髪の毛が見えるようになります↓

f:id:USAGI-WRP:20200410105515p:plain

より根本的に修正したい場合は一般論としては FBX またはその手前の .blend の編集段階でこの部分の面の向きを反転…=頂点データのインデックス群をいじる必要があるかもしれません。

(E-3) Unity で Rig を Humanoid にして Apply しようとしたら 'Error(s) found while importing rig in this animation file. Open "Import Messages" fold out below for more details' エラー

f:id:USAGI-WRP:20200410110041p:plain

↑つらい。これが出たら先ずエラー表示(='この下の方の"Import Message"を展開して詳細を確認してね')に従い "details" を確認します。

f:id:USAGI-WRP:20200410110923p:plain

Invalid Avatar Rig Configuration. Missing or invalid transform: Required human bone 'LeftLowerLeg' not found

今回の例では Unity さん的にはあってほしかった LeftLowerLeg が無かったようです。Leftが無い場合はたいていRightも無いので一緒にどうにかします。

先ず、 Blender で、この部分については問題のなかった un-h001; LachesisArmature の構造を展開して期待動作系のボーンの状態を確認すると:

f:id:USAGI-WRP:20200410112708p:plain

↑期待動作系: Hips -> Left leg -> Left knee -> Left ankle -> Left toe

次に、問題が発生してしまった un-h002; AtroposArmature の構造を同様に確認すると:

f:id:USAGI-WRP:20200410113145p:plain

LeftLowerLegエラー系: Hips -> Left leg -> Left ankle -> Left toe

Hip から Left toe までの間にジョイント部分が3箇所欲しいのが Unity の Humanoid の要件ですが、エラー系では1つ足りなかったようです。Armature のツリー構造をよく見ると:

f:id:USAGI-WRP:20200410113559p:plain

今回ケースでは UpLeg_L_001Left leg の先、 Left ankle と同じ階層に浮いている事に気付きました。Animationタブへ切り替えてどのボーンがどの部分なのかもっとよく確認してみます↓

f:id:USAGI-WRP:20200410114304p:plain

↑の確認の結果:

  • ふとももの辺りにある随分と小さなボーン = UpLeg_L_001
  • 膝から脛の辺りにあるボーン = Left leg
  • 踵から指の付け根にあるボーン = Left ankle
  • 爪先のボーン = Left toe

↓ここから(2020-04-11 追記)↓

昨日の時点ではこのあとボーンを手修正へ流れましたが、あらためて「もしかして」と思い CATSFix Model する「前」の状態を Import して統合処理前のボーンの構造と Pose Mode での動きを確認したところ、元VRMデータに問題はないので「 Fix のオプションを見直したら手間暇かからず重要なボーンの消失と再構築を免れるかも」と思い、試してみました💪

f:id:USAGI-WRP:20200411154604p:plain

結果、Fix Model しても脚を構成する Humanoid 用のボーンに欠陥が生じないようにできました✨ これで解決する場合はこの↓↓は不要になります。以下は一応この追記後も「やむを得ない場合は」用の対処メモと残しておきます💪💪

↑ここまで(2020-04-11 追記)↑

期待動作系との比較から、おそらく…:

  • UpLeg_L_001 ボーンが本来は Left leg 相当になるボーン
  • Left leg になっているボーンが本来は Left knee 相当になるボーン
  • Left ankle ボーンと Left toe ボーンはそのまま

となるところ、CATS の Importer か Fix Model で大雑把に作業し過ぎたために狂ってしまったか、あるいは元になった素体データのボーンの実装に Importer を誤動作させてしまう微妙な値が設定されていたのかもしれません。↑の推定に基づいたボーンの階層構造の修正を行います。修正は Left も Right も同じ様に必要そうなので、左右セットで同様に編集します:

f:id:USAGI-WRP:20200410115721p:plain

↑左右の同等パーツをCTRL+クリックでセットで選択し↓

f:id:USAGI-WRP:20200410115816p:plain

↓ボーンの親が期待しないところへ繋がっている場合は Armature -> Parent -> Clear Parent で親への接続を解除し、

f:id:USAGI-WRP:20200410120803p:plain

↓ボーンに期待する親を繋ぎたい場合は子ボーンと親ボーンになる予定のボーン2つを選択して Armature -> Parent -> Make -> Keep Offset (親子の距離を維持) or Connected (子の根本を親の先端まで移動)

f:id:USAGI-WRP:20200410121236p:plain

↓ボーンを選択して Armature -> Transform -> Move (移動) and Scale (拡縮)して綺麗に再配置します。必要に応じて回転ほか適当にそれらしく配置されるようにします。

f:id:USAGI-WRP:20200410121056p:plain

↓名前の変更はぽちって F2 キー押下でできます。

f:id:USAGI-WRP:20200410122409p:plain

今回の場合のようにボーンを大きく編集した際には、ウェイトも調整が必要になります。現状アニメーションするとどうなるかは CATS から Start Pose Mode を押下してボーンごとに回しておかしな挙動がないか確認します↓

f:id:USAGI-WRP:20200410143002p:plain

f:id:USAGI-WRP:20200410143250p:plain

↑どのように実験的にポージングさせても、Stop Pose Mode すれば T姿勢に戻ります↓

f:id:USAGI-WRP:20200410143344p:plain

ウェイトの調整がどの辺りにどういった具合に必要そうか Pose Mode でおおよそ把握できたら、緑の頂点3つが繋がった印のメッシュデータをぽちり、 Weight Paint にモードを切り替えます↓

f:id:USAGI-WRP:20200410143734p:plain

↓Vertex Group からウェイト編集したい頂点群を選択すると、

f:id:USAGI-WRP:20200410144006p:plain

↓選択した頂点群のウェイトが青=0→緑→赤=1で色分けされます。

f:id:USAGI-WRP:20200410143926p:plain

↓画面上部の Weight, Radius, Falloff ほかを塗りやすいブラシに設定し、視界も塗りやすいように回しながらぽちぽちウェイトを塗り絵します。

f:id:USAGI-WRP:20200410144334p:plain

↓パーツごと、あるいはある程度塗ったら Pose Mode で妙な事が起こらないか確認します。肉(ポリゴンはサーフェスなので皮だけど)がボーンの動きに付いてこなかったり、逆に余計なところまで付いてきたり、そうした妙な事が起こらなければボーンの修正は完了です💪

f:id:USAGI-WRP:20200410144512p:plain

この修正を施した状態から改めて CATS から Expoert Model したところ、 Unity の作業用プロジェクトでも .fbx をインポートしたモデルに Humanoid を設定できました🎉

(E-4) cluster で "アップロードに失敗しました。ファイルサイズを25MB以下にして下さい。」エラー

f:id:USAGI-WRP:20200410142215p:plain

↑無慈悲なエラー。さすがにひとり50MBのアバターを使ったら20人集まっただけで1GBショックになります。これは寧ろエラーにしてくれないとワールドがつらくなるかも。

一般的に3DCGデータのファイルサイズを縮めるための対処方法は、

  1. テクスチャーを圧縮(内部保持形式を高圧縮率のフォーマットへ変更する、解像度を半減する、色情報のビット表現をケチる…など)
  2. 頂点数を削る(頂点数を削るとインデックス数も削れるのでハイポリ→ローポリくらい削れる場合には大きく削れます)
  3. スケルタルアニメーション用の頂点ブレンディング、アニメーションのキーフレームを削る
  4. プロシージャルにして実行時(描画時)にごりごり計算して絵を作らせる; 今回はたぶん使えません

f:id:USAGI-WRP:20200410150258p:plain

f:id:USAGI-WRP:20200410150952p:plain

.fbx の段階では膨れていません。 .fbx には Atlas 化したテクスチャーが含まれていないので別途、確認:

f:id:USAGI-WRP:20200410151247p:plain

5MBしかない。48MB -> 25MB のためには最小でも 23MB 削る必要がありますし、テクスチャーを0にはできないのでもっとずっしりと VRM のファイルサイズに効いている何かを発見する必要があります。

un-h002; Atroposセシル変身アプリ で cluster 用など気にせず思いのまま作成したので、アニメーションが多めでマテリアルの Atlas 化の副作用でスケルタルアニメーションの頂点ブレンディングが最終的な VRM のファイルサイズに強く響いている…かもしれません。または、単純に髪の毛と猫しっぽにボーンが多すぎるのかもしれません。

↓当たりを付けるため、髪の毛のボーンをいったん削除してみます。悲しい😭

f:id:USAGI-WRP:20200410154147p:plain

↓ .fbx の段階でもそこそこ減りました。

f:id:USAGI-WRP:20200410154313p:plain

Unity で .vrm にします。T姿勢では違いはでません。

f:id:USAGI-WRP:20200410154554p:plain

f:id:USAGI-WRP:20200410154834p:plain

↑DT1がボーン削除前、DT2がボーン削除後です。82ボーン削除しているのでそれなりに効果があるかと思いましたが、ほぼ無意味でした☠

UniVRM には Import 機能も付いているのでボーン削除していない状態の un-h002; AtroposVRM (UniVRMで Export したもの) を Import して prefab を作って貰いました。どこにどれくらいファイルサイズ爆発の要因があるか powershellGet-DirTotalFileSize . してみました↓

f:id:USAGI-WRP:20200410155955p:plain

↑Meshes が 300MB 弱もあるようです😂 Textures + BlendShapes + その他は porefab 段階では全体の 1% にも満たない事がわかりましたが、恐らく頂点ブレンディングに起因したスケルタルアニメーションの頂点データが大爆発している、が答えかもしれません。

ここで UniVRMExport 機能には Export -> Import -> Export として prefab がある状態からのみ使用可能なオプション Reduce Blendshape Size 機能が付いている事を思い出しました。試してみます:

f:id:USAGI-WRP:20200410160748p:plain

f:id:USAGI-WRP:20200410161352p:plain

  • DT1 = 思うままに作った VRM を detune してみたつもりだったけどファイルサイズは爆発していた detune-1
  • DT2 = 試しに .blend に戻り髪の毛のボーンを82個削除してみたけどほぼ無意味だった detune-2
  • DT3 = UniVRMExport した DT1 を Import して prefab を生成して再度 ExportReduce Blendshape Size 付きで行った detune-3

解決しました。こんなに今回があるとは思っていなかったので驚きました🎉

…まだ un-h002; Atropos の cluster 登録まではもうひと頑張り、必要だったけどね😅

f:id:USAGI-WRP:20200410161917p:plain

↑このくらいだと希望が見えます。

(E-その後) cluster へ セシル変身アプリ で作成した un-h002; Atropos をアップロード -> 成功✨

目標、残り:

  • node 数: 290 -> 256 ; ⊿node = +34 💪
  • joint 数: 287 -> 128 ; ⊿joint = +159 💪💪💪

https://clustervr.gitbook.io/sdk/guide/avatar_customization/avatar_limit を確認すると、アップロード時のエラーメッセージの "joint" は "bone" の事、 "node" は "GameObject" の事のようです。"GameObject"は Unity用語の"GameObject" のようです。

今回のゴールが見えてきたため、ヤルキを醸し出して Blender でごりごり削ろうかと思いました…が、 "GameObject" の削減効果の要因を探る意味でも、ここまでの過程で作成していた DT2 ( 試しに .blend に戻り髪の毛のボーンを82個削除してみたけどほぼ無意味だった detune-2 ) を UniVRMImport -> Export with Reduce Blendshape Size してどの程度 joint と node が cluster のアップロードシステムで低下するか確認してみることにしました。

↓まだ削らないと joint の判定もダメなはず…

f:id:USAGI-WRP:20200410164413p:plain

↑と、思っていたので意表を突かれてしまいましたが、アップロードに成功してしまいました🎉🎉🎉

clusterアバターは現時点では同時に複数登録しておけない仕組みみたいですね。 VRoid Hub から使用可能なアバターを一覧表示、選ぶと全自動で↑↑↑でしてきたような detune をしたデータを cluster のアクティブなアバターとして取り込んでくれる、とか機能要望したいですね。案外、いろいろと仕組みが分かった今ではそれほど無茶な要望でもないような気がしてきました。

f:id:USAGI-WRP:20200410164903p:plain

cluster にログインして歩いてみました↓

f:id:USAGI-WRP:20200410165319p:plain

大丈夫そうです💪…と、言いたいところでしたが、

f:id:USAGI-WRP:20200410165501p:plain

頭パーツのボーンかウェイトが甘いみたいで、上下に首をふると怪しげな事になってしまいました😂 でも、ここまで来ていれば、ボーンのウェイト調整くらいどうという事は無いですね💪 これでようやく cluster も楽しめそうです🎉🎉

f:id:USAGI-WRP:20200410214546p:plain

↑あらまあ😂 こういう状態(髪のボーンを削減した際に髪の頂点ブレンディングのウェイトの再設定を忘れている!)でした。髪の毛を構成する頂点群にウェイトがまったく入っていない状態になってしまっていたんですねー。入れましょう💪

↓ここから(2020-04-11追記)↓

↑はMatCombiner で「マテリアルを Atlas 化した後」だと、髪の毛とヘア・アクセサリー、メガネ、顔、服、肩などなどが重なっている造形状態で頂点ウェイトをペイントで再設定するのはかなり"気難しい"作業になります。ポージングでやや無理やり髪の毛だけペイントしやすいようにボーンを回したり移動したりして作業するのはかなり大変です😂

入り組んだパーツ単位での編集作業を施したい元VRMを扱う場合には、 Blender で 元VRMImport して Fix Model した後、マテリアルをアトラス化する「前」の状態で編集するとその後で行うよりもとても楽です✨

"楽"のなかにも Blender では方法がたくさんあると思いますが、1つの"楽"の例として「髪の毛だけ編集しやすい選択と表示の状態」にする方法をメモに残します:

(x-1) 「選択」機能の対象を「面」に↓

f:id:USAGI-WRP:20200411163440p:plain

(x-2) 髪の毛の「面」を1つ、どれでもよいので選択しやすいところを「選択」↓

f:id:USAGI-WRP:20200411163902p:plain

(x-3) SHIFT + G (または F3 -> "Select Similar" など) で "Material" ↓

f:id:USAGI-WRP:20200411164256p:plain

↑こうすると実質的に髪のパーツだけ選択できます↓

f:id:USAGI-WRP:20200411164517p:plain

(x-4) SHIFT + H (または F3 -> Hide Selected など) ↓

f:id:USAGI-WRP:20200411164818p:plain

↑こうすると実質的に髪のパーツだけ編集しやすい状態になります💪

対象部位の編集がおわり表示状態を戻したくなったら ALT + H で隠していたメッシュ群を表示状態に戻します。

(x-応用) マテリアル的には複数部位に跨る部分を同時に選択したい場合

f:id:USAGI-WRP:20200411174146p:plain

↑複数マテリアルに分かれている部位の最低1つ以上の面について、2箇所目以降は SHIFT + CLICK で選択し、あとは同様に SHIFT + G -> "Material" -> SHIFT + H すると↓

f:id:USAGI-WRP:20200411174327p:plain

こうなります💪

ボーン節約のために髪とヘア・アクセサリーとネコミミを同じボーンでスケルタルアニメーションの頂点ブレンディングをしたい場合に便利です。

↑ここまで(2020-04-11追記)↑

まとめ: VRoid Studioセシル変身アプリ で作成した VRM を cluster へアップロードして使う方法

  1. VRoid Studioセシル変身アプリ で「元VRM」を作成 (他のアプリ、フルスクラッチでも以下は応用できます✨)
  2. BlenderBlender 用の以下の add-on を導入
    1. VRM_IMPORTER_for_Blender2.8 ; 執筆時点では Testing 扱い
    2. Cats Blender Plugin ; Community 扱い
    3. material-combiner-addon
  3. BlenderCATS を基点に「元VRM」を Import Model
  4. Blender で Import した Object の名前の末尾が .baked または .baked0 でなければ F2 ぽちぽちして全ての Object の名前の末尾に .baked を付ける
  5. BlenderCATSFix Model で軽量化
  6. Blendermaterial-combiner-addon でマテリアルを Atlas 化して軽量化 -> Atlas_12345.png 的な「(A)Atlasテクスチャー」ファイルができる
  7. BlenderExport Model して .fbx な「(B)FBX」ファイルを出力する
  8. Unity を導入
  9. Unity で作業用のプロジェクトを作成し UniVRM パッケージを導入
  10. Unity の作業用プロジェクトの Assets へ「(A)Atlasテクスチャー」と「(B)FBX」を放り込む
  11. Unity の作業用プロジェクトの Assets へ
    1. マテリアルを1つ新規作成し、
    2. Shader を VRM/MToon, VRM/UnlitCutout など VRM 系の良さげな表示になりそうなものに設定
    3. Texture に Assets へ追加しておいた Atlas テクスチャーを設定
  12. Unity の作業用プロジェクトの Asset へ追加しておいたFBXを
    1. Rig を Humanoid に設定して Apply -> ダメだったら Blender へ戻ってボーン見直し(このメモのE-3参考)
    2. ワールドへ配置、扱いがよいので原点に置くとGOOD
    3. 先に新規作成しておいたマテリアルをワールドへ配置したアバターへ適用(Drag&Drop)
  13. Unity の作業用プロジェクトで画面左側のシーンツリーで配置したアバターを選択した状態で UniVRM の機能でメニューから VRM -> UniVRM-xx.x -> Export Humanoid で「(P)軽量化版仕上げ直前VRM」を出力
    • ここで 25MB 以下ならそのままでも cluster へアップロードできる可能性が高いです。重い場合は以下スキップせずもう一息。
  14. Unity の作業用プロジェクトの UniVRM の機能でメニューから VRM -> UniVRM-xx.x -> Import で「(P)軽量化版仕上げ直前VRM」をプロジェクトへインポートし 「(Q) prefab なアバター」をプロジェクト内へ生成
  15. 「(Q) prefab なアバター」をワールドへ配置
  16. Unity の作業用プロジェクトで画面左側のシーンツリーで配置した「(Q) prefab なアバター」の方を選択した状態で UniVRM の機能でメニューから VRM -> UniVRM-xx.x -> Export HumanoidReduce Blendshape Size オプション付きで行い「(R)軽量化完成してるはず版VRM」を出力
  17. cluster へ「(R)軽量化完成してるはず版VRM」をアップロードできたら完成確定✨
    • できなかったら Blender に戻って cluster 用には削れる部分を削り、このまとめの工程12から16までを同様にリトライします💪 (このメモの E-その後 参考)

Happy avatar life.

参考

  1. [初心者向け]VRoid Studioで作った3Dモデルをclusterへアップロードする
  2. マイアバターを持つ方法まとめ|FaL / ShunYoshida|note
  3. [2.8]ベイクしたい|MITSUDA Tetsuo|note
  4. 【Blender2.8】ボーンとウェイトペイント【初心者用】 | ゲームの作り方!
  5. VRMデータの容量が大きくて困ったときの対処方法(初心者向け) | 南条@東方のブログ

Node.js & native code: NEON vs. node-ffi-napi vs. pure Node.js

Node.js と Rust による native code のマーシャリング手法は NEONnode-ffi-napi の2種類が現時点で有力です。マーシャリング部分のコストの特徴を大雑把に掴むため雑なベンチマーキングをしました。

Note: 理論的にはもう1つ N-API を直接リンクして cdylib を out してもできますが、手間暇、標準化、実用性などから今回は人生のリソースを節約しました。

結果 2020-03-31 版

pure NEON node-ffi-napi
empty 47.92550206 128.3124983 5800.287876
rng 35.47475487 175.1873791 5176.462751
buffer 175394.375 534.950003 59332.4

単位は [ns/function-call] です。

  1. empty: 何もしない関数を何度も呼びつけて1回あたりのコストを算術平均で評価しています。
  2. rng: f32 な unorm 値域の擬似乱数を何度も呼びつけて1回あたりのコストを算術平均で評価しています。
  3. buffer: ArrayBuffer(ES) または Buffer(Node.js) を Node.js 側で生成し、単純な加算処理をその領域全域に適用する関数を何度も呼びつけて1回あたりのコストを算術平均で評価しています。

詳細は参考からソースを眺めて下さい。また、具体的な個々の数値だけを見てもあまり意味はありません。この結果からわかる事は:

  1. 軽量な処理しか行わないなら native code は必要ありません。寧ろ、 native code とのマーシャリングコストは native code によるメリットを大きく上回るかもしれません。
  2. 特別な事情が無い限り、 native code とのマーシャリングが必要な場合は NEONnode-ffi-napi より優先しましょう。
  3. もし ArrayBuffer, Buffer のようなバッファーを操作する必要がある場合は native code を使うのが良い選択となる可能性がとても高いです。

マーシャリングコードのコストはNEONnode-ffi-napiもやり方がわかっていれば同じくらいなので、よほど特別な状況が無ければ node-ffi-napi の出番は無さそうです。

参考

vs. COVID-19: Folding@home の FAHControl でご家庭内で参加中の複数のクライアントをまとめて監視・制御する方法のメモ

ご家庭内から複数のPC端末等をクライアントとして Folding@home に参加する場合に、1箇所の FAHControl (管理画面ツール) でそれ自体はもちろん、他のクライアントも全て管理したい場合もあります。

f:id:USAGI-WRP:20200327170347p:plain

例えば↑な具合で管理できるようにしたい、と。こうしておくと、動作を監視するだけでなく、制御もできるため、アイドル時のみの設定ではなく手動で今は計算資源を提供しよう、あるいはちょっと止まって欲しい、そうした扱い方がしやすくなります。

設定方法は簡単なのですが、 FAHControl アプリは妙なところで癖の強さを残していて、たぶんこのメモを見ているであろう人には何か困っている事もある予感がするので、つまりメモを残すことにしました。

必要な予備知識

  • Folding@home が何か概要だけでも知っている (知らない場合は→ Wikipedia とか PC Watch - 自宅のPCで新型コロナウイルス治療に向けた解析が可能に とか )
  • FAHControl に関する特殊な操作方法
    • FAHControl には、たぶん Latin-1 的なキーボードじゃないとまともに文字を入力できません (何故か謎の半角カタカナが入力されても驚かない😅)
    • FAHControl には、キーボードで CTRL+V しても貼り付けが動作しないキーボードがあります
    • FAHControl には、ポインティングデバイスで右クリックメニューを出して Paste すれば任意の文字列を貼り付けで入力できます
    • まともに入力できて Copy できるテキストエディターか端末エミュレーターっぽい何かなどを起動してから始めましょう
  • ご家庭のネットワークに関する一般的な技術情報
    • ipconfig とか ip address とかして端末の IP を知れる程度の能力
    • ご家庭のネットワークの "ネットワークアドレス" の意味がわかっている程度の知識
    • ご家庭で使用中の OS やルーターFirewall 機能と設定を理解している程度の能力

方法

  1. 別のPCから接続を「受ける」側のPCで、
    1. FAHControl を起動し、
    2. 画面左側の Clients のリストにある local をダブルクリック

f:id:USAGI-WRP:20200327165245p:plain

3. Configure 画面の上部のタブを Remote Access へ切り替え
4. 実はスクロールするすごい縦長画面なので画面右側のスクロールバーを使い IP Address Restriction の囲いまで進み
5. Allow に 127.0.0.1 に加えて接続を許可する"ネットワークアドレス"(例: 192.168.0.0/24 )を「書き足し」ます ( 空白文字区切りなので `127.0.0.1 192.168.0.0/24` とか入力された状態にします )
6. Configure 画面を Save で閉じ
7. OSのタスクトレイ的なところにある Folding のアイコンを右クリックでコンテキストメニューを出し、
8. Quit で一端終了させ

f:id:USAGI-WRP:20200327165631p:plain

9. スタートメニュー的なところ、あるいはデスクトップにあるショートカットなどで [Folding@home][] を起動しなおします
  1. 接続「しに行く」側のPCで、
    1. FAHControl を起動し、
    2. 画面左側の Clients のリスト部分の下部にある Add をクリック
    3. Configure 画面の Connection タブの Address に先に「受ける」側の設定を施したクライアントの IP を入力
    4. Save

もし、この手順でやっても Clients の Status が Connecting のまま繋がらない場合は、

  • 設定を間違えていた (ネットワークアドレスの値、書き方)
  • 「受ける」側の Folding@home を再起動(手動でQuitして起動し直し)していなかった
  • Firewall でブロックしていた
  • そもそも別のネットワークに分離されていた

とかそういう状況かもしれません。よくわからないときは諦めるか、専門家に依頼しましょう。この程度ならスシやヤキニクで十分やってくれる技師もいるかもしれません。

ちなみに

"こういうの" の事は専門的には「グリッド・コンピューティング」と言います。似たようなので有名な例は BOINC とか。広義的には "スーパー・コンピューター" の一種です。実際に Folding@home が COVID-19 の根絶や特効薬の開発に寄与できるかはさておき、こうしたプロジェクトが稼働できる事、人類の危機に際して計算資源の提供が増加する様には、地球の人類にもまだ少しは浪漫を感じられますね😭

私も少なくとも今後しばらくは、 Folding@home に計算資源を提供する事にしましょう。

…そうそう、一般のご家庭では揉める事はないと思いますが、学校、会社、その他何らかの組織などで計算資源の管理者の承諾無く Folding@home のようなボランティアを設定すると人生が終了する可能性もありえなくはないのでご注意下さい。現在は COVID-19 の影響でオフィスや教室に未使用の計算機資源がたくさん眠っていて使い所のように感じる熱いハートをお持ちの方もおられるかと思いますが、先ずは管理者か、あるいは鶴の一声が可能なクラスの役員や大株主などの偉い人に相談しましょう。よほど高性能な計算機資源をたくさん抱えているわけでなければ電気代よりも売名効果の方が大きいかもしれませんが、少なくとも、他人の資産を勝手に使うと、ブラウザーのマイニングアプリでも警察に逮捕される地方があるみたいですし、みなさまどうかご安全に。

Cargo.toml の [dependencies] の特殊化のメモ

例 1. crates.io に公開されている crate を使っている状態:

[dependencies]
neon = "0.4.0"

例 2. ローカルファイルシステムで改変中の独自版へ依存先を特殊化したい場合:

[dependencies]
neon = [ path = "../path-to-my-customized-neon" ]

例 3. features フラグを独自設定に特殊化したいだけの場合:

[dependencies]
#neon = "0.4.0"
[dependencies.neon]
version = "0.4.0"
default-features = false
features = [ "default-panic-hook", "legacy-runtime" ]

例 4. features フラグを独自設定に特殊化しつつ、ローカルファイルシステムで改変中の独自版へ依存先も特殊化したい場合:

[dependencies]
#neon = "0.4.0"
[dependencies.neon]
#version = "0.4.0"
path = "../path-to-my-customized-neon"
default-features = false
features = [ "default-panic-hook", "legacy-runtime" ]

参考

WSL の内側の世界から、外側の Windows の世界の PATH が "何もしていないのに" 通っている件、をどうにかしたい場合のメモ

WSLの内側の世界では、外側の世界の Windows で実行可能なバイナリー「も」実行できます。

実際この機能はしばしば便利な事もあります。例えば、

  • 諸事情によりWSLの内側の世界で作業中に
  • PE32+ (†1) なバイナリーファイルを
  • nm (†2) したいけど ELF (†3) じゃないのでもちろんできないので
  • 仕方がないので dumpbin (†4) しなければならないが
  • わざわざWSLの外側の世界で cmdpowershell で実行するのは面倒 (†5)

みたいな事が起こるユーザーにはとても便利です。 PATH 通すか aliasfunction で呼べるようにしておけばね💪

そういうわけで PATH の話に無理やり繋げました。 WSL の内側の世界では Windows (のWSLの仕組み) が "気の利かせ" て Windows の世界の PATH を WSL の内側の Linux の世界へ全自動デフォルトで注入してくれます。野暮なお節介ババア的な感じで。すごくいい人だけどいい人すぎて余計なお世話のトラブルメーカー的な。

つまりこういう事が起こる:

  1. WSL の内側の世界でプラットフォームのパッケージマネージャーを介して yarn を入れてあるとしましょう:
  << which yarn
/sbin/yarn
  1. その yarnglobal bin 置き場は default な具合でこうなっていて、なにか適当なアプリが入っていたとしましょう (↓の例では neon; 例なのでなんでもいいです ):
  << yarn global bin --offline
/home/usagi/.yarn/bin

  << ll $(yarn global bin --offline)
total 8.0K
drwxr-xr-x 2 usagi usagi 4.0K 2020-03-22T13:20:24 ./
drwxr-xr-x 3 usagi usagi 4.0K 2020-03-20T22:04:21 ../
lrwxrwxrwx 1 usagi usagi   48 2020-03-22T13:20:24 neon -> ../../.config/yarn/global/node_modules/.bin/neon
  1. ところがこの yarn を介して導入したはずのアプリを実行すると何か奇妙な現象に見舞われて、原因を探るためにまず which とか readlink とか headhexdump
  << which neon
/mnt/c/Users/usagi/AppData/Local/Yarn/bin/neon

/(^o^)\

みたいな事が起こります。起こりました。単独で他のファイルに絡まずに実行可能なスクリプトでは実害のある問題は起こらない事もありますが、そうではない場合はわりと危険が危ないです。これについて、ぱわーのあるユーザーなら PATH について .zshrc 的ないつものところへ PATH=$( 💪 MY AWESOME BLACK MAGIC BE DESTROY AUNT MEDDLESOME WINDOWS GIVEN HOLY PATH 💪 ) みたいなみなぎる筋肉をぶつけていくスタイルでマッチョに解決していた時代も実際あったようです。 perl でも php でも awk でも python であれ、何か得意な黒魔術で対処療法の時代。

現在は既に対処療法ではなく遺伝子操作やワクチンによる解決が実用可能になっていて:

WSL 内へ摂取する"ワクチン的"でお手軽な方法として:

  1. /etc/wsl.conf を書く:
[Interop]
appendWindowsPath = False
  1. 管理者権限の powershellRestart-Service LxssManager しておく ( 全ての WSL 環境は安全に閉じてから↑の設定後に1回する )

f:id:USAGI-WRP:20200322150610p:plain

これだけでよくなったようです。🎉

f:id:USAGI-WRP:20200322150804p:plain

外の世界の Windows のお節介による PATH が消えたので先の例の WSL 内 yarn を介して導入した neonwhich でも確認できるようになりました。 yarn だけなら💪的に export PATH="$(yarn global bin --offline):$PATH" だけでも事実上は解決できるのだけど、それだけ、という事ではなく解消しておきたい場合には一般的に WSL のお節介機能を無効化するのが安全です。

"遺伝子操作"的なやつがお好みの場合は regedit して HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss のなかをもぞもぞする方法もあるようです。私は楽にすませたいので /etc/wsl.conf したのでこちらは試しませんでしたけれど。

reddit で話題になった Tauri と NEON についてのメモ、ついでに template-rust-backend-with-electron-frontend の開発理由についてのメモ

reddit の Rust コミュニティーtemplate-rust-backend-with-electron-frontend のリリースについてポストしたところ、主に2つ、 Tauri と NEON について話題になりました。( up-vote たくさんとちょうど使いたかったんだありがとう的なポストも頂きました。幸いです。 )

1. Tauri vs. Electron ( 間接的には template-rust-backend-with-electron-frontend にも関連します )

こちらは template-rust-backend-with-electron-frontend にとっては via via 的な間接的でややオフトピックよりな感じの内容ではありますが、特徴的な部分についての対決姿勢的な議論が生じていました。主な主張は次の2通り(意訳と補足を含みます):

  1. Tauri 上げ上げ系
    • Tauri なら高速でフットプリントも高々6MBに収まるよ、 Electron はすごい重いっす😭
  2. Tauri じゃない理由を推論してくれる系
    • Tauri のフットプリントが小さいのはブラウザーがバンドルされておらず、OS が提供するブラウザーを WebView として使うから。つまり…
      • 動作や見た目が実行環境によって変わる可能性(ブラウザーの種類やバージョンによる互換性問題)をアプリ側で考慮する必要が生じる
        • Electron だと WebView をバンドルする特定版の Chromium に決め打ちできる (少なくともユーザー環境に依存したブラウザーの互換性問題は起こらない)
        • 実際問題: OSX -> Safari が起動、 WindowsMSHTML が起動するとつらい😭 (まともに更新されている一般的なWindows 10しか考慮しなくていい世界ならEdgeだけどね😜)
    • Tauri はまだ若く TODO な部分がまだたくさんあるんだ

vs. な姿勢については、 Rust バックエンドに Web 系テクノロジーのフロントエンドをマーシャリングする選択肢について「でも、現時点の選択肢としては両方からどちらもかんたんに選べるのが嬉しいじゃろ」と思います。 Tauri が順調に TODO を消化し、その上でさらにビルド・プロセスで特定のブラウザーをバンドルする能力を獲得する日が来たら、 Tauri は Electron に対してほとんど上位互換的な技術要素になり得ますが、 TODO 消化はともかくバンドル能力の獲得は恐らく Tauri の開発思想的に無さそうな気はします。

Tauri は Rust のフロントエンド向け技術要素として現状に対しては良い選択肢の1つだと思います。名前が STARGATE の地球の人の意味の Tau'ri っぽいのも面白い点です。しかし、ブラウザー互換性問題への対処可能性がアプリ開発のプロジェクトチームの負担となる事は、特に「日本企業」とか「役所」とか「瑕疵担保」などを考える場合には2020年現在もまだネガティブな要因として考慮から外せないかなって思います。そうした懸念の低い分野のプロジェクトでは積極的に採用しても面白いのかな、と思います。

また、 Tauri の公式 README.md を見る分には TODO もそれほど困るコア機能的な部分は既に無さそうには見えます(どう見ても OSX 好きな開発者が中心になっているので WindowsGNU/Linux ではうまく動かない機能とかある"かも")。 Tauri vs. Electron については reddit で生じていた議論も核心の1つではありますが、 Tauri 公式の README.md の Comparison between Tauri and Electron が網羅的でわかりやすい1つ比較例として有用そうです:

参考: https://github.com/tauri-apps/tauri

  1. フットプリント: 例えるなら、
    • Electron はスイス・アーミー・ナイフ、
    • Tauri は刃先は現地で調達した何かを挿して使えるナイフの柄みたいなものかなーと思うので、「Tauri、せやろな」と素直に思います
  2. Interface Service Provider:
    • 多様性とユーザーへ選択肢を与える事は思想的にも、もしもの「こんなこともあろうかと」への備えとしては理想性が高いけれど、
      • 現実は reddit でも議論になったように可能性があるだけでもしんどい上に実際にもブラウザー互換性問題はしんどい部分がまだ捨てきれません
    • 開発チームの現実の負荷と生産性へのデメリットの視点からは Tauri は不利に思います
  3. Backend Binding:
    • Tauri vs. template-rust-backend-with-electron-frontend ではどちらも Rust になるし、
      • どちらもネイティブなバイトコードが実行されるので差はないですね
  4. FLOSS:
    • Electron も現実的に問題になる可能性は既に極めて低いので、
    • あとは宗教的/政治的な意図で Free/Libre 互換性を気にしたいかどうか次第じゃないかな
  5. Multithreading:
    • Tauri が Yes と堂々と書いていてつよいなーとは思います。技術変態的な用途で遊びたいなら Tauri が楽しいかもしれません。
    • ただ、実用性としてはそもそもデスクトップアプリのフロントエンド部分としての Electron について、 ES の Worker、そもそもの ES の実行効率、また Tauri が堂々と Yes と言うからにはそこにはブラウザー互換性とそれをカバーするであろうサブシステムもあるのでしょうし、遊びかなーって思います。
    • バックエンド部分を分離したアーキテクチャーでフロントエンド部分で MT が活きる用途というのは実際には非常に限られます。もしレンダリングをMT化できれば嬉しいのでは、と思う人もいるかもしれませんが、OSの描画コンテキストを生で扱える処理系でもレンダリングのMT化はまだまだ極めて困難な技術です。仮にGLコンテキストを使うならWindowsではGLコンテキストのMT対応は不十分ですし、そもそもプラットフォーム依存という仕様で困難が大きいです。加えて、ブラウザーで標準化された Web Worker (厳密にはこれはJavaScriptECMAScriptの仕様ではなくブラウザー側で追加されるWeb APIに含まれる仕様です)は MT としてはメッセージングベースで…閑話休題
  6. Auto Updater:
    • Electron にはあります。製品の要求にこの機能が欲しい事はしばしばあるので便利です
    • Tauri も Soon らしいので期待したいですね。
  7. Android, iOS:
    • Electron の弱点の1つかもしれません
    • この点は「 Tauri なら」と言える強みかもしれません
  8. No localhost option:
    • Electron の実行環境がマルチユーザーなシステムの場合にファイアーウォールを比較的高度に設定できないと脆弱性になり得えます
    • この点も「 Tauri なら」と言えそうです
      • もし開発するアプリのリリースターゲットがマルチユーザー同時ログオンを考慮する必要があって、その上でリッチなGUIデスクトップアプリを実行させたいなら、というニッチな状況も想定しなければならないなら、だけど
  9. Desktop Tray:
    • Tauri では Soon
    • Electron では No
      • native バックエンドを持つアプリなら必要に応じてトレイ常駐的な機能の後付けはどうとでもならない事もない
      • プロセス内から実現しにくい場合はトレイ用の別プロセスを立ててIPCすればそれほど難しい機能ではありません (クロスプラットフォーム対応したいとなると少々しんどくなります)

以上の比較表の外で Tauri の TODO から気になるのは Multiwindow Mode です。「にゃんにゃんしますか?(でんっ)」みたいなダイアログも原理的にはマルチウィンドウなのだけど、そういう部分なのか、また少し違ったニュアンスなのかは調査してみないとわかりませんが、チョット気になります。

(1と2のハザマ): template-rust-backend-with-electron-frontend の開発理由

私が template-rust-backend-with-electron-frontend を準備したモチベーションはそもそも…

  • WebView 系フロントエンド技術要素の開発
  • Electron や関連プロジェクトへのコミット
  • Tauri や関連プロジェクトへのコミット
  • FLOSS の普及/布教または単にボランティア・ハートによる奉仕したみ
  • Rust バックエンドに対応する GUI Toolkit の開発
  • 何であれとにかく GUI Toolkit 的なフロントエンド技術の開発

ではなく、寧ろそのようなあたりへ触れている人生の資源の余裕は残念ながら無いので、

  • Rust でアプリ作りたいけど
  • GUI Toolkit 的なフロントエンド技術の成熟がまだ弱い
    • Qt や Gtkバインディングはあるけど高価 or Free/Libre なライセンス汚染が生じてしまう ( OSS 作りたいだけならいいんだけど… )
    • conrod だとビジネス的なGUI部品やデフォルトでの見栄え的に弱い (日本語の入出力ちゃんとできてすごいし、ゲーム開発ならGUI部品も絵作りから独自にやりたいからその用途で使うなら問題にならないんじゃが…)
    • OrbTkdruid にも期待したいけど、"今"使うには若すぎる…せめて日本語の入出力…ウィジェット(≃コントロール)もモウチョット欲しい… (作ったりミコットしたりしつつ、それを使った製品本体も作る余力はない用途にも使いたいんじゃ…スマヌ…)
  • どうせ "今を凌ぐ仮のフロントエンド技術" を採用するなら…
    • できるだけバックエンドとは疎結合ですげ替えやすさを維持できる要素技術だと嬉しい
    • あれがない、これはできない、それは作らないといけない…そうした事ができるだけ少ない成熟した技術をぽんっと使っておきたい
      • (=プロジェクトチームの生産性を "仮のフロントエンド" にできるだけ注ぐ必要が少ないと嬉しい)
    • ルック&フィール、意匠設計的な部分に手を付けるのにフレームワークの深い理解が必要ない方が嬉しい
      • ("顧客が本当に必要だったGUI" への要求に手早く簡潔に対応できるものだとなお嬉しい)
    • 表示とUIとしての入出力部分だけきちんとうまいことやって貰えたら、あとはバックエンドの開発力でどうとでもするぞい💪💪💪

と、いうわけで、そういう事なら…

  • .NET Core/WPF/Prism
  • Node.js/(...brabrabra...)/React/Electron

どっちかが私や関連するプロジェクトチームのスキルセット的にも有利な選択肢ネ、でも Rust 純度高い技術の可能性も捨てたくは…やっぱり少なくとも今はまだダメでした…となった流れと、 .NET Core の WPF だと Windows 以外へのクロスプラットフォーム・ポータビリティー的に不安しかない、 Node.js/React/Electron 系を採用しておけばバックエンド Rust と融合させて Windows, OSX, GNU/Linux での心配は少ないし、チョットどうにかすれば Web サービス、さらにモウチョットどうにかすれば Android / iOS 向けにも fork しやすい…ハズ…。

と、いうことでにゃんにゃんしてできたのが template-rust-backend-with-electron-frontend です。要素技術のシンプルなマーシャリングを実現しただけの状態のプロジェクトテンプレートはバックエンドプロジェクトに余計な束縛を与えない事も大事な要素です。もちろん、ある程度はアレ向け、ソレ向け、と用途にフォーカスした派生プロジェクトテンプレートも用意したいとは考えていますし、できれば Rust cdylib と Electron の間のマーシャリング・コードの自動生成機能を付けたい気持ちもあります。但し、しつこいようですが、バックエンドの Rust コードが主体で、その開発に制約を設けたり、特定のツールやビルド方法への強めの束縛は取り込みたくありません。

2. NEON; Rust と Node.js を繋ぐマーシャリング技術

NEON 自体は Rust と Node.js のマーシャリング技術要素部分に注力した「補助的なビルドツール」のプロジェクトです。

実用していなかったので単純に存在を忘れていました、という正直なところはあるのですが、 reddit で「どうして NEON 使わないんだい?」的な議論から試みました。いい感じに template-rust-backend-with-electron-frontend へ組み込めたら素直に嬉しい技術です。

Windows で試したところ、開発環境への NEON ツール群の導入は簡単にできたものの、プロジェクトテンプレートを作成するコマンドライン・インターフェースが挙動不審で制御が帰ってこなくなりました😭

誰も助けてくれない気配を感じたので NEON の repos を git clone して NEONcli 部分についてコードを確認、 TypeScript と判明したのでデバッガー(今回は VSCode を使いました)に必要な設定の変更と追加をして適当にブレークポイントを挿しつつステップ実行してみました: -> https://github.com/neon-bindings/neon/issues/406#issuecomment-601832883

結果: console.log() で制御が行方不明になる -> https://github.com/neon-bindings/neon/issues/406#issuecomment-601832883

原因がわかればもちろん修正して PR したいところでしたが、わたしの Node.js / TypeScript のワカリでは状況に対して修正すべき原因に適切な直感が綺麗には働きませんでした。残念。

ただ、不幸中の幸い、この問題はどうやら neon new の主だった処理を終えた後、残すところフレーバー的な表示の出力だけになった状態で発生する、実質的にはそれがわかっていれば実害は無い部分という事もわかりました。WSL2 を使えば Windows と共有するファイルシステム上にも neon new 期待動作できる事は先の Issue #406 へのレポートのようにわかっていましたし、さいあく neon new できなくても neon build --release できるプロジェクトをどうにかできれば NEON 自体は WSL2 も使えない Windows 開発環境のメンバーがプロジェクトチームに居たとしても大丈夫…かも…などなどありましたが、その前提の "さいあく" はとりあえず回避できました。

  • neon build --release

これで "Hello, world" 的な .js は出力できました。build では new のような問題が起こらなくて一安心です。続いて、NEON 公式の docs の確認を進めると、

を見つけました。「ぉ、これはオレオレテンプレートからもりもりしたり、 neon new から Electron/React/その他いっぱいのプロジェクト初期状態の構築や依存関係のうにゃーとかせずに使えて便利になるのかも?」とちょっと期待しました。

ダメでした…。よく見ると neon-hello は4年も保守されていません。 昨年あったらしい PR #1 にも Author の応答が無さそうです。お手軽にはじめるテンプレート・プロジェクト的な部分だと思うので、手動でどうにかすればいいのかも、とも思いますが…

  1. 少なくとも "いま" フロントエンド部分の要素技術に手間暇を割きたくはありません ( 趣味のOSS開発&コミット活動としてなら何であれ参加できるのはたのしーのだけど… )
  2. neon build --release しかできない束縛ちょっと嫌かなぁ…
    • たぶん NEON ではマーシャリング部分のみ neon して、結果的には3層構造の Node.js 処理系 <--> neon した .rs から .js と cdylib のマーシャリング部分 <--> .rs pure な rlib なアーキテクチャーを前提にしているのかなーと思うので、そのために --release しか受け取らないのかなーとは感じるものの

加えて、NEON本体のソースについても要素技術の更新がかなーーり古いまま保守されていない部分もあり、「うーむ、これなら… template-rust-backend-with-electron-frontend にマーシャリング・コードの自動生成機能を cargo か npm / yarn のカスタムビルドでオレオレ機能追加した方がいいかなぁ…というお気持ちも。 NEON と良し悪しで戦う気はありませんが、アプローチの思想的にも、アーキテクチャー的にも差異を感じつつ、もう少し NEON については試しておこうかな…といった具合です。いまのところ。