蒼水技術録

蒼水家技術録

蒼水が普段制作とか勉強とかして覚えたことを復習としてまとめてるものです。 それが最適解かどうかはわからないけど、何を目的として始めて、何が必要で、実際何をやって、どうなったか、まで全部過程を残すための技術ブログです。

Blender アドオン編 #3 -アドオン開発勉強記録 第3回-

どうも蒼水です。
今回は前回の内容をもう少し掘り下げていく回になります。
前回の記事はこちら
atelier-aomi.hatenablog.com

今回の目的

前回実際にアドオンとして機能する部分であるオペレータークラスを1つ作成しましたが、今回はこのクラスを複数作成した場合どうすれば良いのか、という部分まで掘り下げていきます。
また、そのために機能をもう一つ作成することになります。
今回はそこまで重い内容でもないのでさらっと流す程度になるかなと思います。

オペレータークラスを1つ追加する。

前回ICO球を生成する機能を追加しましたが、今回はさらに立方体も生成する機能も付けてみます。
オペレータークラスに関する基本的なことは前回解説しているので省きますが、クラス変数、execute関数がクラスごとに必ず必要になるのでそれに合わせて今回は以下のようにしました。

class SAMPLE21_OT_CreateICOSphere(bpy.types.Operator):
    bl_idname = "object.sample21_create_icosphere"
    bl_label = "ICO Sphere"
    bl_description = "Add ICO Sphere."
    bl_options = {'REGISTER' , 'UNDO'}
    
    def execute(self, context):
        bpy.ops.mesh.primitive_ico_sphere_add()
        print("Sample : Add ICO Sphere.")
        
        return {'FINISHED'}
    
class SAMPLE21_OT_CreateCube(bpy.types.Operator):
    bl_idname = "object.sample21_create_cube"
    bl_label = "Cube"
    bl_description = "Add Cube."
    bl_options = {'REGISTER' , 'UNDO'}
    
    def execute(self, context):
        bpy.ops.mesh.primitive_cube_add()
        print("Sample : Add Cube")
        
        return{'FINISHED'}

前回からクラス名は変更していますが基本的には同じです。
プリミティブモデルを生成する関数を呼び出しているだけです。
適宜クラス変数の内容等は前回から変更しています。
クラスに関してはとりあえずこれだけです。

メニューに登録する

オペレータークラスが増えたということはそれぞれをメニューに追加してやらねばなりません。
こちらも前回と同様に引数にクラス.bl_idnameを渡してやるだけでOKです。

def menu_fn(self, context):
    self.layout.separator()
    self.layout.operator(SAMPLE21_OT_CreateICOSphere.bl_idname)
    self.layout.operator(SAMPLE21_OT_CreateCube.bl_idname)

Blenderにアドオンをぞれぞれ登録する

最後にいつものごとくBlenderにアドオンを登録していくわけですが、クラスが複数ある場合はこのあとのregister/unregisterでfor文に使うため、配列に入れておきます。

classes = [
    SAMPLE21_OT_CreateICOSphere,
    SAMPLE21_OT_CreateCube,
]

次にこれを使って1つずつ登録していきます。

def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.VIEW3D_MT_mesh_add.append(menu_fn)
    print("クラスを二つ使用するサンプルアドオンが有効化されました。")

def unregister():
    bpy.types.VIEW3D_MT_mesh_add.remove(menu_fn)
    for c in classes:
        bpy.utils.unregister_class(c)
    print("クラスを二つ使用するサンプルアドオンが無効化されました。")

これでアドオンを有効化してみるときちんと【上部メニュー -> 追加】 にICO球に加え立方体も表示されていることが確認できます。
もちろん2個まで、ということもなくもっとたくさん扱うこともできます。登録さえできればとりあえす動く。(良いかどうかはさておき)
f:id:Yui_Aomi:20200315174352p:plain

コード全体

import bpy

bl_info = {
    "name": "サンプル:クラスを二つ使用するアドオン",
    "author": "Yui_Aomi",
    "version": (3, 0),
    "blender": (2, 80, 0),
    "location": "3D view Port > mesh > add",
    "description": "クラスを二つ使用するサンプルアドオン",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}

class SAMPLE21_OT_CreateICOSphere(bpy.types.Operator):
    bl_idname = "object.sample21_create_icosphere"
    bl_label = "ICO Sphere"
    bl_description = "Add ICO Sphere."
    bl_options = {'REGISTER' , 'UNDO'}
    
    def execute(self, context):
        bpy.ops.mesh.primitive_ico_sphere_add()
        print("Sample : Add ICO Sphere.")
        
        return {'FINISHED'}
    
