本日は Blender の小ネタ枠です。
Bevel Curve Tools アドオンを使ってBlenderで髪の毛オブジェクトを作る手順を記事にします。
前回記事の続きです。
bluebirdofoz.hatenablog.com
UVマップの調整
毛並の方向を揃える
この状態で改めてUVマップを確認すると左右でUVマップの上下が反転していることが分かります。
毛並みの方向を揃えたいのでメッシュ上下の両端位置はUVマップ上の同じ位置に固定します。
[編集モード]で上下両端のUVマップの頂点を選択します。
頂点を固定したい位置に移動させ、[UV]->[ピン止め]で位置を固定します。
この状態で改めてUV展開を実行します。
すると同じ方向に毛並みが流れる形でUVマップを作成できました。
メッシュサイズとの比率を合わせる
UVマップの方向を揃えることができましたが、自動で展開したため、マップのサイズが各オブジェクトで最大になっています。
このため、小さいオブジェクトではテクスチャの解像度が高く、大きいオブジェクトでは低くなってしまいます。
[UVエディター]上で頂点を全選択して[拡大縮小]を実行し、サイズをオブジェクトとのサイズ比に合わせます。
処理の自動化
上記の作業を全てのメッシュに実施します。
こちらの作業もスクリプト化しました。
毛並の方向を揃える処理
以下の記事の python スクリプトの関数を全てのオブジェクトに対して処理を行います。
bluebirdofoz.hatenablog.com
# bpyインポート import bpy # メッシュ操作のため import bmesh # 選択中の頂点のUVマップの頂点を指定の位置に移動する def move_UVmap_selected(arg_objectname="Default", arg_uvname="UVMap", arg_uvpos=[0.0, 0.0]) -> bool: """選択中の頂点のUVマップの頂点をピン止めする Keyword Arguments: arg_objectname {str} -- 指定オブジェクト名 (default: {"Default"}) arg_uvname {str} -- 指定UVマップ名 (default: {"UVMap"}) Returns: bool -- 実行正否 """ # 指定オブジェクトを取得する # (get関数は対象が存在しない場合 None が返る) selectob = bpy.data.objects.get(arg_objectname) # 指定オブジェクトが存在するか確認する if selectob == None: # 指定オブジェクトが存在しない場合は処理しない return False # オブジェクトがメッシュであるか確認する if selectob.type != 'MESH': # 指定オブジェクトがメッシュでない場合は処理しない return False # 指定オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = selectob # 元々の操作モードを記録する befmode = bpy.context.active_object.mode # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # Bメッシュデータを取得する # Bメッシュアクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMesh) bmeshdata = bmesh.from_edit_mesh(selectob.data) # UVレイヤーの参照を取得する # レイヤーアクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMLayerAccessLoop) targetuvlayer = bmeshdata.loops.layers.uv.get(arg_uvname) # 指定UVマップが存在するか確認する if targetuvlayer == None: # 指定UVマップが存在しない場合は処理しない return False # メッシュの面の参照からUVマップの頂点を編集する # 面の参照を取得する # 面アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMFace) for face in bmeshdata.faces: # 要素の参照を取得する # 要素アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMElemSeq) for loop in face.loops: # 頂点が選択中か否か判定する if loop.vert.select == True: # UVレイヤーの参照を取得する loop_uv = loop[targetuvlayer] # 頂点のUV情報を取得して座標を設定する # UV情報アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMLoopUV) loop_uv.uv = arg_uvpos # メッシュ情報を更新する bmesh.update_edit_mesh(selectob.data) # 変更前のモードに移行する bpy.ops.object.mode_set(mode=befmode) return True # 選択中の頂点のUVマップの頂点をピン止めする def pin_UVmap_selected(arg_objectname="Default", arg_uvname="UVMap") -> bool: """選択中の頂点のUVマップの頂点をピン止めする Keyword Arguments: arg_objectname {str} -- 指定オブジェクト名 (default: {"Default"}) arg_uvname {str} -- 指定UVマップ名 (default: {"UVMap"}) Returns: bool -- 実行正否 """ # 指定オブジェクトを取得する # (get関数は対象が存在しない場合 None が返る) selectob = bpy.data.objects.get(arg_objectname) # 指定オブジェクトが存在するか確認する if selectob == None: # 指定オブジェクトが存在しない場合は処理しない return False # オブジェクトがメッシュであるか確認する if selectob.type != 'MESH': # 指定オブジェクトがメッシュでない場合は処理しない return False # 指定オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = selectob # 元々の操作モードを記録する befmode = bpy.context.active_object.mode # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # Bメッシュデータを取得する # Bメッシュアクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMesh) bmeshdata = bmesh.from_edit_mesh(selectob.data) # UVレイヤーの参照を取得する # レイヤーアクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMLayerAccessLoop) targetuvlayer = bmeshdata.loops.layers.uv.get(arg_uvname) # 指定UVマップが存在するか確認する if targetuvlayer == None: # 指定UVマップが存在しない場合は処理しない return False # メッシュの面の参照からUVマップの頂点を編集する # 面の参照を取得する # 面アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMFace) for face in bmeshdata.faces: # 要素の参照を取得する # 要素アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMElemSeq) for loop in face.loops: # 頂点が選択中か否か判定する if loop.vert.select == True: # UVレイヤーの参照を取得する loop_uv = loop[targetuvlayer] # 頂点のUV情報を取得してピンを設定する # UV情報アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMLoopUV) loop_uv.pin_uv = True # メッシュ情報を更新する bmesh.update_edit_mesh(selectob.data) # 変更前のモードに移行する bpy.ops.object.mode_set(mode=befmode) return True # Y軸座標が最大または最小の座標の頂点を選択する def select_vert_Ytip(arg_objectname="Default", arg_uvname="UVMap", arg_max=False, arg_selectmargin=0.01) -> bool: """Y軸座標が最大または最小の座標の頂点を選択する Keyword Arguments: arg_objectname {str} -- 指定オブジェクト名 (default: {"Default"}) arg_uvname {str} -- 指定UVマップ名 (default: {"UVMap"}) arg_max {bool} -- 最大値の端か否か (default: {True}) Returns: bool -- 実行正否 """ # 指定オブジェクトを取得する # (get関数は対象が存在しない場合 None が返る) selectob = bpy.data.objects.get(arg_objectname) # 指定オブジェクトが存在するか確認する if selectob == None: # 指定オブジェクトが存在しない場合は処理しない return False # オブジェクトがメッシュであるか確認する if selectob.type != 'MESH': # 指定オブジェクトがメッシュでない場合は処理しない return False # 指定オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = selectob # 元々の操作モードを記録する befmode = bpy.context.active_object.mode # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # Bメッシュデータを取得する # Bメッシュアクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMesh) bmeshdata = bmesh.from_edit_mesh(selectob.data) # 選択モードを頂点選択モードにする bmeshdata.select_mode = {'VERT'} # 頂点を走査する # 頂点アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMVert) for vert in bmeshdata.verts: # 全ての頂点は一旦選択状態を解除する vert.select = False # Y座標の比較用変数を初期化 Ytip_pos = 0.0 Ytip_checked = False # 頂点を走査する # 頂点アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMVert) for vert in bmeshdata.verts: # 1つ目の座標は基準として取得する if Ytip_checked == False: # 座標の値とインデックスを取得する Ytip_pos = vert.co[1] Ytip_checked = True else: # Y軸座標が最大または最小の座標かチェックする if (vert.co[1] > Ytip_pos) == arg_max: # 座標の値とインデックスを取得する Ytip_pos = vert.co[1] # インデックス参照のため、テーブルを初期化する bmeshdata.verts.ensure_lookup_table() # 再び頂点を走査する for vert in bmeshdata.verts: # Y軸座標が最大または最小の座標を選択する if arg_max: if (vert.co[1] > (Ytip_pos - arg_selectmargin)): vert.select = True else: if ((Ytip_pos + arg_selectmargin) > vert.co[1]): vert.select = True # 辺または面の選択状態を更新する bmeshdata.select_flush_mode() # 変更前のモードに移行する bpy.ops.object.mode_set(mode=befmode) return True # UV展開を実行する(デフォルト設定) def project_UVmap_mesh(arg_objectname="Default") -> bool: """UV展開を実行する(デフォルト設定) Keyword Arguments: arg_objectname {str} -- 指定オブジェクト名 (default: {"Default"}) Returns: bool -- 実行正否 """ # 指定オブジェクトを取得する # (get関数は対象が存在しない場合 None が返る) selectob = bpy.data.objects.get(arg_objectname) # 指定オブジェクトが存在するか確認する if selectob == None: # 指定オブジェクトが存在しない場合は処理しない return False # オブジェクトがメッシュであるか確認する if selectob.type != 'MESH': # 指定オブジェクトがメッシュでない場合は処理しない return False # 不要なオブジェクトを選択しないように # 全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select_set(False) # 指定のオブジェクトのみを選択状態にする selectob.select_set(True) # 対象オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = selectob # 元々の操作モードを記録する befmode = bpy.context.active_object.mode # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # 頂点を全選択した状態とする bpy.ops.mesh.select_all(action='SELECT') # デフォルト設定のUV展開を実行する bpy.ops.uv.unwrap() # オブジェクトモードに移行する bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # 変更前のモードに移行する bpy.ops.object.mode_set(mode=befmode) return # 全データオブジェクトのUVマップを展開する for item in bpy.data.objects: # メッシュのUVマップを展開する project_UVmap_mesh(arg_objectname=item.name) # メッシュ上のY軸下端の頂点を選択する select_vert_Ytip(arg_objectname=item.name, arg_uvname="UVMap", arg_max=False) # 対応するUVメッシュの頂点座標を移動する move_UVmap_selected(arg_objectname=item.name, arg_uvname="UVMap", arg_uvpos=[0.5, 0.0]) # 対応するUVメッシュの頂点座標をピン止めする pin_UVmap_selected(arg_objectname=item.name, arg_uvname="UVMap") # メッシュ上のY軸上端の頂点を選択する select_vert_Ytip(arg_objectname=item.name, arg_uvname="UVMap", arg_max=True) # 対応するUVメッシュの頂点座標を移動する move_UVmap_selected(arg_objectname=item.name, arg_uvname="UVMap", arg_uvpos=[0.5, 1.0]) # 対応するUVメッシュの頂点座標をピン止めする pin_UVmap_selected(arg_objectname=item.name, arg_uvname="UVMap") # メッシュのUVマップを再展開する project_UVmap_mesh(arg_objectname=item.name)
毛並の方向を揃える処理
以下の記事の python スクリプトの関数を全てのオブジェクトに対して処理を行います。
bluebirdofoz.hatenablog.com
# bpyインポート import bpy # メッシュ操作のため import bmesh # numpyインポート(ベクトルの長さ計算のため) import numpy as np # オブジェクトのバウンドボックスから指定軸の対角線の長さを計算する def get_object_diagonal(arg_objectname="Default", arg_x=True, arg_Y=True, arg_Z=True) -> float: """オブジェクトのバウンドボックスから対角線の長さを計算する Keyword Arguments: arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"}) arg_x {bool} -- X軸の指定 (default: {True}) arg_Y {bool} -- Y軸の指定 (default: {True}) arg_Z {bool} -- Z軸の指定 (default: {True}) Returns: float -- 対角線の長さ """ # 指定オブジェクトを取得する # (get関数は対象が存在しない場合 None が返る) selectob = bpy.data.objects.get(arg_objectname) # 指定オブジェクトが存在するか確認する if selectob == None: # 指定オブジェクトが存在しない場合は処理しない return 0.0 # X軸の寸法の取得 dimension_x = 0.0 if arg_x: dimension_x = selectob.dimensions[0] # Y軸の寸法の取得 dimension_y = 0.0 if arg_Y: dimension_y = selectob.dimensions[1] # Z軸の寸法の取得 dimension_z = 0.0 if arg_Z: dimension_z = selectob.dimensions[2] # 対角線の配列を取得する dimension = np.array([dimension_x, dimension_y, dimension_z]) # 対角線の長さを取得する diagonal = np.linalg.norm(dimension) return diagonal # UVマップを指定値で拡大縮小する def change_UVmap_scale(arg_objectname="Default", arg_uvname="UVMap", arg_scale=1.0) -> bool: """UVマップを指定値で拡大縮小する Keyword Arguments: arg_objectname {str} -- 指定オブジェクト名 (default: {"Default"}) arg_uvname {str} -- 指定UVマップ名 (default: {"UVMap"}) arg_scale {float} -- 拡大縮小倍率 (default: {1.0}) Returns: bool -- 実行正否 """ # 指定オブジェクトを取得する # (get関数は対象が存在しない場合 None が返る) selectob = bpy.data.objects.get(arg_objectname) # 指定オブジェクトが存在するか確認する if selectob == None: # 指定オブジェクトが存在しない場合は処理しない return False # オブジェクトがメッシュであるか確認する if selectob.type != 'MESH': # 指定オブジェクトがメッシュでない場合は処理しない return False # 指定オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = selectob # 元々の操作モードを記録する befmode = bpy.context.active_object.mode # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # Bメッシュデータを取得する # Bメッシュアクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMesh) bmeshdata = bmesh.from_edit_mesh(selectob.data) # UVレイヤーの参照を取得する # レイヤーアクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMLayerAccessLoop) targetuvlayer = bmeshdata.loops.layers.uv.get(arg_uvname) # 指定UVマップが存在するか確認する if targetuvlayer == None: # 指定UVマップが存在しない場合は処理しない return False # メッシュの面の参照からUVマップの頂点を編集する # 面の参照を取得する # 面アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMFace) for face in bmeshdata.faces: # 要素の参照を取得する # 要素アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMElemSeq) for loop in face.loops: # UVレイヤーの参照を取得する loop_uv = loop[targetuvlayer] # 頂点のUV情報を取得して座標を拡大縮小する # UV情報アクセスのマニュアル # (https://docs.blender.org/api/current/bmesh.types.html#bmesh.types.BMLoopUV) loop_uv.uv[0] *= arg_scale loop_uv.uv[1] *= arg_scale # 中心位置を調整する if arg_scale > 1.0: loop_uv.uv[0] -= ((arg_scale - 1.0) / 2.0) loop_uv.uv[1] -= ((arg_scale - 1.0) / 2.0) else: loop_uv.uv[0] += ((1.0 - arg_scale) / 2.0) loop_uv.uv[1] += ((1.0 - arg_scale) / 2.0) # メッシュ情報を更新する bmesh.update_edit_mesh(selectob.data) # 変更前のモードに移行する bpy.ops.object.mode_set(mode=befmode) return True # 最大サイズのオブジェクト長を取得する max_size = 0.0 for item in bpy.data.objects: get_size = get_object_diagonal(arg_objectname=item.name, arg_x=True, arg_Y=True, arg_Z=True) if get_size > max_size: max_size = get_size # 最大サイズのオブジェクトとの比率に合わせてUVマップのサイズを調整する for item in bpy.data.objects: get_size = get_object_diagonal(arg_objectname=item.name, arg_x=True, arg_Y=True, arg_Z=True) change_size = get_size / max_size change_UVmap_scale(arg_objectname=item.name, arg_uvname="UVMap", arg_scale=change_size)
これでBevel Curve Tools アドオンを使って作ったオブジェクトを元に、毛皮オブジェクトを作成することができました。