本日は Blender の技術調査枠です。
Blender2.8で利用可能なpythonスクリプトを作ります。
頂点カラーをテクスチャにベイクする
頂点カラーをテクスチャにベイクし、そのテクスチャを設定したプリンシプルBSDFのマテリアルを再設定します。
有効な頂点カラーレイヤー名を取得してエミッションカラーとして利用します。
レンダリング時に、GPUが利用可能な環境の場合は利用設定を行います。
・paint_vertexcolor_rgba.py
# bpyインポート import bpy # 指定オブジェクトの頂点カラーを画像テクスチャにベイクする # その後、画像テクスチャを設定したプリンシプルBSDFのマテリアルを再設定する def bake_vertexcolor_texture(arg_objectname="Default") -> bool: """指定オブジェクトの頂点カラーを画像テクスチャにベイクする その後、画像テクスチャを設定したプリンシプルBSDFのマテリアルを再設定する 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 # オブジェクトをアクティブにする bpy.context.view_layer.objects.active = selectob # 各種ノードの参照名を定義する texturenodename = "ShaderNodeTexImage" attrnodename = "Attribute" emissionnodename = "Emission" # オブジェクトの選択状態をクリアする object_select_clear() # スマートUV展開を実行する uv_result = project_uv_smart(arg_objectname=arg_objectname) if uv_result != True: # UV作成に失敗した場合は処理しない return False # 指定オブジェクトの全マテリアルを削除する delmat_result = delete_material_all(arg_objectname=arg_objectname) if delmat_result != True: # マテリアル削除に失敗した場合は処理しない return False # 頂点カラーレイヤー名を取得する vertexcolor_name = get_vertexcolor_active(arg_objectname=arg_objectname) if len(vertexcolor_name) < 1: # 名称取得に失敗した場合は処理しない return False # 指定オブジェクトに頂点カラーとエミッションを使ったシンプルなマテリアルを作成する addmat_result = add_material_vertexemission(arg_objectname=arg_objectname, arg_vertexcolorname=vertexcolor_name) if addmat_result != True: # マテリアル追加に失敗した場合は処理しない return False # テクスチャ用の画像ノードを追加してメッシュオブジェクトをベイクする # その後、画像ノードをカラーとして接続する bake_result = bake_texture_object( arg_objectname=arg_objectname, arg_addnodename=texturenodename, arg_texturesize=1024, arg_GPUuse=True ) if bake_result != True: # テクスチャ作成に失敗した場合は処理しない return False # 不要なノード(属性ノード)を削除する delete_node_target(arg_objectname=arg_objectname, arg_deletenodename=attrnodename) # 不要なノード(エミッションノード)を削除する delete_node_target(arg_objectname=arg_objectname, arg_deletenodename=emissionnodename) return True # 全てのオブジェクトを非選択状態にする def object_select_clear(): """全てのオブジェクトを非選択状態にする Returns: Bool -- 実行正否 """ # 全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select_set(False) return True # スマートUV展開を実行する(デフォルト設定) def project_uv_smart(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 # 不要なオブジェクトを選択しないように # 全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select_set(False) # オブジェクトを選択状態にする selectob.select_set(True) # 対象オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = selectob # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # 頂点を全選択した状態とする bpy.ops.mesh.select_all(action='SELECT') # デフォルト設定のスマートUV展開を実行する # 角度制限:66,島の余白:0,エリアウェイト:0,アスペクト比の補正:True,UV境界に合わせる:True bpy.ops.uv.smart_project(angle_limit=66, island_margin=0, user_area_weight=0, use_aspect=True, stretch_to_bounds=True) # オブジェクトモードに移行する bpy.ops.object.mode_set(mode='OBJECT', toggle=False) return True # 指定オブジェクトの全マテリアルを削除する def delete_material_all(arg_objectname="Default") -> bool: """指定オブジェクトに新規マテリアルを作成する 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 # オブジェクトをアクティブにする bpy.context.view_layer.objects.active = selectob # マテリアルスロットを走査する for material_slot in selectob.material_slots: # マテリアルスロットをアクティブにする selectob.active_material = material_slot.material # マテリアルスロットを削除する bpy.ops.object.material_slot_remove() return True # レンダリング時に有効な頂点カラーレイヤーの名前を取得する def get_vertexcolor_active(arg_objectname="Default") -> str: """レンダリング時に有効な頂点カラーレイヤーの名前を取得する Keyword Arguments: arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"}) Returns: str -- レンダリング時に有効な頂点カラーレイヤーの名前(取得失敗は空文字) """ # 指定オブジェクトを取得する # (get関数は対象が存在しない場合 None が返る) selectob = bpy.data.objects.get(arg_objectname) # 指定オブジェクトが存在するか確認する if selectob == None: # 指定オブジェクトが存在しない場合は処理しない return "" # 指定オブジェクトがメッシュオブジェクトか確認する if selectob.type != 'MESH': # メッシュオブジェクトでない場合は処理しない return "" # オブジェクトをアクティブにする bpy.context.view_layer.objects.active = selectob # 対象オブジェクトのメッシュデータを取得する # メッシュデータのインタフェース # (https://docs.blender.org/api/current/bpy.types.Mesh.html) meshdata = selectob.data # 頂点カラーレイヤーのリストを取得する # 頂点カラーレイヤーのリストのインタフェース # (https://docs.blender.org/api/current/bpy.types.LoopColors.html) vertexcolors = meshdata.vertex_colors # 変数の初期化 render_index = -1 # 頂点カラーレイヤーを走査する for index in range(0, len(vertexcolors)): # レンダリング時の有効無効をチェックする if vertexcolors[index].active_render == True: # レンダリング時に有効な頂点カラーレイヤーのインデックスを取得する render_index = index # 有効なインデックスが取得できたか確認する if render_index == -1: # 取得できなかった場合は空文字を返す return "" # レンダリング時に有効な頂点カラーレイヤーの参照を取得する active_vertexcolor = vertexcolors[render_index] return active_vertexcolor.name # 指定オブジェクトに頂点カラーとエミッションを使ったシンプルなマテリアルを作成する # (予め不要なノードは削除しておくこと) def add_material_vertexemission(arg_objectname="Default", arg_materialname="DefaultMaterial", arg_vertexcolorname="Col") -> bool: """指定オブジェクトに頂点カラーとエミッションを使ったシンプルなマテリアルを作成する (予め不要なノードは削除しておくこと) Keyword Arguments: arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"}) arg_materialname {str} -- 追加マテリアル名 (default: {"NewMaterial"}) arg_vertexcolorname {str} -- 頂点カラーレイヤー名 (default: {"Col"}) 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 # 新規マテリアルを作成する newmaterial = bpy.data.materials.new(arg_materialname) # マテリアルスロットを追加する bpy.ops.object.material_slot_add() # 追加したマテリアルスロットに新規マテリアルを設定する selectob.active_material = newmaterial # ノードを使用する newmaterial.use_nodes = True # 新規マテリアルのノード参照を取得する mat_nodes = newmaterial.node_tree.nodes # マテリアル内の全ノードを走査する for delete_node in mat_nodes: # 一旦全てのノードを削除する mat_nodes.remove(delete_node) # 属性ノードを追加する attr_node = mat_nodes.new(type="ShaderNodeAttribute") # 属性ノードのアトリビュート名を頂点カラーレイヤー名に変更する attr_node.attribute_name = arg_vertexcolorname # エミッションノードを追加する emit_node = mat_nodes.new(type="ShaderNodeEmission") # 出力ノードを追加する output_node = mat_nodes.new(type="ShaderNodeOutputMaterial") # ターゲットマテリアルのノードリンク参照を取得する mat_link = newmaterial.node_tree.links # 属性ノードのカラーと放射ノードのカラーを接続する mat_link.new(attr_node.outputs[0], emit_node.inputs[0]) # 放射ノードの放射と出力ノードのサーフェスを接続する mat_link.new(emit_node.outputs[0], output_node.inputs[0]) return True # テクスチャ用の画像ノードを追加してメッシュオブジェクトをベイクする # その後、画像ノードをカラーとして接続する def bake_texture_object(arg_objectname="Default", arg_addnodename="ShaderNodeTexImage", arg_texturesize=2048, arg_GPUuse=False): """テクスチャ用の画像ノードを追加してメッシュオブジェクトをベイクする その後、画像ノードをカラーとして接続する Keyword Arguments: arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"}) arg_addnodename {str} -- 追加ノード名 (default: {"ShaderNodeTexImage"}) arg_texturesize {int} -- テクスチャサイズ (default: {2048}) arg_GPUuse {bool} -- GPU利用有無 (default: {False}) 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 # マテリアルスロットを走査する if len(selectob.material_slots) == 0: # マテリアルスロットが設定されていなければ処理しない return False # 処理対象のマテリアルを取得する target_mat = selectob.material_slots[0].material # ターゲットマテリアルのノード参照を取得 mat_nodes = target_mat.node_tree.nodes # テクスチャノードの追加 texture_node = mat_nodes.new(type="ShaderNodeTexImage") # テクスチャノードをアクティブにする mat_nodes.active = texture_node # 新規画像を作成する bakeimage = bpy.data.images.new( name="BakeTexture", width=arg_texturesize, height=arg_texturesize, alpha=True ) # テクスチャノードに新規画像を設定する texture_node.image = bakeimage # レンダリングエンジンを CYCLES に切り替える bpy.data.scenes["Scene"].render.engine = 'CYCLES' # GPUの利用有無を確認する if arg_GPUuse == True: # 利用設定ならGPUの設定を行う bpy.data.scenes["Scene"].cycles.device = 'GPU' # CUDAを選択する bpy.context.preferences.addons['cycles'].preferences.compute_device_type = 'CUDA' # デバイスの一覧を取得する for devices in bpy.context.preferences.addons['cycles'].preferences.get_devices(): for device in devices: # デバイスタイプがCUDAならば利用対象とする if device.type == 'CUDA': print("利用可能なGPUを検出しました:" + device.name) device.use = True # ベイクの余白を設定 bpy.data.scenes["Scene"].render.bake_margin = 2 # 放射タイプのレンダリングを実行する bpy.ops.object.bake(type='EMIT') # ノードリンクの取得 mat_link = target_mat.node_tree.links # プリンシプルBSDFノードを追加する bsdf_node = mat_nodes.new(type="ShaderNodeBsdfPrincipled") # 出力ノードを追加する output_node = mat_nodes["Material Output"] # テクスチャノードのカラーとプリンシプルBSDFノードのベースカラーを接続する mat_link.new(texture_node.outputs[0], bsdf_node.inputs[0]) # 放射ノードの放射と出力ノードのサーフェスを接続する mat_link.new(bsdf_node.outputs[0], output_node.inputs[0]) return True # 対象オブジェクトの先頭マテリアルの指定ノードを削除する def delete_node_target(arg_objectname="Default", arg_deletenodename="DeleteNode"): """対象オブジェクトの先頭マテリアルの指定ノードを削除する Keyword Arguments: arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"}) arg_deletenodename {str} -- 削除ノード名 (default: {"DeleteNode"}) 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 # マテリアルスロットを走査する if len(selectob.material_slots) == 0: # マテリアルスロットが設定されていなければ処理しない return False # 処理対象のマテリアルを取得する target_mat = selectob.material_slots[0].material # ターゲットマテリアルのノード参照を取得 mat_nodes = target_mat.node_tree.nodes # 削除ノードの取得 delete_node = mat_nodes[arg_deletenodename] # ノードを削除する mat_nodes.remove(delete_node) return True # 関数の実行例 bake_vertexcolor_texture(arg_objectname="Sphere")