class SAMPLE21_OT_CreateCube(bpy.types.Operator):
    bl_idname = "object.sample21_create_cube"
    bl_label = "Cube"
    bl_description = "Add Cube."
    bl_options = {'REGISTER' , 'UNDO'}
    
    def execute(self, context):
        bpy.ops.mesh.primitive_cube_add()
        print("Sample : Add Cube")
        
        return{'FINISHED'}

def menu_fn(self, context):
    self.layout.separator()
    self.layout.operator(SAMPLE21_OT_CreateICOSphere.bl_idname)
    self.layout.operator(SAMPLE21_OT_CreateCube.bl_idname)
    
classes = [
    SAMPLE21_OT_CreateICOSphere,
    SAMPLE21_OT_CreateCube,
]
    

def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.VIEW3D_MT_mesh_add.append(menu_fn)
    print("クラスを二つ使用するサンプルアドオンが有効化されました。")

def unregister():
    bpy.types.VIEW3D_MT_mesh_add.remove(menu_fn)
    for c in classes:
        bpy.utils.unregister_class(c)
    print("クラスを二つ使用するサンプルアドオンが無効化されました。")

if __name__ == "__main__":
    register()

まとめ

クラスをひとつコードの中で複数扱う場合はそれぞれクラスを作成後、使用するクラスを配列にすべて格納、それをBlenderに登録してやる。
これで複数のオペレータークラスを扱うことが可能になるらしい。とりあえずクラスたくさん作ったら配列にぶっこんでおこう(雑)

次回はこれまで使用してきたプリミティブオブジェクトを生成するときにオペレータープロパティを使用して後から各種パラメータを制御する、ということをまとめていきます。

前回
atelier-aomi.hatenablog.com

次回(リンク作成予定)

Blender アドオン編 #2 -アドオン開発勉強記録 第2回-

どうも蒼水です。
前回に引き続き何も機能のないアドオンに実際に何かしらの簡単な機能を一つ追加するところまで行きます。
前回の記事はこちら
atelier-aomi.hatenablog.com

今回の目的

前回作成したアドオンの有効化/無効化のみ行えるアドオンのような何かに、実際に簡単な機能を一つ追加してみることを目的に進めていきます。
メニューからICO球をシーンに追加するアドオンで、実行結果としては以下の画像の様な感じになります。

f:id:Yui_Aomi:20200308164819p:plain
オブジェクト(メッシュ)を追加するアドオンの実行結果

BlenderAPIを利用する

Blenderにおいてアドオンを開発するうえで欠かせないのが公式で用意してくれているAPI【bpyモジュール】の利用です。
これを利用するためにこのモジュールをインポートする必要があります。
インポートは以下のように行います。

import bpy

これでAPIの利用ができるようになります。

今回はbpyモジュールしかインポートしていませんが、とりあえず今はこれだけにとどめておきます。
import方法は他にも書き方がありますがpython自体の話になってきてしまうのでそれに関しては割愛します。
気になる方は”from import” とかでググりましょう。

オペレータクラスの作成

クラス名を決める

Blender上で操作した際に実際に動作する部分を定義していきます。
クラス名には決まりがあり、以下のフォーマットに従います
【〔アドオンの名称〕_〔XX〕_〔任意の名称〕】
また、真ん中のXXの部分に関しては以下のような決まりがあり

文字 内容
OT オペレータ
MT メニュー
PT パネル
UL リスト

■例:SAMPLE_OT_SampleAddon

みたいな感じでクラス名を決めます。

クラス変数を定義する

クラスを作成したら次にクラス変数を以下の4点定義します。

クラス変数 説明
bl_idname str 内部で使用されるID。唯一の文字列。
bl_label str メニューに表示される文字列
bl_description str メニューに表示される説明文
bl_options set 処理の属性
class SAMPLE21_OT_CreateObject(bpy.types.Operator):
    bl_idname = "object.sample21_create_object"
    bl_label = "ICO Sphere"
    bl_description = "Add ICO Sphere."
    bl_options = {'REGISTER' , 'UNDO'}

bl_idnameは他とはかぶらない唯一の文字列である必要があります。
また、文字列は全て小文字である必要があり、以下のフォーマットに沿って決めます。
【〔アドオンのカテゴリ〕.〔任意の文字列〕】
なお、bl_idnameは自由に決めて良く、あくまでも唯一の文字列であれば大丈夫らしい。

