本日はモンスター型モデルの作成枠です。
今回は仕上げ処理の自動化を記事にします。
前回記事の続きです。
bluebirdofoz.hatenablog.com
モデルの最終仕上げ
前回の頭部オブジェクトの作成手順に従って、手足や体など、全てのメッシュを作成しました。
全てスクリプトとシュリンクラップを用いたモデリングで、それぞれハイポリゴンメッシュとローポリゴンメッシュがあります。
ここから以下の手順を行って、最終的な3Dモデルに出力を行います。
1.ハイポリゴンメッシュからローポリゴンメッシュにノーマルマップをベイクする
2.同じテクスチャ参照のノーマルマップを合成して一つの画像に出力する
3.出力したノーマルマップをマテリアルのテクスチャにジオメトリ情報として登録する
4.シュリンクラップのモディファイアを適用した後、ハイポリゴンメッシュを削除する
5.残ったメッシュを結合する
しかし、これらの作業を全て手作業で行うと時間がかかります。
一度だけなら良いですが、モデルの微修正を掛けるたび、同じ作業を実施するのは面倒です。
そこでこれらの手順を python スクリプトに落とし込んでみました。
・normal_bake_HoloMon.py(自動化スクリプト)
# sysインポート(引数取得のため) import sys # osインポート(ファイルパス取得のため) import os # shutilインポート(ファイルコピーのため) import shutil # mathインポート(角度計算のため) import math # bpyインポート(Blender操作のため) import bpy # bpyインポート(頂点操作のため) import bmesh # cv2インポート(画像処理のため) import cv2 # numpyインポート(画像作成のため) import numpy as np # PILインポート(画像作成のため) from PIL import Image # テスト関数 def main(): # 指定の組合せでメッシュを処理する combo_meshs01=['ArmClaw','ArmClawSphere','Arm_Hand','Arm_Lower','Arm_Upper', 'LegClaw','LegClawSphere','Leg_Toes','Leg_Lower','Leg_Upper'] combo_meshs02=['Head','Body_Chest','Body_Hip','Fang_Lower','Fang_Upper', 'Eye_Hitomi','Eye_Sirome'] # テスト用 #combo_meshs01=['Arm_Hand','Arm_Lower'] #combo_meshs02=['Head','Body_Chest'] # 組み合わせリストを作成する combo_meshs_list=[combo_meshs01,combo_meshs02] # その他追加オブジェクトを指定する addtarget_meshs=['Low_Eye_Trans','Low_Fang_Lower','Low_Fang_Upper', 'Low_HeadAntenna','Low_HeadArmor','Low_ZFur_Body','Low_ZFur_Tail'] # テクスチャディレクトリ image_dir='D:\\BlenderDir\\HoloMonProject\\01_SVN_HoloMon01\\trunk\\BlenderProject\\Texture' # 結合オブジェクト名 jointobj_name='JointModel' # 全てのリストに対して処理を行う for combo_meshs in combo_meshs_list: for combo_mesh in combo_meshs: # 以下の名称を作成する # ベイク元オブジェクト名,ベイク先オブジェクト名,ベイク画像名 bake_objectfrom_name = 'High_' + combo_mesh bake_objectto_name = 'Low_' + combo_mesh bake_image_name = 'NormalMap_' + combo_mesh # HighからLowへのノーマルベイクを実行する bake_normal_select2active( arg_sourcename=bake_objectfrom_name,arg_destinationname=bake_objectto_name, arg_savefiledir=image_dir, arg_savefilename=bake_image_name) # 画像の結合 # 全てのリストに対して処理を行う for combo_meshs in combo_meshs_list: # 統合画像ファイル名の作成 joint_image_filename = 'NormalMap_Joint_' + combo_meshs[0] + '.png' # 画像番号のカウント meshloop=0 for combo_mesh in combo_meshs: # 以下の名称を作成する # ベイク画像ファイル名 bake_image_filename = 'NormalMap_' + combo_mesh + '.png' if meshloop == 0: # 1つ目の画像はファイルコピーのみ shutil.copyfile(image_dir + '\\' + bake_image_filename, image_dir + '\\' + joint_image_filename) else: # 2つ目以降の画像は合成する image_synthesis_superposition_PIL( arg_synthesisfiledir=image_dir, arg_imagefilename01=joint_image_filename, arg_imagefilename02=bake_image_filename, arg_synthesisfilename=joint_image_filename) meshloop = meshloop + 1 # 統合リストに対して処理を行う for combo_meshs in combo_meshs_list: # 統合テクスチャスロット名と画像ファイル名の作成 joint_image_name = 'NormalMap_Joint_' + combo_meshs[0] joint_image_filename = 'NormalMap_Joint_' + combo_meshs[0] + '.png' # マテリアル一覧とマテリアル名一覧を作成する materials=[] materialnames=[] # 対象オブジェクトのマテリアル一覧を取得する for combo_mesh in combo_meshs: # 以下の名称を作成する # ベイク先オブジェクト名 bake_objectto_name = 'Low_' + combo_mesh # オブジェクトのマテリアルを取得する obmats = get_materiallist_object(arg_objectname=bake_objectto_name) # 全てのマテリアルをチェックする for obmat in obmats: # 既にリストにマテリアルが含まれているかチェックする if (obmat.name in materialnames) == False: # リストに含まれていなければリストに追加する materials.append(obmat) materialnames.append(obmat.name) # 取得した対象マテリアルに処理を行う for material in materials: set_normalmap_texture( arg_materialname=material.name,arg_textureslotname=joint_image_name, arg_applyfiledir=image_dir, arg_applyfilename=joint_image_filename) # 結合対象オブジェクト名のリスト取得 jointmeshs = get_namelist_jointtarget(arg_combo_meshs_list=combo_meshs_list,arg_addtarget_meshs=addtarget_meshs) # 削除対象オブジェクト名のリスト取得 deletemeshs = get_namelist_deletetarget(arg_joint_meshs=jointmeshs) # モディファイアの適用 apply_modifier_targetlist(arg_target_meshs=jointmeshs) # オブジェクトの削除 unlink_object_targets(arg_target_meshs=deletemeshs) # オブジェクトの結合 join_objects_mesh(arg_objectname=jointobj_name) # オブジェクトの透過有効化 set_transparent_object(arg_objectname=jointobj_name) return # 結合対象のオブジェクト名をリスト化する # 引数 arg_combo_meshs_list:組み合わせメッシュ名リストの配列 # arg_addtarget_meshs:追加メッシュ名リスト # 戻り値 結合対象オブジェクト名リスト def get_namelist_jointtarget(arg_combo_meshs_list=[[]],arg_addtarget_meshs=[]): # 空リストを作成 joint_meshs=[] # 結合対象の名称を抽出する # 全てのリストに対して処理を行う for combo_meshs in arg_combo_meshs_list: for combo_mesh in combo_meshs: # 以下の名称を作成する # ローポリゴンメッシュ名 low_object_name = 'Low_' + combo_mesh joint_meshs.append(low_object_name) # 追加メッシュ名を追加する for add_mesh in arg_addtarget_meshs: joint_meshs.append(add_mesh) return joint_meshs # 削除対象のオブジェクト名をリスト化する # 引数 arg_joint_meshs:追加メッシュ名リスト # 戻り値 削除対象オブジェクト名リスト def get_namelist_deletetarget(arg_joint_meshs=[]): # 空リストを作成 delete_meshs=[] # 全てのメッシュオブジェクトの名称を確認する for ob in bpy.context.scene.objects: if ob.type == 'MESH': if (ob.name in arg_joint_meshs) == False: # 結合対象にメッシュ名が含まれていなければ削除対象 delete_meshs.append(ob.name) return delete_meshs # HighとLowの組み合わせのあるメッシュを検索してリスト化する # 戻り値 対象オブジェクト名リスト def get_namelist_baketarget(): # 空リストを作成 name_list=[] # 全てのメッシュオブジェクトの名称を確認する for ob in bpy.context.scene.objects: if ob.type == 'MESH': objectname = ob.name # 名前が[High_]で始まっていればベイク元と判定する if objectname.find('High_') == 0: # 基礎名称を取得する basename = objectname.lstrip('High_') # 対になるメッシュの名称は Low_ + 基礎名称 とする pairobjectname = 'Low_' + basename # 対になるメッシュオブジェクトを走査する for checkob in bpy.context.scene.objects: if checkob.type == 'MESH': checkobjectname = checkob.name # 対となるメッシュの名称ならばベイク先と判定する if pairobjectname == checkobjectname: # リストに基礎名称を保存して走査を完了する name_list.append(basename) break return name_list # 指定された画像名の画像データを取得する # 指定の画像データが存在しない場合は新規作成する # 引数 arg_imagename:画像名 # arg_savefiledir:画像保存ファイルディレクトリ # 戻り値 画像データ def get_target_image( arg_imagename='NewImage', arg_savefiledir='C:\\Blender\\PythonScriptTest\\NormalBaker\\Texture'): # 既に同名の画像ファイルがあるか確認する # get関数の場合、キーが存在しなければ None が返る image = bpy.data.images.get(arg_imagename) # 同名の画像ファイルがない場合は新規作成する if image == None: # 新規画像を作成する image=bpy.data.images.new(name=arg_imagename, width=2048, height=2048, alpha=True) # 保存ファイルパスを作成する savepath=arg_savefiledir + '\\' + arg_imagename + '.png' # 保存ファイルパスを指定する image.filepath_raw = savepath # ファイルフォーマットをPNGに設定する image.file_format = 'PNG' # 画像を一旦保存する(初期化のため) image.save() return image # ベイク元オブジェクトからベイク先オブジェクトへノーマルベイクを実行する # 引数 arg_sourcename:ベイク元オブジェクト名 # arg_destinationname:ベイク先オブジェクト名 # arg_savefiledir:ベイク画像保存ファイルディレクトリ # arg_savefilename:ベイク画像保存ファイル名 # 戻り値 def bake_normal_select2active( arg_sourcename='Default',arg_destinationname='Default', arg_savefiledir='C:\\Blender\\PythonScriptTest\\NormalBaker\\Texture', arg_savefilename='NormalMap'): # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select=False # ベイク先オブジェクトを取得する destinationob = bpy.context.scene.objects[arg_destinationname] # ベイク先オブジェクトをアクティブに変更する bpy.context.scene.objects.active=destinationob # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # 頂点を全選択した状態とする bpy.ops.mesh.select_all(action='SELECT') # ベイク対象画像を取得する bakeimage=get_target_image(arg_imagename=arg_savefilename,arg_savefiledir=arg_savefiledir) # 全てのスクリーンを走査する for area in bpy.context.screen.areas: # エリアタイプが3Dビューであるか if area.type == 'VIEW_3D': # エリアタイプをUV画像エディターに変更する area.type='IMAGE_EDITOR' # エディター内の各領域を走査する for space in area.spaces: # 領域が画像エディターの表示領域であるか if space.type == 'IMAGE_EDITOR': # 画像エディターの表示領域の作成した画像を設定する space.image=bakeimage # ベイク元オブジェクトを取得する sourceob = bpy.context.scene.objects[arg_sourcename] # ベイク元オブジェクトを選択状態にする sourceob.select=True # ベイク先オブジェクトを選択状態にする destinationob.select=True # ベイクモードを「テクスチャ」に設定する bpy.data.scenes['Scene'].render.bake_type="NORMALS" # 法線空間を「タンジェント」に設定する bpy.data.scenes['Scene'].render.bake_normal_space="TANGENT" # ベイク先を「選択→アクティブ」を有効に設定する bpy.context.scene.render.use_bake_selected_to_active=True # ベイク処理を実行する bpy.ops.object.bake_image() # 画像を再保存する bakeimage.save() # 全てのスクリーンを走査する for area in bpy.context.screen.areas: # エリアタイプがUV画像エディターであるか if area.type == 'IMAGE_EDITOR': # エリアタイプを3Dビューに戻す area.type='VIEW_3D' # 頂点を全て非選択した状態とする bpy.ops.mesh.select_all(action='DESELECT') # ベイク先オブジェクトをオブジェクトモードに戻す bpy.ops.object.mode_set(mode='OBJECT', toggle=False) return # 指定されたオブジェクトのマテリアルリストを取得する # マテリアルが存在しない場合は新規作成する # 引数 arg_objectname:指定オブジェクト名 # 戻り値 マテリアルリスト def get_materiallist_object(arg_objectname='Default'): # 空リストを作成 material_list=[] # 指定オブジェクトを取得する targetob = bpy.context.scene.objects[arg_objectname] # 指定オブジェクトをアクティブに変更する bpy.context.scene.objects.active=targetob # オブジェクトが保持するマテリアルスロットの数を取得する material_num = len(targetob.material_slots) # マテリアルスロットが空の場合はスロットを追加する if material_num == 0: # マテリアルスロットを1つ追加する bpy.ops.object.material_slot_add() for material_slot in targetob.material_slots: # マテリアルスロットにマテリアルが割当られていない場合はマテリアルを新規作成する if material_slot.material == None: # 新規マテリアルを作成する material_slot.material=bpy.data.materials.new(arg_objectname) # マテリアルをリストに追加する material_list.append(material_slot.material) return material_list # 指定されたマテリアルの指定のテクスチャスロットを取得する # 指定のテクスチャスロットが存在しない場合は新規作成する # 引数 arg_materialname:指定マテリアル名 # arg_textureslotname:指定テクスチャスロット名 # 戻り値 マテリアルリスト def get_texture_object(arg_materialname='Default',arg_textureslotname='Default'): # 返り値の作成 textureslot = None # 指定マテリアルを取得する targetmat = bpy.data.materials[arg_materialname] # 指定テクスチャスロットを取得する targettextureslot = targetmat.texture_slots.get(arg_textureslotname) # 指定テクスチャスロットがない場合は新規作成する if targettextureslot == None: targettextureslot = targetmat.texture_slots.add() # テクスチャスロット名を変更する targettextureslot.name = arg_textureslotname # 指定テクスチャスロットのテクスチャを取得する texture = targettextureslot.texture # 指定テクスチャスロットのテクスチャがない場合は新規作成する if texture == None: # 新規テクスチャを作成する texture = bpy.data.textures.new(arg_textureslotname,type='IMAGE') # 作成したテクスチャスロットに新規テクスチャを設定する targettextureslot.texture = texture # 返り値にテクスチャスロットを設定する textureslot = targettextureslot return textureslot # 指定されたマテリアルの指定のテクスチャスロットにノーマルマップを設定する # 引数 arg_materialname:指定マテリアル名 # arg_textureslotname:指定テクスチャスロット名 # arg_applyfiledir:画像ファイルディレクトリ # arg_applyfilename:画像ファイル名 # 戻り値 def set_normalmap_texture( arg_materialname='Default',arg_textureslotname='Default', arg_applyfiledir='C:\\Blender\\PythonScriptTest\\NormalBaker\\Texture', arg_applyfilename='NormalMap'): # 指定マテリアルを取得する targetmat = bpy.data.materials[arg_materialname] # 指定テクスチャスロットを取得する targettextureslot = targetmat.texture_slots.get(arg_textureslotname) # 指定テクスチャスロットがない場合は新規作成する if targettextureslot == None: targettextureslot = targetmat.texture_slots.add() # 指定テクスチャスロットのテクスチャを取得する targettexture = targettextureslot.texture # 指定テクスチャスロットのテクスチャがない場合はテクスチャを設定する if targettexture == None: # 指定名のテクスチャを取得する targettexture = bpy.data.textures.get(arg_textureslotname) # 指定名のテクスチャがない場合はテクスチャを新規作成する if targettexture == None: # 新規テクスチャを作成する targettexture = bpy.data.textures.new(arg_textureslotname,type='IMAGE') # 作成したテクスチャスロットに新規テクスチャを設定する targettextureslot.texture = targettexture # 反映画像のファイルパスを取得する allpy_filepath = arg_applyfiledir + '\\' + arg_applyfilename # 反映画像を読み込み apply_image = bpy.data.images.load(filepath=allpy_filepath) # 作成した新規テクスチャに画像を設定する targettexture.image = apply_image # 全ての要素を一旦無効化する # ディフューズ要素を無効 targettextureslot.use_map_diffuse = False targettextureslot.use_map_color_diffuse = False targettextureslot.use_map_alpha = False targettextureslot.use_map_translucency = False # シェーディング要素を無効 targettextureslot.use_map_ambient = False targettextureslot.use_map_emit = False targettextureslot.use_map_mirror = False targettextureslot.use_map_raymir = False # スペキュラー要素を無効 targettextureslot.use_map_specular = False targettextureslot.use_map_color_spec = False targettextureslot.use_map_hardness = False # ジオメトリ要素を無効 targettextureslot.use_map_normal = False targettextureslot.use_map_warp = False targettextureslot.use_map_displacement = False # ジオメトリのノーマルのみ有効化 targettextureslot.use_map_normal = True return # 画像を合成する(アルファブレンド) # 引数 arg_synthesisfiledir:結合画像ディレクトリ # arg_imagefilename01:合成元画像名1 # arg_imagefilename02:合成元画像名2 # arg_synthesisfilename:合成画像ファイル名 # 戻り値 def image_synthesis_addWeighted( arg_synthesisfiledir='D:\\WORK\\Texture', arg_imagefilename01='NewTexture01.png', arg_imagefilename02='NewTexture02.png', arg_synthesisfilename='SynTexture.png'): # 画像のファイルパスを取得する imagefilepath01 = arg_synthesisfiledir + '\\' + arg_imagefilename01 imagefilepath02 = arg_synthesisfiledir + '\\' + arg_imagefilename02 synthesisfilepath = arg_synthesisfiledir + '\\' + arg_synthesisfilename # 画像を読み込む image01 = cv2.imread(imagefilepath01) image02 = cv2.imread(imagefilepath02) # 画像を合成する synthesis = cv2.addWeighted(src1=image01,alpha=1.0,src2=image02,beta=1.00,gamma=0) # 合成した画像を保存する cv2.imwrite(synthesisfilepath, synthesis) return # 画像を合成する(マスク処理) # 引数 arg_synthesisfiledir:結合画像ディレクトリ # arg_imagefilename01:合成元画像名1 # arg_imagefilename02:合成元画像名2 # arg_synthesisfilename:合成画像ファイル名 # 戻り値 def image_synthesis_bitwise_and( arg_synthesisfiledir='D:\\WORK\\Texture', arg_imagefilename01='NewTexture01.png', arg_imagefilename02='NewTexture02.png', arg_synthesisfilename='SynTexture.png'): # 画像のファイルパスを取得する imagefilepath01 = arg_synthesisfiledir + '\\' + arg_imagefilename01 imagefilepath02 = arg_synthesisfiledir + '\\' + arg_imagefilename02 synthesisfilepath = arg_synthesisfiledir + '\\' + arg_synthesisfilename # 画像を読み込む image01 = cv2.imread(imagefilepath01) image02 = cv2.imread(imagefilepath02) # 画像を合成する synthesis = cv2.bitwise_and(src1=image01,src2=image02) # 合成した画像を保存する cv2.imwrite(synthesisfilepath, synthesis) return # 画像を合成する(重ね合わせ) # 引数 arg_synthesisfiledir:結合画像ディレクトリ # arg_imagefilename01:合成元画像名1 # arg_imagefilename02:合成元画像名2 # arg_synthesisfilename:合成画像ファイル名 # 戻り値 def image_synthesis_superposition( arg_synthesisfiledir='D:\\WORK\\Texture', arg_imagefilename01='NewTexture01.png', arg_imagefilename02='NewTexture02.png', arg_synthesisfilename='SynTexture.png'): # 画像のファイルパスを取得する imagefilepath01 = arg_synthesisfiledir + '\\' + arg_imagefilename01 imagefilepath02 = arg_synthesisfiledir + '\\' + arg_imagefilename02 synthesisfilepath = arg_synthesisfiledir + '\\' + arg_synthesisfilename # 画像を読み込む background = cv2.imread(imagefilepath01, -1) foreground = cv2.imread(imagefilepath02, -1) # オフセット x = 0 y = 0 #x,yで貼り付け位置を選択 f_h, f_w, _ = foreground.shape #透明部分が0、不透明部分が1のマスクを作る alpha_mask = np.ones((f_h, f_w)) - np.clip(cv2.split(foreground)[3],0,1) #貼り付ける位置の背景部分 target_background = background[y:y+f_h,x:x+f_w] #各BRGチャンネルにalpha_maskを掛けて、前景の不透明部分が[0, 0, 0]のnew_backgroundを作る new_background = cv2.merge(list(map(lambda x:x * alpha_mask,cv2.split(target_background)))) #BGRAをBGRに変換した画像とnew_backgroundを足すと合成できる background[y:y + f_h,x:x+f_w] = cv2.merge(cv2.split(foreground)[:3]) + new_background # 合成した画像を保存する cv2.imwrite(synthesisfilepath, background) return # 画像を合成する(重ね合わせPIL) # 引数 arg_synthesisfiledir:結合画像ディレクトリ # arg_imagefilename01:合成元画像名1 # arg_imagefilename02:合成元画像名2 # arg_synthesisfilename:合成画像ファイル名 # 戻り値 def image_synthesis_superposition_PIL( arg_synthesisfiledir='D:\\WORK\\Texture', arg_imagefilename01='NewTexture01.png', arg_imagefilename02='NewTexture02.png', arg_synthesisfilename='SynTexture.png'): # 画像のファイルパスを取得する imagefilepath01 = arg_synthesisfiledir + '\\' + arg_imagefilename01 imagefilepath02 = arg_synthesisfiledir + '\\' + arg_imagefilename02 synthesisfilepath = arg_synthesisfiledir + '\\' + arg_synthesisfilename # 画像を読み込む cv_background_image = cv2.imread(imagefilepath01, -1) cv_overlay_image = cv2.imread(imagefilepath02, -1) point = [0,0] overlay_height, overlay_width = cv_overlay_image.shape[:2] # OpenCV形式の画像をPIL形式に変換(α値含む) # 背景画像 cv_rgb_bg_image = cv2.cvtColor(cv_background_image, cv2.COLOR_BGR2RGB) pil_rgb_bg_image = Image.fromarray(cv_rgb_bg_image) pil_rgba_bg_image = pil_rgb_bg_image.convert('RGBA') # オーバーレイ画像 cv_rgb_ol_image = cv2.cvtColor(cv_overlay_image, cv2.COLOR_BGRA2RGBA) pil_rgb_ol_image = Image.fromarray(cv_rgb_ol_image) pil_rgba_ol_image = pil_rgb_ol_image.convert('RGBA') # composite()は同サイズ画像同士が必須のため、合成用画像を用意 pil_rgba_bg_temp = Image.new('RGBA', pil_rgba_bg_image.size, (255, 255, 255, 0)) # 座標を指定し重ね合わせる pil_rgba_bg_temp.paste(pil_rgba_ol_image, point, pil_rgba_ol_image) result_image = Image.alpha_composite(pil_rgba_bg_image, pil_rgba_bg_temp) # OpenCV形式画像へ変換 cv_bgr_result_image = cv2.cvtColor(np.asarray(result_image), cv2.COLOR_RGBA2BGRA) # 合成した画像を保存する cv2.imwrite(synthesisfilepath, cv_bgr_result_image) return # 対象オブジェクトの参照を削除する # (参照が切るため、プロジェクト保存には注意) # 引数 arg_target_meshs:結合オブジェクト名リスト # 戻り値 def unlink_object_targets(arg_target_meshs=[]): # リストのメッシュを全て走査する for mesh in arg_target_meshs: ob = bpy.context.scene.objects[mesh] # オブジェクトの参照を削除する bpy.context.scene.objects.unlink(ob) return # 以下のモディファイアを適用する # - ミラーモディファイア # - シュリンクラップモディファイア # - 細分割曲面モディファイア # - 辺分離モディファイア # - 厚み付けモディファイア # - ベベルモディファイア # 対象モディファイアを保持しない場合は無視する # 引数 arg_target_meshs:操作対象オブジェクト名リスト # 戻り値 def apply_modifier_targetlist(arg_target_meshs=[]): # リストのメッシュを全て走査する for mesh in arg_target_meshs: # 指定オブジェクトを取得する selectob = bpy.data.objects[mesh] # 変更オブジェクトをアクティブに変更する bpy.context.scene.objects.active = selectob # オブジェクトの全てモディファイアを走査する # モディファイアのタイプ一覧 # (https://docs.blender.org/api/blender_python_api_2_71_release/bpy.types.Modifier.html) for modifier in selectob.modifiers: # ミラーモディファイアを適用する if modifier.type == 'MIRROR': bpy.ops.object.modifier_apply(apply_as='DATA',modifier=modifier.name) # 細分割曲面モディファイアを適用する if modifier.type == 'SUBSURF': bpy.ops.object.modifier_apply(apply_as='DATA',modifier=modifier.name) # シュリンクラップモディファイアを適用する if modifier.type == 'SHRINKWRAP': bpy.ops.object.modifier_apply(apply_as='DATA',modifier=modifier.name) # 辺分離モディファイアを適用する if modifier.type == 'EDGE_SPLIT': bpy.ops.object.modifier_apply(apply_as='DATA',modifier=modifier.name) # 厚み付けモディファイアを適用する if modifier.type == 'SOLIDIFY': bpy.ops.object.modifier_apply(apply_as='DATA',modifier=modifier.name) # ベベルモディファイアを適用する if modifier.type == 'BEVEL': bpy.ops.object.modifier_apply(apply_as='DATA',modifier=modifier.name) return # 以下のモディファイアを適用する # - ミラーモディファイア # ※ ミラーモディファイアの名称が'Mirror'であることを前提とする # - シュリンクラップモディファイア # ※ シュリンクラップモディファイアの名称が'Shrinkwrap'であることを前提とする # - 細分割曲面モディファイア # ※ 細分割曲面モディファイアの名称が'SubSurf'であることを前提とする # - 辺分離モディファイア # ※ 辺分離モディファイアの名称が'EdgeSplit'であることを前提とする # 対象モディファイアを保持しないオブジェクトは無視する # 引数 arg_target_meshs:操作対象オブジェクト名リスト # 戻り値 def apply_modifier_target(arg_target_meshs=[]): # リストのメッシュを全て走査する for mesh in arg_target_meshs: # 指定オブジェクトを取得する selectob = bpy.data.objects[mesh] # 変更オブジェクトをアクティブに変更する bpy.context.scene.objects.active = selectob # 'Mirror'モディファイアがあるか確認する # get関数の場合、キーが存在しなければ None が返る selectmod = selectob.modifiers.get('Mirror') # 指定名のテクスチャがない場合はテクスチャを新規作成する if selectmod != None: # モディファイアを適用する bpy.ops.object.modifier_apply(apply_as='DATA',modifier='Shrinkwrap') # 'Shrinkwrap'モディファイアがあるか確認する # get関数の場合、キーが存在しなければ None が返る selectmod = selectob.modifiers.get('Shrinkwrap') # 指定名のテクスチャがない場合はテクスチャを新規作成する if selectmod != None: # モディファイアを適用する bpy.ops.object.modifier_apply(apply_as='DATA',modifier='Shrinkwrap') return # メッシュオブジェクトの結合 # 引数 arg_objectname:結合オブジェクト名 # 戻り値 def join_objects_mesh(arg_objectname=''): # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # オブジェクトがメッシュであるか確認する if ob.type == 'MESH': # メッシュであれば選択状態にする ob.select = True # 対象アクティブオブジェクトに切り替える # メッシュはアクティブオブジェクトに結合される bpy.context.scene.objects.active = ob else: # メッシュでなければ選択状態にしない ob.select = False # オブジェクトの結合を実行する bpy.ops.object.join() # 結合オブジェクト名が設定されているか if len(arg_objectname): # オブジェクト名が設定されていれば名前を変更する bpy.context.scene.objects.active.name = arg_objectname return # 対象のオブジェクトの透明を有効化する # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def set_transparent_object(arg_objectname='Default'): # 対象オブジェクトを取得する selectob=bpy.context.scene.objects[arg_objectname] # オブジェクトの透過表示を有効化する selectob.show_transparent=True return # main関数の呼び出し main()
スクリプトの実行
試しにスクリプトを実行してみます。
ウィンドウの一つに[テキストエディタ―]を開きます。
[開く]ボタンをクリックして先ほどの python スクリプトを読み込みます。
後は[スクリプト実行]をクリックするだけです。
するとオブジェクトとノーマルマップが結合されたモデルが作成されました。
後は完成したモデルを FBX ファイルとしてエクスポートします。
ハイポリゴンメッシュをスカルプトで少し修正して再出力するといった事が手軽にできるようになります。
手直しが何度もできるので最終的にはクオリティアップにもつながりそうです。
次からは Unity 上での作業となります。セカンダリマップによる質感表現を試します。
bluebirdofoz.hatenablog.com