2017年12月13日水曜日

Blenderで「やり直しの効く」モデリング-シルエットから入るモデリングの話

Blender Advent Calendar 2017の13日の記事です
先日 BlendxJPというユーザーミーティングイベントがあったのは前の記事で話題にしたことですが、その中のMax Pulieroさんの登壇で正に「目から鱗」だったので
一月以上経ってしまいましたが、自分のやり方と絡めて書き留めておきます

会場でのものとは少し異なりますが
MaxさんがYouTubeにアップされているメイキング動画を中心に話をしたいと思います。
最初に話題にしたいのは5分頃までのBlenderでの作業です
私の初見では技術的な部分のみ目が行って何のためにこの操作をしてるのかというロジカルな部分に考えていませんでした

動画中では一瞬で終わってるのですが肉の塊のベースになるオブジェクトを作って
そのベースオブジェクトを複製して変形し肉の塊を重ねることで形を構成しているようです

動画ではキューブにサブデビジョンを加えるところからスタートして
度々Ctrl+Lで作成した他のオブジェクトに設定したモデファイアを適応させていますが
会場では 下図のような
  • 3段階のサブデビジョンを加えたキューブ
  • 上記キューブを複製してミラーモデファイアを加えた状態のもの
を配置したものから始めていました
ここからスカルプトでコネコネしている途中がこんな様子

ここから全体のフォルムを作り込んでいき
手などのパーツはライブラリ化したものを使用したりもしているようです

ちなみに 動画中でのスカルプトでは G(grab)とS(smooth)のブラシを頻繁に切り替えた操作をしているようです
そうして作られたものがこのような状態に。
全体のシルエットが出来上がった段階で一旦クライアントのチェックに出すそうです


さて、この固まりから作っていく方式メリットは会場でも
「早い段階でクライアント等にシルエットを見せることができる」
「全体が一塊よりオブジェクトに分かれているもの方がスカルプトが軽い」
等のことはおっしゃってましたが
これは純粋にMaxさんの作業が速いということだけでなく、
パーツ毎に編集が効くことと、後述するモデファイア活用と併せて
やり直しが効く
ことが大きいかと思います



Maxさんのものでは比較的ハイポリゴンなクリーチャーでしたが
ここで私の場合の例をあげてみたいと思います
比較的ローポリゴンなキャラクターモデルのテンプレートにこういったものを私は使用しています
髪 袖 ズボン ローブ状の上着などです
服装のテンプレートとして これのメッシュを使うこともありますが
プロポーショナル変形や スカルプト等を使って全体のフォルムを調整した後
次記事で話題にする 面スナップ等使ってポリゴンモデルにしていっています

プリミティブから頂点を作成する場合に比べて 全体のバランスと メッシュの流れを個別に考えて作業ができるので
手戻りが少なく作れるのでお勧めしたい手法です


というわけで 記事が長くなってしまったので次の記事に分けたいと思います

2017年11月6日月曜日

選択オブジェクトの一括結合

先日BlendXJPというBlenderのユーザーイベントがあったのですが
その中で登壇された方のモデリング作業で
モデファイアを活用して組み上げたモデルを1つのオブジェクトにまとめる作業を
一つ一つ変換かけた後に結合していたようだったので
自分が今使ってるスクリプトをアップしておきます
(理解が違ったら恥ずかしい限りですが)

オブジェクトモードで選択しているもののモデファイアを適応した状態のものを
一つのメッシュオブジェクトにまとめるという動作になります

自分の用途では細分化のモデファイアだけは適応していない状態で結合してほしいので実行しない処理になっています
import bpy
bl_info = {
    "name": "duplicate modified mesh objects",
    "author": "Y",
    "version": (3, 0),
    "blender": (2, 60, 0),
    "location": "3Dビュー > オブジェクト",
    "description": "apply modifier on selected objects and join objects",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}

# 翻訳用辞書
translation_dict = {
    "en_US" :
        {("*", 'duplicate modified mesh') : 'duplicate modified mesh'},
    "ja_JP" :
        {("*", 'duplicate modified mesh') : '結合したメッシュを作成'} }