メイン処理を行う関数を定義する

上記4つのクラス変数を定義したら、次にメニューなどから実行される関数(execute)をここに追加していきます。

def execute(self, context):
        bpy.ops.mesh.primitive_ico_sphere_add()
        print("Sample : Add ICO Sphere.")
        
        return {'FINISHED'}

オペレータ内で定義する関数の種類はいくつかありますが、一旦今はメイン処理となるexecute関数のみ使用します。
この中に機能を実際に追加していきますが、まずBlenderAPIに含まれている関数を使用します。
使用できる関数は下記URLに列挙されています。今回はICO球を追加しています。
docs.blender.org
引数を指定できるので大きさや位置など指定することもできますが、一旦今回はデフォルトの状態のICO球を追加するだけのアドオンになります。
printは前回もありましたが、実行時コンソールに表示される文字列となります。
戻り値、returnには4種類指定できますが今回はFINISHEDというexecuteの処理が正常に行われたことを示すものを指定します。
逆に、失敗した場合にはCANCELLEDを指定します。
他2種類は今回はいったんスルーします。

メニューを構築する関数を定義する

全項で実際の処理を定義しましたが、このままではどこから実行できるのかが定義されていません。
ので、メニューに実行するためのUIを構築する関数(menu_fn)も定義する必要があります。
そのためには、指定したメニューに登録してやる必要があります。
メニューに関するリファレンスはこのあたり。
docs.blender.org

def menu_fn(self, context):
    self.layout.separator()
    self.layout.operator(SAMPLE21_OT_CreateObject.bl_idname)

メニューUIはメンバ変数であるlayoutを使用します。
separatorではメニューの項目と項目の間に区切る線を入れるためのものです。
operatorではメイン関数であるexecuteを定義した〔クラス名.idname〕を指定します。
これによりメニューへの登録を行うことができます。
実際追加されるとこのようになります。

f:id:Yui_Aomi:20200308165722p:plain
UIに追加されている

上記で作成したクラスをまとめてBlenderに登録する。

前項までオペレータクラスの作成が完了しましたが、まだこのままではアドオンとして利用できません。
最後にアドオンとして利用できるようにregister/unregisterでBlenderに登録してやる必要があります。
前回の記事では単に文字をコンソールに表示させただけですが、今回はこの作成したオペレータクラスを登録してやらねばいけません。
作成したクラスは以下のように一旦まとめておきます。

classes = [
    SAMPLE21_OT_CreateObject,
]

今回はひとつだけしかクラスがないので1つだけですが、複数クラスを扱う場合はそれらを全てclassesにまとめておきます。

次にregister/unregisterの処理。

def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.VIEW3D_MT_mesh_add.append(menu_fn)
    print("サンプル: ICO球を生成するアドオンが有効化されました。")

def unregister():
    bpy.types.VIEW3D_MT_mesh_add.remove(menu_fn)
    for c in classes:
        bpy.utils.unregister_class(c)
    print("サンプル: ICO球を生成するアドオンが無効化されました。")

まず、作成したクラスはbpy.utils.register/unregister_classに渡してやることでクラスをBlenderに登録してくれます。
変数cにクラスを格納し、それを一個一個登録していく、という部分になります。登録の解除も同様。
1点注意することは順番の問題で、
登録時にはクラスの登録→メニューへの追加
削除時にはメニューから削除→クラスの削除
の順で記述する必要がある。

次にbpy.types.VIEW3D_MT_mesh_add.append/removeという部分。
VIEW3D_MT_xxxというのは3Dビューの上部メニューにあるこれらのこと。

f:id:Yui_Aomi:20200308171312p:plain
3DVIEW_MT_xxx

ここにappend(追加)/remove(削除)するよ!っていうのがこの部分。
なお、今回はオブジェクトの追加なので追加であるmesh_addを指定している。
他にも【オブジェクト】や【選択】などにも追加することができる。

詳細は後述するが、リファレンスを開かずともどの関数が使われているのかは実は下画像の様にBlender上で確認することができる。
f:id:Yui_Aomi:20200308171800p:plain

コード全体

import bpy

bl_info = {
    "name": "サンプル:ICO球を生成するアドオン",
    "author": "Yui_Aomi",
    "version": (3, 0),
    "blender": (2, 80, 0),
    "location": "3D view Port > Add > Mash",
    "description": "ICO球を生成するサンプルアドオン",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}

