2018年5月28日月曜日

BlenderからPhotoshopを制御5 -選択形状をPhotoshopの3Dレイヤに

こちらのサイトでは今までもBlenderのスクリプトからPhotoshopを制御をする方法の解説を書いてきました

BlenderからPhotoshopを制御1-Blender-PyhtonとPhotoshop-JavaScriptについて
BlenderからPhotoshopを制御2-レンダリングした画像をPhotoshpで開く
BlenderからPhotoshopを制御3-画像をPhotoshpで開くアドオン作成
BlenderからPhotoshopを制御4 -選択UVからPhotoshpの選択範囲

今回、「Blenderの3D形状を簡単な操作でPhotoshopに読み込ませる方法はないかな?」
という話を見かけて作ってみました。
Photoshopの3Dレイヤの操作って通常のメニュー操作でやると手順が多いので煩わしいのですよね・・・


結果としては拍子抜けするほど簡単でした(笑
基本的な動作は BlenderからPhotoshopを制御3 からやってきたように
PhotoshopのjavascriptにBlenderから書き出したデータのパスを渡して処理させるといった感じになります

まず Photoshopのjavascript

#target photoshop;

//ファイルの読み込み
var obj_file =  File(arguments[0]);
//指定したファイルから3Dレイヤを作成
// =======================================================
var idaddthreeDLayerFromFile = stringIDToTypeID( "add3DLayerFromFile" );
    var desc10 = new ActionDescriptor();
    var idfileList = stringIDToTypeID( "fileList" );
        var list1 = new ActionList();
        list1.putPath( obj_file );
    desc10.putList( idfileList, list1 );
executeAction( idaddthreeDLayerFromFile, desc10, DialogModes.NO );

非常にシンプルですね

次に Blender側のPython

bl_info = {
    "name": "3D Object to Photoshop",
    "description": "選択形状をPhotoshopの3Dレイヤに",
    "author": "Yukimi",
    "version": (0,4),
    "blender": (2, 6, 0),
    "location": "object",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "http://yukimi-blend.blogspot.jp/",
    "category": "Import-Export"}
 
import bpy
from io_scene_obj import export_obj
from bpy_extras.io_utils import axis_conversion

import os
import subprocess
import time
import random


#実行するjavascriptの名前
js_name = "Add3DLayerFromFile.jsx"
#このスクリプトのあるディレクトリのパス
mydoc_dir = os.path.dirname(__file__)
#実行するスクリプトのパス
VB_Hub = os.path.abspath(os.path.join(mydoc_dir, "VB_Hub.vbs"))
jscript = os.path.abspath(os.path.join(mydoc_dir, js_name))
#Blenderの一時ファイルディレクトリを利用する場合
tmp_dir = bpy.context.user_preferences.filepaths.temporary_directory
#ファイルの書き出し先をデスクトップにしたい場合は↓をコメントアウト
#tmp_dir = os.path.join(os.getenv("HOMEDRIVE"), os.getenv("HOMEPATH") , "Desktop")

def obj_to_photoshop(context):
    #ファイル名の作成
    source_str = 'abcdefghijklmnopqrstuvwxyz'
    f_name = time.strftime("%y%m%d%H%M") + "".join([random.choice(source_str) for x in range(3)]) + ".obj"
    obj_path  = os.path.join(tmp_dir,f_name)
    #Objデータの出力
    scale = 1.0
    obj_export(scale, obj_path)
    #javascriptの実行
    subprocess.call(["CScript", VB_Hub, jscript, obj_path, "//nologo"])

def obj_export(scale, obj_path):
    if len(bpy.context.selected_objects) == 0: return()
    from mathutils import Matrix
    global_matrix = (Matrix.Scale(scale, 4) *
                     axis_conversion(to_forward= '-Z',
                                     to_up='Y',
                                     ).to_4x4())
    export_obj.save(bpy.context,
                         filepath      = obj_path,
                         global_matrix = global_matrix,
                         use_triangles = True,
                         use_selection = True,
                         path_mode     = 'STRIP')




###################################################
class DupulicateActionAtCurrentTime(bpy.types.Operator):
    '''selected 3D Object to Photoshop 3D Layer'''
    bl_idname = "action.obj_to_photoshop"
    bl_label = "3D Object to Photoshop 3D Layer"
    def execute(self, context):
         obj_to_photoshop(context)
         return {'FINISHED'}
# メニューの構築処理
def menu_func(self, context):
    self.layout.operator("action.obj_to_photoshop", 
           text="選択形状をPhotoshopへ" )
# アドオン有効化時の処理
def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_object.prepend(menu_func)
# アドオン無効化時の処理
def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_MT_object.remove(menu_func)
 
if __name__ == "__main__":
    register()
##########################################################
標準アドオンのWavefrontObj書き出しを利用してデータを書き出して
javascriptを動かすための中継ソフト VB_Hub.vbs に受け渡しています
上記の2つとのスクリプトとVB_Hub.vbsを入れたフォルダをaddonフォルダに作成するか、
ユーザー設定のアドオンタブの「ファイルからインストール」から下記のzipを読み込んでアドオンを有効にすることで使えます

Blender上で選択している形状をOBJで書き出す方式にしてます
多分Obj以外の形式でも同様にできるかと思いますが、今回はこの形式を利用しました。
アドオンとして利用しやすいように今回のzipファイルをここにアップしておきます

いかがでしょうか

2018年5月11日金曜日

選択した形状に関連するデータの名前をそろえる

Blenderでは個々のオブジェクトの名前の他
オブジェクトのポリゴンやマテリアル、テクスチャ、画像までがファイル内で名前をつけて管理されています。
映像等、レンダリング画像が最終出力になる場合には問題がないのですが、
ゲームのデータ等では名称を統一する必要があったりします

命名規則は様々ですが 今使ってる命名パターンでアドオンを作成してみました
bl_info = {
    "name": "リソースの名前を統一",
    "author": "Yukimi",
    "version": (3, 0),
    "blender": (2, 60, 0),
    "location": "プロパティ > オブジェクト",
    "description": "アクティブオブジェクトのデータ、マテリアルの名称を統一",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}
import bpy
from bpy.props import StringProperty

class CalibratePanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "uniform name Panel"
    bl_idname = "OBJECT_uniform_name"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"
    
    def draw(self, context):
        
        layout = self.layout
        scene = context.scene
        obj = context.object
        row = layout.row()
        row.label(text="Active object is: " + obj.name)
        row = layout.row()
        row.operator("ab.get_f_name")
        row = layout.row()
        row.prop(scene, "uni_basename")
        row = layout.row()
        row.operator("ab.uniform_name")

#名称の変更
class RenameButton(bpy.types.Operator):
    bl_idname = "ab.uniform_name"
    bl_label = "名前の統一化"
    bl_options = {'REGISTER', 'UNDO'}
    def execute(self, context):
        scene = context.scene
        if not "uni_basename" in scene: pass
        ID = scene.uni_basename
        obj = context.object
        #オブジェクトの名前を揃える
        obj.name = ID + "_Obj"
        obj.data.name = ID + "_Mesh"
        if obj.data.uv_textures:
            obj.data.uv_textures.active.name = ID + "_UV" #アクティブなもののみ変更
        if obj.material_slots:
            for  i,mat_slot in enumerate(obj.material_slots):
                mat = mat_slot.material
                if not mat: continue
                mat.name= ID + "_Mtl_%02d" % i
                if mat.active_texture:
                    tex = mat.active_texture
                    tex.name = ID + "_Tex_%02d" % i
                    if tex.type == 'IMAGE' and tex.image:
                        tex.image.name = ID + "_Img_%02d" % i
        return{'FINISHED'}

#ファイル名を取得してテキスト入力の値を設定
class fpathButtonButton(bpy.types.Operator):
    bl_idname = "ab.get_f_name"
    bl_label = "ファイル名の取得"
    def execute(self, context):
        import os
        base_name = os.path.basename(bpy.context.blend_data.filepath)
        f_name = os.path.splitext(base_name)[0]
        context.scene.uni_basename = f_name
        return{'FINISHED'}

def init_props():
    scene = bpy.types.Scene
    scene.uni_basename = StringProperty(name = "揃える名前", default= "" )


#プロパティの削除
def crear_props():
    scene = bpy.types.Scene
    del uni_basename


def register():
    bpy.utils.register_module(__name__)
    init_props()


def unregister():
    bpy.utils.unregister_module(__name__)
    crear_props()


if __name__ == "__main__":
    register()

プロパティのオブジェクトタブに追加する形になっています
現状はUVはアクティブなもののみ末尾に_UVをつけ、マテリアルに通し番号をつけ、 テクスチャ、イメージ名はマテリアルの通し番号で命名するようにしています
名称の変更部分はシンプルなので 個々の案件によって調整できるかと思います
いかがでしょうか

2018年1月2日火曜日

アニメの監督がBlenderでコンテを書く本をだしてみえたので・・・

2017年皆様お疲れ様でした
先頃開催されたコミックマーケット93で アニメ監督のりょーちも氏が
Blenderでコンテを書くメイキングを書いた本を出されていたのですが
本を拝見して勝手ながら こういうやり方もあるということをいくつか書き連ねておきます
本の内容で個人的に連想したものであって これが実用にかなうものか分からないのでご注意ください

まずは 監督が頒布されてるデータの紹介
『timosh × blender』本の中で紹介したひな型データ
本ではこちらのconte_v1.blendの3Dビュー上でグリースペンシルで時間軸に沿って絵を描き
ファイルに内蔵しているスクリプトを実行することで
レンダリングした画像を コンテ撮(コンテから作った動画でアフレコ等をする)や従来の紙コンテ画像にする方法を解説されています
(因みに12/31現在ダウンロードファイルのOpenglレンダリング部分(69行目)がコメントアウトされていて画像が出力されない状態のようです)

ビューの移動の設定

デフォルトではマウスでのビューの移動は
「中ボタンドラッグが回転 Shift+中ボタンが平行移動」になっていて
監督がどうされているか分かる記述はないですが
2Dソフト的な使い方をメインにすることが多い場合 ショートカットの入れ替えをして
AfterEffects等のように中ボタンでの平行移動にしてしまった方がいいように思います
3Dビュー>3D View(Global)> ビューを回転 / ビューを移動 の項目になります

ドープシートにタイムラインウィンドウのUIを移植

頒布されている.blendファイルでは 操作の関係で多数のエディッタが表示状態になった状態になっていますが
UIを書き換えて必要なものだけに整理してしまうことはできないかなと感じました

Blenderのテキストエディッタをウィンドウ内のいずれかに表示した状態にして
GUIを右クリックして出るメニューの「ソース編集」を選択すると エディッタ上にGUIの配置等を記述した部分のPythonコードが表示され、これを編集することでGUIの並び等を変えることができるのですが
機能によっては他のエディッタの部品を移植することができます
細かい方法は別の記事にしますが、自分の環境ではドープシート画面にタイムラインのUIを一部移してあります

コンテ台紙への合成を自動化

本では描かれたグリースペンシル画像を.blendファイル内にあるスクリプト「blender_jump_keyframe_render_code_gl」を実行して
グリースペンシルのキーフレーム毎にOpenGLレンダリング出力するようになっています

本では書き出したコマ画像を並べて改めてレンダリングし直すことで
従来の紙で描いたコンテ用紙のフォーマットにされていますが、並べて合成する程度ならばスクリプトでやっればと作成してみました。
「blender_jump_keyframe_render_code_gl」の後半に追記することで動作するような仕組みにしてあります。
import numpy as np

#rgbaの画像をnumpy arrayに変換
def img_to_nparray(img):
    bit_len = len(img.pixels)
    (width,height) = img.size
    channels = img.channels #色数
    #numpy arrayを作成
    pixlist = np.array(img.pixels)
    pixlist = pixlist.reshape( height, width, 4)
    return( pixlist )

#画像の読み込み
def load_as_nparray(f_path):
    img = bpy.data.images.load(f_path)
    img_array = img_to_nparray(img)
    #読み込んだ画像オブジェクトの消去
    bpy.data.images.remove(img)
    return(img_array)


#numpy arrayをpng画像として保存
def save_nparray_png( save_path, img_name, np_array ):
    f_path = os.path.join( save_path, img_name )
    (height, width, deps) = np_array.shape
    #画像オブジェクトの作成
    img = bpy.data.images.new(name=img_name, width=width, height=height)
    img.pixels = list( np_array.flatten())
    #保存
    img.save_render(filepath=f_path)
    #画像オブジェクトの消去
    bpy.data.images.remove(img)
    

def combine_conteseet(output_path, name, frames):
    base_h_offset = 456 #コンテを並べる場所への縦オフセット
    base_end = 168 #コンテ台紙のフッタ部分の幅
    tile_height = 384 #コマの縦幅
    tile_offset = 30  #コマの横のオフセット
    #開いている.blendファイルのあるディレクトリを取得
    bl_path = bpy.data.filepath
    bl_directory = os.path.dirname(bl_path)
    render_path = bpy.path.abspath(output_path)
    #コンテ台紙の読み込み
    conte_base_path = os.path.join(bl_directory, "base", "conte_base_print_saize.png" )
    conte_array = load_as_nparray(conte_base_path )
    #情報の取得、調整(画像は左下が基準)
    (height_b, width_b, deps) = conte_array.shape
    tiles = (height_b -base_h_offset -base_end ) // tile_height
    base_h_offset = height_b -base_h_offset -tile_height
    count = len(frames)
    for i in range( (count -1)//tiles +1 ):
        #画像の初期化
        conte_array[base_end:base_h_offset +tile_height, 0: width_b] = np.array([1,1,1,1])
        #コマ画像を台紙上に並べて配置
        for j in range( min(tiles, count - i *tiles) ):
            #コマ画像の読み込み
            t = frames[ i*tiles +j ]
            fname = name+ str(t).zfill(4) + ".png"
            img_path = os.path.join(render_path, fname)
            cut_array = load_as_nparray(img_path)
            #合成する位置の設定
            h_offset = base_h_offset - tile_height * j
            (height, width, deps) = cut_array.shape
            #はみ出しはエラーになるため対策
            if width+tile_offset > width_b:
                width = width_b - tile_offset
                cut_array = cut_array[:,:width]
            #結合処理
            conte_array[h_offset : h_offset + height, tile_offset: width+tile_offset] = cut_array
        #numpy配列をPNG画像で保存
        img_name = 'conte_' + str(i).zfill(4) +".png"
        save_nparray_png( render_path, img_name, conte_array )
        
"""# テスト用データ
output_path = bpy.context.scene.render.filepath
name = "Camera"
frames = [8,40,41,95,99,100]
"""
combine_conteseet(output_path, name, frames)


画像を行列演算ライブラリのnumpyで合成しています。(普通に合成処理を書くよりだいぶ速い)
「blender_jump_keyframe_render_code_gl」で本体で使われていた 書き出し先、ファイル名に使う名前 書き出したキーフレームのリスト の変数を利用しています

今回のスクリプトは既存のものに追記する形ですが
bpy op のモジュールをインポートして多少の改変で他の画像を並べる処理にも使えるかと思います

以上独り善がりに書いてみましたが、
監督の本の内容の関連だけでなく何かしらのお役にたてれば幸いです

2017年12月30日土曜日

クリップボードの中身を処理してみる

Blenderの位置や回転等のプロパティ等の数値はGUI上では小数点以下5桁まで表示されているものの
バージョン2.5x時代のように3桁までに丸め込みたいという話をされたので色々試してみた

一括変換なら楽なのだけれど、個別に値を調整したいらしいので
クリップボード中の数値を丸め込む方式で試作。
import bpy
class RoundingClipboard(bpy.types.Operator):
    bl_idname = "text.roundclip"
    bl_label = "round_data_on_clipboard"
    def execute(self, context):
        #bpy.ops.text.copy()
        #クリップボードデータの取得
        clipboard = context.window_manager.clipboard
        try:
            if isinstance(clipboard, float):
                rounded =  round( clipboard,3)
                context.window_manager.clipboard = rounded
                #bpy.ops.text.paste()
            else:
                rounded = str( round( float(clipboard), 3) )
                context.window_manager.clipboard = rounded
                print(rounded)
                #bpy.ops.text.paste()
        except:
            pass
        return {'FINISHED'}
def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    #unregister()
    register()

Blenderは数値をコピーしても文字列として内部で保持しているらしい
Phthonの文字列を数値に変換できるかはstr.isdigit()で調べれるということだけど
手元の環境ではfloat値の場合もFalseが返されてしまうので文字列チェックはなし。

本当はパネル上で動くようにオペレーターを割り当てたり
パネル上でのショートカットで動くようにしたかったけれど現状ではやり方が分からなくて現状では未完成だけれど
Blenderでクリップボードの中身を制御する例としてアップしておきます


2017年12月26日火曜日

モデルチェック用のターンテーブルカメラを作成する

Blender Advent Calendarで3Dモデルチェック用のターンテーブル回転をさせるのに
回転設定させた他ファイルに読み込むという記事を書かれていましたが

スクリプトで回転を設定したカメラを作る方式のものを需要があるのではと
以前作成したものを調整して公開します

選択しているオブジェクト
の中心と範囲を計算して
ビュー内にオブジェクトが収まるような位置でカメラを回転させます

スクリプトのテキストを Blenderのテキストエディタで開いて
回転表示させたいメッシュオブジェクトを選択して実行すると
回転アニメーションを設定したEmptyとその子に配置したカメラを作成します

デフォルトでは 80mmのカメラでオブジェクトを上方30度から見下ろす設定になっていて
6行目と8行目にある数値を変更することで調整できます

import bpy
import math
import mathutils

#俯角(オブジェクトを見下ろす角度 0)で水平) 
Depression_deg = 30  #digree
#レンズの画角(35mm換算)
focal_length = 80    #35mm equivalent focal length

def main(camera_angle, Depression):
    context = bpy.context
    #選択オブジェクトのBBoxを取得中心位置の計算
    (bbox_width, center_pos) = get_bound_data(context)
    #カメラまでの距離を計算(外接球の半径を 最大幅/2 *1.8と設定)
    d_camera = max(bbox_width)*1.8 /math.sin(camera_angle)
    #?アスペクト比での補正は?
    #Blenderはカメラ毎にレンダリング解像度の情報を持ったりできないので 正方形でもしくは形状に合った比率で手動設定すると仮定
    #アスペクト比を取得して 短辺に合わせるよう補正することも考えられる
    table_empty = set_table_obj(camera_angle, Depression, center_pos, d_camera)
    #カメラの回転アニメーションを作成
    set_turn( context, table_empty )


#選択形状から描画範囲のデータを取得(中心点, 幅)
def get_bound_data(context):
    #get bound box size and center
    objects = context.selected_objects
    bb_point_list = []
    #選択形状のbbox値をグローバル座標で取得
    for obj in objects:
        if obj.type != 'MESH':continue
        bbox_list = [mathutils.Vector(v[:]) for v in obj.bound_box]
        mat = obj.matrix_world
        bb_point_list += [mat*v for v in bbox_list]
    if bb_point_list == []:
        return([2,2,2],[0,0,0])
    #範囲を取得
    bbox_width = []
    center_pos = []
    for i in range(3):
        min_i = min(bb_point_list, key = (lambda x: x[i]))[i]
        max_i = max(bb_point_list, key = (lambda x: x[i]))[i]
        bbox_width.append( max_i - min_i )
        center_pos.append( (max_i + min_i)/2 )
    return(bbox_width, center_pos)

def set_table_obj(camera_angle, Depression, center_pos, d_camera):
    #meke turn table objects
    #親になるエンプティをオブジェクトの中心に作成
    table_empty = bpy.data.objects.new( "empty", None )
    bpy.context.scene.objects.link( table_empty )
    table_empty.location = center_pos
    table_empty.empty_draw_type = 'ARROWS'
    
    #設定に従ってカメラを作成
    cam = bpy.data.cameras.new("TurnCamera")
    camera1 = bpy.data.objects.new( "Camera", cam )
    bpy.context.scene.objects.link( camera1 )
    
    camera1.location.x = 0
    camera1.location.y = -1*d_camera *math.cos( Depression )
    camera1.location.z = d_camera *math.sin( Depression )
    camera1.rotation_euler.x = math.pi/2 -Depression
    camera1.data.angle = camera_angle
    
    #カメラをエンプティの子に
    camera1.parent = table_empty
    return(table_empty)

def set_turn( context, table_empty ):
    #set tuabe animation
    context.scene.frame_set(0)
    table_empty.keyframe_insert(data_path="rotation_euler")
    context.scene.frame_set(100)
    table_empty.rotation_euler.z = 2*math.pi
    table_empty.keyframe_insert(data_path="rotation_euler")
    for fcurve in table_empty.animation_data.action.fcurves:
        fcurve.keyframe_points[0].interpolation = 'LINEAR'
        fcurve.keyframe_points[1].interpolation = 'LINEAR'

def focallength_to_fov(focal_length):
    #return( 2.0 *math.atan((sensor/ 2.0) / focal_length) )
    return( 2.0 *math.atan((32/ 2.0) / focal_length) )
    
camera_angle = focallength_to_fov(focal_length)
Depression = math.pi *Depression_deg/180
main(camera_angle, Depression)

レンダリング画像の設定は正方形になっているのを想定しています
必要に応じて画像サイズやカメラ位置を微調整してください

回転動作は0フレームで正面 100フレームで一周した正面に回転になっています
例えばアニメーションの開始を1 終了を100にして連続再生すれば連続して回転したようになります

This Script to Create a Turntable Animatied camera.
Select turget object on 3DView to execution, Camera view fitted to object and rotation.

2017年12月14日木曜日

Blenderで「やり直しの効く」モデリング‘‘- スナップとモデファイアの活用の話

さて 話を冒頭のMaxさんの動画に話を戻します
25分50秒以降 面を対象に「スナップ」をオンにして
ベースとなるオブジェクトの表面に沿うようにポリゴンを作成していっています
5分から14分40秒まではZbrushでのスカルプト 25分50秒までは小物の作成と配置をしていますが
今回の話題にしたいこととは異なるので割愛します
共通の物を配置することで目移りせずに安定感が出るといったことを会場でも話題にしていました


スナップ機能について軽い解説を少々
スナップ機能は磁石アイコンをオンにすると有効になる機能で プルダウンで吸着する対象を選ぶことができます
また 一番右のアイコンを有効にすることで複数選択した場合に個々の要素がそれぞれ面の表面に吸着します
(オフの場合は個々の相対的な位置関係はそのままでスナップ対象に吸着)


面スナップはビュー上で表示されている形状が対象になるため、動画では
  • スナップ対象にしたくない形状を別のレイヤに移して非表示に
  • Shift+Tabの スナップのオンオフのショートカットで切り替え
  • "/"で選択中のオブジェクトのみが表示されるモードに移って形状を確認
等しているようです

モデファイアを使った形状作成
Blenderにもモデファイアという形状のポリゴン等を元に元データを壊すことなく編集を加える機能があります
あまり使い込んでいない人でも 配列複製 再分割曲面(サブデビジョン) アーマチュア のモデファイアを使うことが多いかと思います

Maxさんのメイキングでは
スナップで皮膚に吸着したポリゴンから「厚み付け」モデファイアを付けることで鎧を作っていますね(26分00秒)

別のメイキング動画ですが 非常に勉強になったものをもう一つ

英語でも解説されているところですが
オブジェクトモードでは原点がスナップ対象に吸着するように動いて「面に合わせて回転」がオンになっていると 面の法線に合わせて形状が回転して吸着します

表面に吸着させた後、土台とのつなぎ目を滑らかにするために「シュリンクラップ」モデファイアを活用していますね
シュリンクラップは先に説明した面スナップ機能のように メッシュを指定したオブジェクトの表面に吸着させるモデファイアです
この時 モデファイアに頂点グループを指定して 頂点グループのみが変形対象にでき ウエイトの強度によっては影響度合いを制御できるのを利用しているようです
(2分45秒あたりで影響度合いを設定するウエイトをアサインして 3分50秒から実際のモデファイアの設定)


シュリンクラップモデファイアでのウエイトでの吸着の制御はこんな感じ
例えば画像の円盤状のオブジェクトで 変形対象の頂点グループのウエイトのつけ方次第ではなめらかに接続するような感じにもできるということです

また5分50秒以降では バージョン2.79の新機能の「サーフェイス変形」モデファイアを利用して 曲面に沿わせた形での変形を解説されています

ここまでモデファイアを活用されている動画を参照してきましたが
重要なのは元データを壊さずに形状をモデリングを進められて後からの調整が可能なことです
モデファイアのパネルの「適用」ボタンを押したり オブジェクトメニューの「変換」を実行して 編集結果をメッシュそのものに変更を適用できますが、
適応しなければいくらでもモデルのデータの使いまわしができるということでもあります

サブデビ+シュリンクラップのリトポロジー

ここまでは全体のディテールを作っていく作業でしたが、映像作品等に使ったりするには メッシュを整える「リトポロジー」という作業が必要になります
作業の詳細についてはデータを使う用途によって違ってやり方も千差万別なのですが
BlendXJP会場の動画のみで解説されてた方式が面白かったので最後に書いておきたいと思います

リトポロジーの作業は 先にとりあげた 鎧を作るときのように 面スナップを使って面ポリゴンを作っていくようですが
少ない手間でディテールを転写する方式として 再分割のモデファイアを入れた上でシュリンクラップのモデファイアを入れるというものを紹介されていました
面の流れを考えてメッシュを打っていくと どうしても分岐部分の処理に悩まされることになります
ここの処理を 自分で手で打ってしまうのではなく 多角形ポリゴン(Nゴン)のまま残していて
分割処理できれいなメッシュにしてもらおういということですね


映像作品等の比較的ハイポリゴンでも問題がないという場合に使える手法なものの
非常に面白い考え方だと思います

長々と書いてしまいましたが 何かしらの一助になるようなことがあれば幸いです

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

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


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