#モデファイアを適応した複製を作成
def duplicate_modified(obj):
    bpy.ops.object.select_all(action = 'DESELECT')
    bpy.context.scene.objects.active = obj
    obj.select = True
    bpy.ops.object.duplicate()
    for mod in obj.modifiers:
        if  "Subsurf" in mod.name:continue
        try:
            bpy.ops.object.modifier_apply(modifier = mod.name)
        except: pass
    if obj.type == "CURVE":
        bpy.ops.object.convert(target='MESH')
    return(bpy.context.active_object)

#結合オブジェクトの作成
def joined_obj():
    serected_objects = bpy.context.selected_objects
    duplicate_objects = []
    #モデファイアの適応
    for obj in serected_objects:
        if obj.type in ["MESH","CURVE"]: 
            duplicate_objects.append( duplicate_modified(obj))
    #複製を選択
    for obj in duplicate_objects:
        obj.select = True
    #結合
    bpy.ops.object.join()

#モデファイアを適応して結合
class y_join_modified(bpy.types.Operator):
    bl_idname = "object.join_modified"
    bl_label = bpy.app.translations.pgettext('duplicate modified mesh')
    
    def execute(self, context):
        joined_obj()
        return {'FINISHED'}
def menu_funk(self, context):
    self.layout.operator(y_join_modified.bl_idname)

# 有効化時の処理
def register():
    bpy.app.translations.register(__name__, translation_dict)   # 翻訳辞書の登録
    bpy.utils.register_module(__name__)
    #オブジェクト>適応にメニュー項目を追加
    bpy.types.VIEW3D_MT_object_apply.append(menu_funk)
    

# 無効化時の処理
def unregister():
    bpy.app.translations.unregister(__name__)   # 辞書の削除
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_MT_snap.remove(menu_funk)
################################################    


# メイン処理
if __name__ == "__main__":
    register()
    #joined_obj()
スクリプトエディッタで実行するか アドオンとして読み込むと オブジェクト>適応 の中に duplicate modified mesh という項目が追加されます

2017年8月22日火曜日

グリースペンシルからメッシュ

画像を基準にメッシュを作りたくて作成したアドオンを公開します。

画像のアルファから境界線を判別するものも作成したのですが
今回はグリースペンシルで囲った範囲にメッシュを作るものになります


メッシュ作成部分はCOA Toolsアドオンのコードを参考に細かい三角メッシュで埋めるようになっています