class SAMPLE21_OT_CreateObject(bpy.types.Operator):
    bl_idname = "object.sample21_create_object"
    bl_label = "ICO Sphere"
    bl_description = "Add ICO Sphere."
    bl_options = {'REGISTER' , 'UNDO'}
    
    def execute(self, context):
        bpy.ops.mesh.primitive_ico_sphere_add()
        print("Sample : Add ICO Sphere.")
        
        return {'FINISHED'}

def menu_fn(self, context):
    self.layout.separator()
    self.layout.operator(SAMPLE21_OT_CreateObject.bl_idname)
    
classes = [
    SAMPLE21_OT_CreateObject,
]
    

def register():
    for c in classes:
        bpy.utils.register_class(c)
    bpy.types.VIEW3D_MT_mesh_add.append(menu_fn)
    print("ICO球を生成するアドオンが有効化されました。")

def unregister():
    bpy.types.VIEW3D_MT_mesh_add.remove(menu_fn)
    for c in classes:
        bpy.utils.unregister_class(c)
    print("ICO球を生成するアドオンが無効化されました。")

if __name__ == "__main__":
    register()

おまけ:Blenderで使われている関数をBlender上で確認する。

今回リファレンスも貼って確認してきたが、量が膨大故探すのが大変である。
ので、必要な機能が欲しい場合2種類確認方法がある。
1:対象箇所にマウスオーバーすると表示されるものを確認する
こちらはメニュー上での操作などを確認しやすい。
f:id:Yui_Aomi:20200308172729p:plain

2:コンソールを確認する
こちらは実際呼ばれているクラス/関数などが確認できる。
下画像で言えば今回作成したアドオンが呼ばれていることが確認できる。
f:id:Yui_Aomi:20200308174010p:plain
bpy.ops.mesh.primitive_ico_sphere_add(enter_editmode=False, location=(0,0,0,))
bpy.ops.object.sample21_create_object()
これがその部分にあたる。

bpy.ops.object.editmode_toggle()
はオブジェクトモードと編集モードの切り替え時に呼ばれている。

このようにBlender上でどの機能がどのタイミングでどの関数を呼んでいるのか、くらいはBlender上で確認することができます。

まとめ

オペレータクラス作ってメニュー関数作って1個1個登録してやると機能追加ができるらしい。
Blender上でいろいろ確認できるのとてもたすかる。ありがたや...。

次回は今回作成したオペレータクラスを複数作成し、機能の拡張と登録をやっていこうと思います。

前回
atelier-aomi.hatenablog.com

次回
atelier-aomi.hatenablog.com

Blender アドオン編 #1 -アドオン開発勉強記録 第1回-

どうも蒼水です。
先月からBlenderでアドオンを開発すべく、こちらのページを参考にしつつ勉強を進めております。
いつかTA(テクニカルアーティスト)のような何かになりたい(願望)

最高なので興味ある方は是非読みましょう...!
https://colorful-pico.net/introduction-to-addon-development-in-blender/2.8/index.html

今回の目的

Blenderアドオン編では復習がてら自分で咀嚼したものを個人的にまとめなおして理解を深める目的で進めていきます。
序盤ではその通り上記で紹介した記事を参考に自分が勉強した内容を自分なりに咀嚼して書いていきます。
一通り学び終え、中盤以降では改造から始め自分のオリジナルでアドオンを開発するまでに至れればなと思っております。

そもそもアドオンとは?

BlenderにおけるアドオンとはBlenderのデフォルトではない機能を拡張して使うためのもの、という感じでしょうか。
BlenderPythonで開発されており、また各種APIも公開されているためこれらを利用してアドオンを開発することができるようになっています。
docs.blender.org
とはいえ全部英語であること&物量が半端ない、という感じで全部見ていくのは結構キツイですね...。
ので、先ほどのようなページを用意してくださっている方には感謝極まりないです。ありがたい...。

f:id:Yui_Aomi:20200301190358p:plain
アドオンのON/OFF
こんな感じで【編集->プリファレンス->アドオン】からON/OFFできます。
インストール方法は直接アドオンのフォルダにぶち込む方法と、Zipからインストールする方法がありますがこれに関しては割愛します。

アドオン開発の準備

前述の通りBlenderのアドオン開発にはPythonを使ってコーディングしていく必要があります。
そのために開発環境が必要になるわけですが、Blender公式でしっかりコーディング用の機能を用意してくれています。
上部のタブの中にあるScriptingというものがあるのでこれを選択するとこのような画面になります。

f:id:Yui_Aomi:20200301185311p:plain
Blenderの開発環境
開発に必要なテキストエディタ、コンソール、オートコンプリートの機能も備わっており開発するには十分かと思いますのでこれを使用していくため特に準備は不要です。

▼オートコンプリート(自動補完機能)に関して
Visual Studioを普段使っているのでインテリセンスが欲しくてたまらないんですよね....
Blenderにも一応それに近い機能があるのですが、自動では表示してくれないので上部【メニュー->編集->オートコンプリート】をしてやる必要があります。
ショートカットは【Ctrl+Space】なので基本これを使っていくことで快適にコーディングが行えるようになります。

f:id:Yui_Aomi:20200301190557p:plain
オートコンプリートに関して

一点注意点としては、どうもこのオートコンプリート、VSのようにモジュールが全て読み込まれていてそこから候補を出してくれるというものではないらしく自分が今書いているスクリプト内で使われた関数や変数を候補として出してくれる機能らしいです。そこまでで出てこなかったモノ、インポートしたbpyモジュールなどからの候補は上げてくれないらしいです。
そこは少し不便ですが最初一回書けば後は補完してくれるのでないよりマシかな、というくらいですかね。

そこまで気にならなければこのエディタで、いやインテリセンス欲しい!ってなったらVS使うのがいいかな~くらいに自分は思っております。

Blenderアドオンのおまじない要素

アドオンを開発していくにあたって必ず必要になる要素に関して書いていきます。

bl_info

これはアドオンを認識させるために基本的な情報を辞書型で列挙しておくためのものですね。
主にプリファレンスに表示されるために必要になる部分です。
こんな感じで列挙します。

bl_info = {
    "name": "サンプル",
    "author": "Yui_Aomi",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "追加→メッシュ",
    "description": "サンプルアドオン",
    "warning": "機能はありません",
    "support": "TESTING",
    "wiki_url": "sample",
    "tracker_url": "sanmple",
    "category": "Object"
}

それぞれが以下のように対応します。
f:id:Yui_Aomi:20200301192446p:plain

なお、blenderは対応するblenderのバージョン
supportはプリファレンス上部にある【公式】【コミュニティ】【テスト中】のどれかを認識するためにそれぞれ
【公式】→OFFICIAL
【コミュニティ】→COMMUNITY
【テスト中】→TESTING

と指定すればそれぞれで認識されます。
開発中はTESTINGに、完成したらCOMMUNITYにする感じの運用になるかなと思います。


register

これはプリファレンスからアドオンを有効にするために必ず必要になる関数になります。
アドオンを有効化する際に呼ばれアドオンの登録やコンソールへの出力などを行います。

def register():
    print("サンプルアドオンが有効になりました")

今回はメニューや実際実行される関数はないためコンソールに文字列を表示させるためだけのものです。
今回はありませんが機能が追加してclass等が出てきた場合それらを登録していくのがこの部分になります。

unregister

これは逆にアドオンを無効にするために必ず必要になる関数です。
こちらも同様に今回は文字列の表示のみになります

def unregister():
    print("サンプルアドオンが無効になりました")
メイン処理

これはもはや慣習的なものでとりあえず書いとけなまさにおまじないです。
エディタ上部のテキスト->スクリプトを実行で呼ばれる関数です。
アドオンの場合必要ない部分なのでregisterだけ呼んでいます。
何かしら拡張してアドオンとしてではなく一時的に何か動作させる場合は自分で定義した関数などを呼べば【テキスト->スクリプトを実行】一時的に実行することができるものかと思われます(試していないため不明)

if __name__ == "__main__":
    register()

コード全体

bl_info = {
    "name": "サンプル",
    "author": "Yui_Aomi",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "追加→メッシュ",
    "description": "サンプルアドオン",
    "warning": "機能はありません",
    "support": "TESTING",
    "wiki_url": "sample",
    "tracker_url": "sanmple",
    "category": "Object"
}


def register():
    print("サンプルアドオンが有効になりました")


def unregister():
    print("サンプルアドオンが無効になりました")


if __name__ == "__main__":
    register()

このコードで最低限アドオンのON/OFFができるだけのものが出来上がります。機能はありません。
他にも必ず必要になるオペレータークラスとexecuteやmenu_fnなどがありますが、これらは次回に回します。

まとめ