bl_info = {
    "name": "grease_pencil storoke to mesh",
    "description": "UV/画像エディッタの上のグリースペンシルのストロークからメッシュの作成",
    "author": "Yukimi",
    "version": (0,2),
    "blender": (2, 6, 0),
    "location": "Object",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"}

import bpy
import bmesh
def triangle_fill(bm):
    #三角面作成
    triangle_fill = bmesh.ops.triangle_fill(bm,edges=bm.edges,use_beauty=True)
    if triangle_fill["geom"] == []:
        return False
    else:
        return True

def average_edge_cuts(bm,cuts=1):
    ### collapse short edges
    edges_len_average = 0
    edges_count = 0
    shortest_edge = 10000
    for edge in bm.edges:
        if True:#edge.is_boundary:
            edges_count += 1
            length = edge.calc_length()
            edges_len_average += length
            if length < shortest_edge:
                shortest_edge = length
    edges_len_average = edges_len_average/edges_count

    subdivide_edges = []
    for edge in bm.edges:
        cut_count = int(edge.calc_length()/shortest_edge*0.5)*cuts
        if cut_count < 0:
            cut_count = 0
        if not edge.is_boundary:
            subdivide_edges.append([edge,cut_count])
    for edge in subdivide_edges:
        bmesh.ops.subdivide_edges(bm,edges=[edge[0]],cuts=edge[1])

def smooth_verts(bm):
    ### smooth verts
    smooth_verts = []
    for vert in bm.verts:
        if not vert.is_boundary:
            smooth_verts.append(vert)
    for i in range(50):   
        bmesh.ops.smooth_vert(bm,verts=smooth_verts,factor=1.0,use_axis_x=True,use_axis_y=True,use_axis_z=True)    

def bm_pos_to_uv(bm):
    ##UVの設定
    uv_list = [[vert.co[0], vert.co[1]] for vert in bm.verts]
    uv_layer = bm.loops.layers.uv.verify()
    bm.faces.layers.tex.verify()
    for bm_face in bm.faces:
        for v in bm_face.loops:
            id = v.vert.index
            v[uv_layer].uv = uv_list[id]

def stroke_to_mesh(stroke, img):
    me = bpy.data.meshes.new('gpenMesh') 
    #オブジェクトの作成
    obj = bpy.data.objects.new(img.name, me) 
    #オブジェクトをシーンにリンク
    bpy.context.scene.objects.link(obj)
    bm = bmesh.new() # BMeshから空のメッシュを作成
    verts = []
    #bmesh頂点データの作成
    for p in stroke.points:
        pos = p.co
        v = bm.verts.new( [pos[0], pos[1], 0.0] )
        verts.append(v)
    bm.verts.index_update()
    #エッジデータの作成
    for i in range(len(verts)-1):
        bm.edges.new( (verts[i], verts[i +1]) )
    bm.edges.new((verts[-1], verts[0]))
    #メッシュで埋める
    fill_ok = triangle_fill(bm)#三角面作成
    if fill_ok:
        average_edge_cuts(bm)
        bm.verts.index_update()
        bmesh.ops.triangulate(bm,faces=bm.faces)
        smooth_verts(bm) #頂点のスムーズ
        bm_pos_to_uv(bm) #UVの設定
    bm.to_mesh(me) #メッシュの作成
    # 画像をUVにリンク
    for i in range( len(obj.data.polygons) ):
        obj.data.uv_textures[0].data[i].image = img
    return( obj )
############################################
class GpenToMesh(bpy.types.Operator):
    '''add Roop  '''
    bl_idname = "action.gpen2mesh"
    bl_label = "make mesh from grease_pencil"
    def execute(self, context):
        #UV/Imageエディッタで使用中のGreasePencil
        gpen_strokes = context.active_gpencil_layer.active_frame.strokes
        #UV/Imageエディッタで表示中の画像
        img = context.edit_image
        for stroke in gpen_strokes:
            stroke_to_mesh(stroke,img)
        return {'FINISHED'}
#メニューへの登録
def mesh_menu_func(self, context):
    self.layout.operator("action.gpen2mesh", 
        text="GreasePencilからメッシュ作成" )
        
def register():
    bpy.utils.register_module(__name__)
    bpy.types.IMAGE_MT_image.prepend(mesh_menu_func)
 
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.INFO_MT_file_import.remove(mesh_menu_func)
 
if __name__ == "__main__":
    #unregister()
    register()

############################################

コードを実行するとかアドオンとして登録すると
UV/画像エディッタの画像メニューに「GreasePencilからメッシュ作成」という項目が登録されます

今回のコードでは 上記理由でUV/画像エディッタで囲った範囲を元に
XY平面上の0から1.0の範囲にメッシュを作成するようになっていますが
少しコードを変更すれば 3Dビューの方で描いたグリースペンシルに沿ってメッシュを作成することもできるかと思います
グリースペンシルの頂点のデータをそのまま使うため
ドローモードで描いたストロークでは細かくなりすぎるため多角形モードで描いた方がいいかもしれません

いかがでしょうか

2017年7月1日土曜日

ボーンからメッシュ形状を作成するアドオン

Blenderでボーンのアニメーションを作るにあたって
簡易的な形状でレンダリング等、 ボーンを基準にメッシュが作成できればと思うことが多かったので作成してみました。

Blenderでは簡易形状の作成だけなら
メッシュオブジェクトでボーン状に線ポリゴンを作成して スキン(Skin)モデファイアをつけて
「アーマーチュアを作成」ボタンでアーマーチュアを作成する手順もあります

ただ 自分のやりたかった用途では操作の手順が煩雑な上に
関節部分の形状や ひとかたまりの形状しか作れない等のデメリットがありました

そこでボーン形状からのモデリングです
Blenderではアーマチュアの表示形式が選べて デフォルトは八面体ですが
今回はエンベロープ表示を利用します

Blenderのボーンはヘッドとテイルで構成されていて
画面の表示は球形のヘッドとテイルを表す球体と 間を結ぶボディ形状という形になっています

編集モードでこの球を選択してAlt+Sを押すと径を変更することができます
プロパティシェルフで大きさを数値指定することもできますが、ボーンを移動させた時に数値が元に戻ってしまう等、不具合があるようです

位置や大きさを調整することで普通にモデリングするよりも素早く人体っぽい感じの見た目のものを作れるかと思います
ただし、これはアーマチュアですのでレンダリングしても表示されないですし
モデリングのスナップ対象にすることもできません

そこで作ったのがこのアドオンです
bl_info = {
    "name": "activebone to mesh",
    "description": "形状をカメラ方向から投影",
    "author": "Yukimi",
    "version": (0,2),
    "blender": (2, 6, 0),
    "location": "Object",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"}
import bpy
import math
from mathutils import Vector, Matrix
class active_bone_to_mesh(bpy.types.Operator):
    bl_idname = "object.active_bone_to_mesh"
    bl_label = "選択ボーンからメッシュ"
    bl_description = "選択したボーンのエンベロープを元にカプセル形状を作成"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        if context.mode == 'EDIT_ARMATURE':
            obj = context.active_object
            #グローバル座標への変換マトリクス
            matrix_world = obj.matrix_world
            #エディットモードの状態で情報を更新
            obj.update_from_editmode()
            bones = obj.data.bones
            for b in bones:
                if b.select:
                    head_radius = b.head_radius
                    tail_radius = b.tail_radius
                    distance = (b.tail_local -b.head_local).length
                    mat = matrix_world*b.matrix_local
                    (vertices,faces) = get_cupsule_base(head_radius, tail_radius, distance)
                    #座標の変換
                    vertices = [mat*Vector(v) for v in vertices]
                    name = b.name
                    #メッシュの作成
                    mesh_obj = add_mesh_from_data(name,vertices,faces)
                    #作成元のボーンのウエイトを付加
                    v_group = mesh_obj.vertex_groups.new(name)
                    v_index = list( range( len(vertices) ) )
                    v_group.add(v_index, 1.0, 'REPLACE')
                    #モデファイアの付加
                    mesh_obj.modifiers.new("Armature", 'ARMATURE')
                    mesh_obj.modifiers["Armature"].object = obj
        return {'RUNNING_MODAL'}
#選択したボーンからカプセル形状を作成
def get_cupsule_base(head_radius, tail_radius, distance, roat_div = 12):
    #平行断面の座標を計算
    r_pos = []
    #両端の球面の分割数
    spire_dev= 10
    delta_rad = math.pi/spire_dev
    #ヘッド側の球面上
    for i in range(spire_dev-1, spire_dev//2 , -1):
        t = delta_rad * float(i)
        pos = [head_radius *math.cos(t), head_radius *math.sin(t)]
        r_pos.append( pos )
    #ヘッドとテールの間を結ぶ線の区間
    #球を結ぶ区間の分割数
    span_div = 4
    P_1 = [0,head_radius]
    P_2 = [distance, tail_radius]
    for i in range(span_div):
        t = float(i)/ span_div
        pos_x = (1-t)*P_1[0] + t*P_2[0]
        pos_y = (1-t)*P_1[1] + t*P_2[1]
        r_pos.append( [pos_x, pos_y] )
    #テール側の球面上
    for i in range((spire_dev//2 -1), 0 , -1):
        t = delta_rad * float(i+1)
        pos = [tail_radius *math.cos(t) +distance, tail_radius *math.sin(t)]
        r_pos.append( pos )
    #回転座標の計算
    pos_list = []
    for r in range(roat_div):
        t = 2* r *math.pi/roat_div
        for p in r_pos:
            pos = [ p[1] *math.cos(t), p[0], p[1] *math.sin(t) ]
            pos_list.append(pos)
    face_list = []
    #面のデータを作成
    #軸方向の頂点数
    n_count = len(r_pos)
    #両端の面データを作成
    f = [i* n_count for i in range(roat_div)]
    face_list.append(f)
    f = [((i+1)* n_count-1) for i in reversed(range(roat_div))]
    face_list.append(f)
    #回転部分の面データの作成
    for r in range(roat_div-1):
        for t in range(n_count-1):
            #面の頂点id
            f = [r*n_count + t, r*n_count + t+1, (r+1)*n_count +t +1 , (r+1)*n_count +t]
            face_list.append(f)
    #回転部分の面を閉じる列の面データ
    for t in range(n_count-1):
        f = [(roat_div-1)*n_count + t, (roat_div-1)*n_count + t+1, t +1, t ]
        face_list.append(f)
    return(pos_list, face_list)
#データからメッシュを作成
def add_mesh_from_data(name,vertices,faces):
    mesh = bpy.data.meshes.new(name)
    obj = bpy.data.objects.new(name, mesh)
    bpy.context.scene.objects.link(obj)
    mesh.from_pydata(vertices, [], faces)
    mesh.update()
    return( obj )

################################################
#メニュー項目の設定
def menu_funk(self, context):
    self.layout.operator("object.active_bone_to_mesh")
 # アドオン有効化時の処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.INFO_MT_edit_armature_add.append(menu_funk)
# アドオン無効化時の処理
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.INFO_MT_edit_armature_add.remove(menu_funk)
################################################

アーマーチュアの編集モードのメニューの「追加」の項目に「選択ボーンからメッシュ」
という項目が追加されます
ボーン一つ一形状でヘッド テイルの設定に合わせた半球とそれを結ぶ形状を作り
元ボーンに合わせた頂点ウエイトやアーマチュア変形のモデファイアを付加してあります

2017年6月8日木曜日

炎パーティクルメモ



Blenderで思い通りの炎を作りたくてテストしてるうちに
意図とは違うものの面白いものができたのでメモ

炎の発生源をパーティクルで配置したオブジェクトしたものにする方式。

パーティクルのレンダーオブジェクトに指定する形状
適当な形状からパーティクルを発生させて
物理演算の煙を有効に フローのタイプを炎フローソースを設定したパーティクルに
炎以外の周りの部分が白くならないように発生源のマテリアルを設定
「高解像度の煙」は計算量が増えるので品質とのトレードオフ
でも、これがないと個人的に好みの煙等にはほど遠い。
立ち上る炎にはしないのでドメインの温度差は0.01以下

2017年5月10日水曜日

XY座標をUVに

Blenderの作業で3Dビュー上の頂点座標をUVマップの値に変換する必要があって
アドオンを作ってみたのでメモ。

厳密に値を合わせるのでなければ通常の「プロジェクション」展開や
たまに使う程度なら UV投影(UVProject)モデファイアを使えばいいので
これ自体を他の人が使うことはなさそうですが UV設定のスクリプトの参考に残しておきます。

bl_info = {
    "name": "projection UV as XY plane",
    "description": "XY平面の座標をUVに",
    "author": "Yukimi",
    "version": (0,2),
    "blender": (2, 6, 0),
    "location": "Object",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"}

import bpy
class UV_ptoject_xy(bpy.types.Operator):
    bl_idname = "object.uv_ptoject_xy"
    #bl_label = "Projection UV  XY-plane"
    bl_label = "XY平面の座標をUVに"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        #形状の取得
        obj = context.active_object
        #グローバル座標への変換マトリクス
        matrix_world = obj.matrix_world
        #頂点座標の取得
        location_list = [v.co*matrix_world for v in obj.data.vertices]
        uv_list = [p[:2] for p in location_list]
        #UVレイヤがない場合は新規作成
        if obj.data.uv_layers.active_index == -1:
            obj.data.uv_textures.new()
        mode = context.mode
        if  (mode == 'OBJECT'):
            self.on_objmode(obj, uv_list)
        elif (mode == 'EDIT_MESH'):
            self.on_editmode(obj, uv_list)
        return {'RUNNING_MODAL'}
    def on_editmode(self, obj, uv_list):
        """
        編集モードでのUVの設定
        """
        #状態の更新
        bpy.context.edit_object.update_from_editmode()
        import bmesh
        mesh = obj.data
        bm = bmesh.from_edit_mesh(mesh)   # メッシュからBMeshを作成
        #アクティブなUV_layer
        #(bmeshで使うにはUV LayerもBMeshオブジェクトでないといけない)
        uv_layer = bm.loops.layers.uv.active
        #UVの設定
        for bm_face in bm.faces:
            for v in bm_face.loops:
                id = v.vert.index
                v[uv_layer].uv = uv_list[id]
        bmesh.update_edit_mesh(mesh)
            
                    
    def on_objmode(self, obj, uv_list):
        """
        オブジェクトモードでのUVの設定(アドオンでは未使用)
        """
        #アクティブなUVレイヤを取得
        uv_index =obj.data.uv_layers.active_index
        uv_layer = obj.data.uv_layers[uv_index]
        #UVの設定
        for f in obj.data.polygons:
            for i,loop_id in enumerate(f.loop_indices):
                uv_layer.data[ loop_id ].uv = uv_list[i]

################################################
#メニュー項目の設定
def menu_funk(self, context):
    self.layout.operator("object.uv_ptoject_xy")
 # アドオン有効化時の処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.append(menu_funk)
 
# アドオン無効化時の処理
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.remove(menu_funk)
################################################    
 
 
# メイン処理
if __name__ == "__main__":
    register()

編集モード時のUVマッピングメニューの末尾に登録しています

非常に単純な機能ですが
UVの取得は少し手順が多いのと
エディットモードでのデータを操作するのにbmeshを使って "bmesh.from_edit_mesh(mesh)" で今ある形状からBmeshオブジェクトを作成し
"bmesh.update_edit_mesh(mesh)" で変更を適応する
といったことは やり方を忘れてしまいがちですね

2017年1月18日水曜日

3DカーソルをZY平面上に移動

ミラーモデファイアを使ってオブジェクトを編集しているときなどに
軸になる位置に3Dカーソルを移動したい場合がありますが
標準の機能にはそういった機能はないようなのでアドオンで作りました


bl_info = {
    "name": "カーソルをオブジェクトのZY平面に",
    "author": "Yukimi",
    "version": (1, 0),
    "blender": (2, 60, 0),
    "location": "View3D > Cursor",
    "description": "3DカーソルをオブジェクトのXY平面上に移動",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "3D View"
}
import bpy
#カーソルのX値を0に
class set_cursor_xy_plane(bpy.types.Operator):
    bl_idname = "cursor.set_cursor_x"
    bl_label = "カーソルをZY平面に"
    def execute(self, context):
        active_obj = bpy.context.active_object
        space = bpy.context.space_data
        if active_obj:
            space.cursor_location[0] = active_obj.location.x
        else:
            space.cursor_location[0] = 0
        return {'FINISHED'}
################################################
def menu_funk(self, context):
    self.layout.operator("cursor.set_cursor_x")

def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_snap.append(menu_funk)

# アドオン無効化時の処理
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_MT_snap.remove(menu_funk)
################################################    


# メイン処理
if __name__ == "__main__":
    register()
Shift+Sのスナップメニューに追加する形式にしてみました

ミラーで円形状になるものを編集している時に
「カーソル>選択物」 「カーソルをZY平面に」 と順に実行することで
円の中心部分にカーソルを移動できます

オブジェクトが回転している場合には現状対応していませんが
いかがでしょうか