最低限【bl_info】【register】【unregister】とメイン処理部分があればアドオンのような何かをBlenderで動作させることができるようになるらしい。
次回はこのON/OFFだけできるようになったコードに機能を追加していき、もう少しアドオンっぽい何かにして行きます。

次回
atelier-aomi.hatenablog.com

Blender Tips編 #1 -シェイプキーの諸々覚書-

どうも蒼水です。
やっとこさ本職としてるものの記事となります。

初回のためあらかじめお伝えしておきますと、自分の書いていくBlenderの記事は中級者以降向けの記事となります。そしてあくまでも自分用メモ書きです。
基本的なことはすっとばします。
同じ道を今後たどる誰かの参考になれば~というものです。

今回はキャラクターの表情関係、つまるところシェイプキーに関するTipsみたいな内容になります。
元の表情はこんなかんじです
f:id:Yui_Aomi:20200209223518p:plain

今回の目的

「ベースをいじると一部シェイプキーが壊れる場合がある」現象。
これを回避(復元)する方法がまず一つ目。

「ベースを後から入れ替えたくなった」場合の対処方法。
これが二つ目。

今回は二つをピックアップして書いていこうと思います。

おまけとしてBlender2.8の色あいなんかおかしくない?な件。解決したのでそちらも。

なんてニッチな....。
あまり遭遇しないシチュな気がしますが、自分が遭遇したのでその覚書です。

そもそもシェイプキーとは

シェイプキーはMAYA/MAXなどでいうブレンドシェイプのことです。
Unityに持って行った際もBlendShapeとしてSkinedMeshRendererに含まれます。

これは簡単に言うと移動前の頂点座標と、移動後の頂点座標の二点を線形補完してパラメータによってその位置を制御することができる機能です。
キャラクターの表情でいえば、真顔から目を閉じた状態に遷移させたり、笑顔にしたり、といった具合に利用されます。

ベースをいじると一部シェイプキーが壊れる場合がある現象の対処

例えばこれは上がもとの目が小さくなるシェイプキー。
次にベースの目をもともと小さくする。
すると目が小さくなるシェイプキーはその影響を受けてさらに小さくなる。(壊れる)
みたいなことが起こります。

f:id:Yui_Aomi:20200209215441p:plain
こわれたシェイプキー

これの対処をしていきます。

任意のシェイプキーを選択部に合成

という機能がBlenderにはもともとあったりします。
これは、ベースを含む既存のシェイプキーの一部だけを現在編集中の頂点座標として適用させることができます。
これを使ってこの壊れた目のシェイプキーを直していきます。

f:id:Yui_Aomi:20200209225347p:plain
任意のシェイプキーを選択部に合成

1)適用したい頂点の未選択状態にする
2)上にある頂点メニューから「任意のシェイプキーを選択部に合成」を選択。
3)左下に出てくるプロパティの「追加」のチェックをはずす。
4)適用したい頂点座標のあるシェイプキーを選択。(完了)

f:id:Yui_Aomi:20200209222216p:plain
目の大きさがもとの大きさに戻っている

今回は解説用に用意したものなのでちょっとアレですが、これだけでこわれた頂点情報を復元可能です。
やってることは、別のシェイプキーから頂点座標をコピーしてくるだけです。
似たケースにだいたいこれで対応できます。

ほか、別のシェイプキーからコピペしてきたい、もできるわけです。
頂点一個一個動かすのはダルいので元はあのシェイプキーから持ってきてそこからいじりたい!とかもできる。
復元だけでなくやりようによっては結構使えるけど影の薄い機能.....。もっと使われてもいいと思うんだ....。

ベースを後から入れ替える

これ、実際やるとわかると思うんですがベース入れ替えると元になる頂点情報が変わってしまうので全体的にシェイプキーがぶっ壊れます。(ベースが変わったため変更された頂点座標情報がズレる)
ベースを変えること自体はいたって簡単で、ベースにしたいシェイプキーを作成、それを最上段(ベースと入れ替え)にしてやるだけです。
下記画像ではJito(ジト目)が最上段に来ており(Basisと入れ替えた)、デフォルトがジト目になっていることがわかるかと思います。

f:id:Yui_Aomi:20200209223219p:plain
JitoとBasisが入れ替わり、ジト目がデフォルトになっている。

それでここである問題は2点。
①全体的に壊れたシェイプキーの修正の必要性
②もとのベースをシェイプキーとして残したい

というパターン。
①に関していうとこれは前項の「任意のシェイプキーを選択部に合成」で対応が可能です。が、1個ずつやらないといけないのでちょっとめんどくさいです...。

②については、これちょっと面倒で元のベースのシェイプキーは2点の情報を持っているわけではないためか反応しなくなります。
これに関しても「任意のシェイプキーを選択部に合成」が活躍してくれて次の手順を踏むことで一つのシェイプキーとして持たせることが可能です。

1)新規でシェイプキーを追加する
2)本来のデフォルトであったBasisから新規シェイプキーに合成を行う。
f:id:Yui_Aomi:20200209224109p:plain
この過程を踏むことでBasisの頂点情報が、ベース変更後の頂点の差分として認識されきちんと動作してくれるようになります。
上画像でも値が1になっており、また表情ももとのデフォルト表情になっていることがわかるかと思います。

まとめ

「任意のシェイプキーを選択部に合成」は影は薄いけどいいぞ。

おまけ

Blender2.8になってからなんかUnlit時の色がおかしいなぁ...と思っていてシェーダーの方で色弄って対応してたんですがもっと簡単な方法があった....
プロパティにあるカラーマネジメントのビュー変換という項目。
これデフォルトだとFilmicというものになっているのでこれを標準にしてあげるだけだったみたいですね...。
f:id:Yui_Aomi:20200209224921p:plain

Unityスクリプト編 #1 -動的に親子関係を作成/解除する-

今回は複数のオブジェクトを動的に親子関係する検証をしていたのでそのまとめになります。
完成図はこんなかんじになります。

今回の目的

目的としては今VR開発編でやっているハンドトラッキング関連で、手でモノを持ちたい場合に持ちたい対象を手の子にしてやれば実質「持つ」ことができるようになります。
=手を動かしたらその手にモノがくっついてくる、という挙動を取らせることができる。

最初に出した動画でうちの子の手に刀がついてきてますよね。そんな感じです。
他にもっと良い方法があれば知りたい...。

親子関係とは?

近い概念で言えばフォルダとその中にあるファイル群、みたいな関係ですね。
Unityだとこんな感じです

f:id:Yui_Aomi:20200119144533p:plain
親子関係の例
上図でいう「IKTarget」が親で、その中にある「LeftIK/RightIK」が子の親子関係です。また「LeftIK」と「RightIK」が親子関係のない状態です。
もう一例、「Blade」が親で、その中にある「Blade/saya」が子の親子関係です。

親子関係では何が起こるかといえば親を動かすと子も一緒に同様に動く(ついてくる)という点かなと思います。
親を右に動かしたら子も一緒に右に動きます。回転させれば同じように回転します。ただそれだけです。

親子関係にするためのコード(Transform.parent)

Transform.parentというものが用意されているのでこいつを使っていきます
公式のドキュメントはこちら
docs.unity3d.com


実際に自分が書いたコードがこんな感じです。(サンプルコード後で全体載せています)

 this.gameObject.transform.parent = RootObject.gameObject.transform;

こう書いてやればどうやら親子関係が作れるらしい。

 this.gameObject.transform.parent = null;

逆に親子関係を解除する場合はこう。

サンプルコード

今回自分が書いたコードの全体です。単純に動作検証用のものなので最低限しか入れていません。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParentObj : MonoBehaviour{

    public GameObject RootObject;

    // Start is called before the first frame update
    void Start(){
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.X)) {
            this.gameObject.transform.parent = RootObject.gameObject.transform;
        }

        if (Input.GetKey(KeyCode.Z)) {
            this.gameObject.transform.parent = null;
        }
    }
}

解説

今回親子関係の動作が確認できればよかったためキーボードからの入力で指定したオブジェクトを親にして、自分が子になるだけスクリプトです。
前項の親子関係にするためのコード(Transform.parent)でもはや完結している気がするのであとは割愛します。

まとめ

Transform.parent使えば親子関係ができる。以上!
思ったほど情報量がなくてなんともアレですね....

とはいえこれだけわかってればやりたいことでできることがちょっと増えるので開発に取り入れていこうかなと思います。
ハンドトラッキングの方の進捗をお楽しみに。

VR開発編 #3 -ハンドトラッキングのピンチ度合い(入力)を取ってみる-

どうも、蒼水です。
前回の記事の続きになります。
atelier-aomi.hatenablog.com

今回はハンドトラッキングでピンチ度合い、いわゆる指の閉じ具合を取ってみるところまでやってみたのでまとめていきます。

今回の目的

今回はハンドトラッキングをするうえでやはり操作していくうえで入力って必要ですよね。
Oculusホームで使えるハンドトラッキングでも指を閉じて操作すると思います。これを自分で開発するときにどうやるのか、という部分になります。
これをできるようにすることで指のジェスチャーで操作を可能にします。

OVRHandにある使える関数

GetFingerIsPinching(HandFinger finger)

この関数は指定した指が他の指を触れてるかどうかをBoolで返してくれます。
指にはそれぞれの指を指定できます。(Thumb/Index/Middle/Ring/Pinky)

基本的には親指(Thumb)を指定してやれば他の指と触れてるかどうかになるのでこれで良い気がしました。

GetFingerPinchStrength(HandFinger finger)

この関数は指定した指とのピンチ度合いをfloatで返してくれます。
こちらも同様に指の指定ができ、基本的にはThumbでいいかなと思います。

実際に数値を取って表示してみる


こちらは試しに取った値をテキストで表示してみたものです。
指の動きに合わせてそれぞれ数値が取れてるのが分かると思います。
これを使って条件分岐とかに使って処理を追加していけば何かしらできるのかなーと踏んでいました。

処理を追加してみる


これは上記の2つの関数を使って処理を追加して動かしてみたものです。

手を握ることでうちの子を手をつないでフリフリできるシステムを組んでみました。
うちの子はかわいいなぁ(迫真)

手を握るとはいえ、Thumbを指定しているので他の指をの接触が検出できれば手をつなぐことができるようにしてあります。

サンプルコード

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandGrabber : MonoBehaviour
{
   //手の指定
    public GameObject LeftHand;
    public GameObject RightHand;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void OnTriggerStay(Collider other) {

        var lefthand = LeftHand.GetComponent<OVRHand>();
        var righthand = RightHand.GetComponent<OVRHand>();

        float LPitchingStrength = lefthand.GetFingerPinchStrength(OVRHand.HandFinger.Thumb);
        float RPitchingStrength = righthand.GetFingerPinchStrength(OVRHand.HandFinger.Thumb);

        //左手操作
        if (LPitchingStrength >= 0.9) {
            this.gameObject.transform.parent = other.gameObject.transform;
        } else {
            this.gameObject.transform.parent = null;
        }

        //右手操作
        if (RPitchingStrength >= 0.9) {
            this.gameObject.transform.parent = other.gameObject.transform;
        } else {
            this.gameObject.transform.parent = null;
        }
    }
}

解説

まず前提としてこのコードではあらかじめキャラの手を動かすためのIKを使っています。
AnimatorのIK Passにチェックを入れ、それ用のスクリプトを用意していますがここでは割愛します。(別記事で書きたい)

コードとしては手がIK targetになるオブジェクトと接触しているときに閉じ具合が0.9以上の時にIK targetを手の子にすることで移動できるようにしています。
0.9以下のとき親子関係を解除してその場に固定できるようになっています。
LeftHand, RightHandはIK targeになるオブジェクトでOVR Hand Prefabを入れています。

今回はGetFingerPinchStrengthを使用していますがGetFingerPinchでも問題ない気はします。
実際最初そちらでやっていましたがほぼ同じ動きをしていました。

まとめ

基本的には「GetFingerPinchStrength()」「GetFingerPinch()」の二つを使っていくことになるのかな?と思います。
ラッキングの精度はかなり良いのですが手が重なったりするとやはり精度が落ちてしまったり、そもそも手が消えてしまうのでガタついてるのが現状ではあります。
もう少しいい感じにしたいところ....今後検証項目。

親子関係の作成とAnimator使ったIKの話を別記事で書こうかなと思います。

新年のごあいさつ2020

あけましておめでとうございます!

どうも、蒼水です。
昨年は、良くも悪くも濃密な経験を得て精神的にも肉体的にも成長した一年となりました。

本ブログは昨年12月より開設しましたが2020年より本格的に始動していきますよ...!
モデリングをはじめ、Oculus Questを用いたVR開発、プログラムの勉強など様々なことに触れて情報を共有していければと思います。

また、2020年の目標としてストアへのリリースまではしませんが、Questでうちの子と戯れられるものを形にできればと思います。
ある程度形になりましたらapkで無料配布等できればなと思っています(apk配布って大丈夫...?)

それでは本年もうちの子ともどもどうぞよろしくお願いいたします!

f:id:Yui_Aomi:20200101175151p:plain