2018年12月25日火曜日

モデリング不要な立体視マテリアルのお話(前編)

※ この記事は Blender Advent Calendar 2018 に合わせて作成しました

ゲームでビル等の建物を表現する時に 建物の中をポリゴンで作りこむのではなく
壁面の見え方を計算で作りだしてそれっぽく見せてしまう「インテリアマッピング」
という手法があります。

ゲームエンジンでゲーム作る人にはある程度知られた手法ですが
アニメ等の他の界隈の人の話題にも出ることも多くなったのと
Blenderでもできるのを確認したかったので試してみていました

やってみた後 街中の景色を見ていて作ってみたのがこれ
こちらのバルコニーのようにマッピングする簡易版がリツイートが伸びたこともあり記事にしてみます
この解説ではアニメの背景のような陰影付けをテクスチャに描いてしまう
作画主体の作業のものを扱います

2回に分ける予定で今回は 基本的な考え方を取り扱うので、使い方だけ知りたい方は後編へどうぞ

まず 複数の物が奥行きを持って見えるというのはどういうことでしょうか?

  1. 奥まっているところは陰った感じになっている
  2. 視線を動かした時には手前のものと動く幅が違う
  3. 遠くのものほど小さく見える
  4. 空気遠近法

といったものが考えられると思います
1に関しては初めからテクスチャの陰影で表現することに 4は無視します
2と3に関しては視界の中の変化と見る角度の変化の違いで実際には同じものです
奥まったものが見えることを図にして考えてみて見ます

平行な面F1 F2があった時に
F1のある点Aから見通した場合 目線の先のA’では少しだけずれた位置のものが見えることになります
つまりF1の面上で見る角度に応じて、ちょっとだけずらしたテクスチャを表示すると奥まって見えないでしょうか
どう計算すればいいでしょうか?



ここでベクトルというものでてきます
調べると「ベクトルとは大きさだけでなく、向きももった量」 と出てきます
三次元の座標も「位置ベクトル」と言いますし、画像のピクセルの座標(x,y)もベクトルです
3Dで面がどちらの方向を向いているのかという表すものを「法線ベクトル」と言ってXYZ軸それぞれの大きさで表現されます
細かいことは抜きにして、数字の組み合わせで向きや位置が表現されていると考えてください
そしてコンピューターの計算では ベクトルの向きや大きさを使う色々な便利な計算が用意されています

因みにBlenderでは画像の座標を
左下を基準の0に 画像の端を1.0になるものとしています
一般に画像の縦横の大きさを1.0として(0.0~1.0)の間で画像内の座標を表すものをUV座標(UVベクトル)と言います



3Dのデータの見た目を設定する仕組みをシェーダーと言いますが
ここからBlenderでそれを設定するための「ノードエディッタ」の使い方を順を追ってみていきたいと思います
Cyclesレンダー利用していきますので レンダーエンジンをCyclesに変更してください
(Blender2.8以降ではShaderEditorで Eeveeレンダーでも同じ操作でできるようになっています)

下準備に 平面を作成してUVを作成しておいてください
また、効果を確認するためにカラーグリッド画像を作成しておきます
平面を選択した状態で
ノードエディッタに移ってマテリアルタブを選択して「新規」ボタンを押します

四角いものが2つが 曲線でつながれた状態になっているかと思います
四角いもの一つ一つがノードと言って、掛け算割り算といった感じの処理をしたりしてくれるもので,
ノードの左右に付いた丸がソケットと言って 左側のソケットから入った情報を処理して右側のソケットに出力します
ノードによってシーンから直接情報を取得するので入力のないものもあって
「マテリアル出力」に入った情報が3Dの形状の見た目を設定します

ソケット部分からドラッグ操作をすると 曲線が出るので 別のソケットまで引っ張るってつなげると情報が伝わる状態になります

まずは テクスチャを表示してみます
最初に設定されてる設定は使用しないので左側のノードは消去して
追加メニュー(Shift+A)から  シェーダー>放射テクスチャ>画像テクスチャを選んで図のように並べてつなげます

画像テクスチャノードの写真アイコンを押して 先に作ったテクスチャのグリッドを選んで
3Dビューのシェーディングマテリアルレンダーにするとカラーグリッドが表示されるかと思います


さて、画像テクスチャノードの左側に「ベクトル」のソケットがありますがこれは何に使うものでしょう?
これは画像のどの座標を3Dの面上にどう対応させるかを決める "UVベクトル"を指定するものになります
今は何も指定していないので アクティブなUVが自動的に設定されています
Blenderは複数のUVマップを持つことができますが
テクスチャのベクトルに入力>UVマップのノードを追加することで利用するUVを指定することができます


さて 見る角度によって画像をずらすノードの簡単なのを作ってみます
先にノードは掛け算割り算といった感じの処理をしたりしてくれるものだと言いましたが
UVに角度によって変わる値を加えることができればできそうです

入力>ジオメトリ ベクトル>ベクトル変換 コンバーター>ベクトル演算のノードを追加して図のようにつないでみてください
(図中で"追加"となっているノードはベクトル演算を選択した時に作られるもので ノード内のプルダウンで名前が変わります)
ジオメトリ"ノーマル"は面がどの方向を向いているかの情報で "ベクトル演算"の下のプルダウンを"カメラ"にすることで
ビューの方向から見て面がどういった方向を向いているのかといって情報が出力されます

3Dビューの表示を回転させてみてください テクスチャが移動しているでしょうか

長くなったしまったので今回の記事はここまで
次回後編は グループ化したノードを使って 実用に即したものを解説したいと思います

2018年10月12日金曜日

Blenderのカメラマップで背景動画の話(2)

前回は「プロジェクション」でのUV展開とUV投影モデファイアについて取り上げましたが
ここからは より実作業に使うものを紹介していきたいと思います

パーツ分け

パーツ分け前回の作業で作ったキャプチャーです


前後のがけの重なりがあるために
手前の岩が奥の岩壁に投影されてしまっているのが分かるでしょうか


こういう場合は元になる2Dの絵自体をパーツ分けしてしまうことにします
今回の分割した画像はこんな感じです。



手前側に当たるパーツに隠れていた部分を塗り足ししています

3D側でもこの画像と同じように個々のパーツに分割してしまって
別々の画像を割り当ててみます

まず 別のパーツにしたい部分を分割します


編集モードで分けたいメッシュを選択して Pキーを押すことで別形状にできます
分離で欠けてしまった部分のエッジを押し出しする等
分けた部分で隠れる部分のポリゴンも少し足しておきます

別オブジェクトに分離で分けたものは 元と同じマテリアルやモデファイアが設定された状態になっています

新しいマテリアルとテクスチャへの置き換え

パーツ分けしたオブジェクトのそれぞれのマテリアルを変更していきます

まず 一番奥の形状を選択してマテリアルタブの「+」のアイコンを押して新しいマテリアルを作成します
マテリアルを選択した状態で「+」のアイコンを押すと 設定を複製した設定がされます
同様に テクスチャタブも 設定を複製したテクスチャ設定を作成します



さらにテクスチャタブの画像パネル内の上側のフォルダアイコンを押して
一番奥にあたる画像ファイルを新しくシーンに読み込みます

透過部分ので設定

ここまではここまでは透過部分のない1枚絵でしたが
手前側に来るパーツのテクスチャ画像には透明な部分があるので
少しだけ設定が異なります

まず これまでと同様に新しいマテリアルを作成して
さらに「透過」にチェックを入れ 「アルファ」の値を0にします

テクスチャタブも新しいテクスチャを作成して
パーツに対応した画像を新しく読み込み
テクスチャタブの下の方にある「影響」パネルの「アルファ」のチェックをオンにします


3Dビューの表示をマテリアルにして確認するとテクスチャの透明度を元に透けているのが確認できます



下絵機能について

前回で触れた下絵機能ですが、
Blenderには複数の下絵を設定できるというメリットがあります
また、3Dのオブジェクトの手前側に下絵を表示したり 透明度を変更することもできます

後ろ側に表示する下絵だと、シェーデングやマテリアル表示でディテールを調整するのは難しいこともあるのですが、前表示だとメッシュに隠れる部分の調整もしやすいです

今回扱っているような複数のパーツに分けて作る場合
下絵画像で特定のパーツの色を変えたものを用意して下絵表示したり
指定のメモを手前に表示するのもいいでしょう

画像のアルファ部分は透過するので扱いやすいです

私は動画作成時に黒でトリミング枠のような画像を表示しています


コンポジット時のためのマージンを始めからつけつつ セーフエリア表示よりも、より仕上がりを意識したカメラワークがつけれるように感じます



カメラワークをつける


Blenderのレンダリングサイズの設定は1つのシーンに1つなので
同じシーンのままカメラワークをつけようと思うと
モデリング用と撮影用でカメラをいちいち切り替えることになります。

幸いBlenderには同じファイル内に複数のシーンを持つことができ、
他のシーンのデータを「背景」として読み込む機能があります

そこで 新しいシーンを作ってそちらでカメラワークをつけることとします

新しいシーンの作成

まず ウインドウ上部のSceneとなってる部分の横の「+」アイコンを押して新規シーンを作成します
いくつか種類が表示されますが引き継ぐ必要のある設定はないので「新規」を選択します


プロパティタブのシーンタブを表示すると
そこの上部に「背景」という項目があると思います 入力部分をクリックするとSceneという名前が表示されるので選択します

3Dビューにさっき作っていたシーンが表示されるかと思いますがどうでしょう。
表示はされたものの 選択しようとしても反応しませんね。 

今は新しく作ったシーンにカメラがないのでレンダリングはできませんが
この「背景」はレンダリングには元のシーンでの位置にあるようにレンダリングされます

「表示はできるけど操作はできない」という状態で
レイヤ設定は元のシーンと同じになるので表示非表示等の管理はレイヤで管理することになります


カメラの作成

さて、レンダリングできる状態にしたいのでカメラを作成します
普通にシーン内で新規作成してもいいのですが、
位置や回転のデータを利用するために プロジェクションに使ったカメラを複製して持ってきます

まず、元のシーンに移動してカメラを選択します。
アウトライナで目的のカメラを選択すると自動的にシーンも移動するのが楽です。

選択したカメラを複製します
この時カメラの位置等は移動させたくないのでShift+Dの後右クリックするといいでしょう
これを 撮影用のシーン利用できるようリンクします
複製したカメラがアクティブになっているはずなのでCtrl+Lでリンクメニューを表示して
「オブジェクトをシーンへ」の項目の中から撮影用のシーンの名前を選択します

アウトライナ上のモデリングのシーンと撮影用のシーン それぞれに同じ名前のカメラがあるのが確認できるでしょうか

ここで 撮影用のシーンに戻って プロパティのシーンタブのシーンのカメラに先ほど複製したカメラを設定します
(同一ファイル内にあるカメラが選択可能なので 複製したカメラをシーンにリンクさせる必要はないのですが
 移動等の操作をするためシーンにリンクする操作を挟んでいます)

これで撮影用のシーンでレンダリング可能な状態になりました

今回のように投影したものを撮影して出力したい場合
一度に撮る範囲は元画像の一部なため、カメラの画角の設定も違ってくるかと思います
元画像の中央部を切り抜いたようなようにするには 
現在のカメラの画角*元画像の長辺/撮影で出す画像の長辺
という風に式を入力するといいかと思います
カメラを奥行方向に移動させたい場合 移動(G)のあと"ZZ"と、Zのキーを連打することで移動方向をカメラの奥行き方向に制限できます

こうして作成したカメラに動きをつけることでこういったものが作れます
参考までにここまでのファイルをアップロードしておきます
https://www.dropbox.com/s/4gdg14ioecp8k5m/Projection_sample.zip?dl=0

2018年10月10日水曜日

Blenderのカメラマップで背景動画の話(1)

私は何年か Blenderで絵をカメラマップ(カメラプロジェクション)する関連の仕事をしていたのですが
少しまとめておいた方がいいかと思い立ち メイキング的なものを使って手法の解説をしてみます

カメラマップとは?

3D形状に対してカメラ方向から画像を貼り付けるする手法で
手描き背景のテイストを維持して立体的な動きをつけれるので 特にアニメで利用されます

Blenderにはカメラマップで動画を作ったりするのに便利な機能があります
こちらでは この画像を元にその機能について解説していきたいと思います。

下絵の読み込みとカメラ設定

Blenderではカメラビューに「下絵」となる画像を表示機能があります
この下絵となる画像を読み込んでカメラの設定を合わせます
はじめに 3Dビューのプロパティパネルを表示して 下絵の「画像を追加」ボタンを押し「開く」でファイル選択画面になるので画像を選択します

カメラビューで見ると 読み込んだ画像が半透明表示されるかと思います
カメラの縦横比はレンダリング設定で決まるので 設定の縦横の数値を読み込んだ画像と同じにします。


次に絵の中と3Dが 重なるようにカメラを調整します
基準になるような形状を作成してそれに合わせるようにするといいかと思います
今回は、地面にあたる部分に平面を配置して グリッド状にポリゴンを分割しています
3Dビューをポリゴン表示モードにして 下絵内の主要な線とポリゴンの線を合わせていきます

この時 下絵の中のキモになる部分が基準形状と重なるようにカメラを移動させたあとに
そこを基準にカメラを回転させるとよいと思います
3Dカーソルをその合わせた地点に移動させ その3Dカーソルをピボットポイントにして
カメラを回転移動させます 回転の時にXキーを押すなど回転軸の制限をするとやりやすいかと思います


因みに この操作を簡単にできる 画像中の目印になるものを線で指定してそれに合わせたカメラ設定をする BLAMというアドオンもあります


UV展開

UV展開とはポリゴンの”この面にテクスチャのこの部分を使う”と対応づけるための操作です
カメラマップではカメラ方向からの見た目を元に設定します

これにはいくつかの方法があります。
まずは他のサイトでも解説されている方法でやってみます

 
ビューをカメラビューにして エディットモードに入り Aキーを押して全ての面を選択した状態にして
U キーを押すか メニュのメッシュUV展開の項目を開き 「プロジェクション」を実行します


デフォルトではタイムラインが表示されている下側のビューを
UV/画像エディッタに変更して 下絵として読み込んだ画像を表示してみます
写真風のアイコンを押すことで シーンにリンクした(読み込んでいる)画像の一覧で選択できます
他のパネル等の画像選択部分でも同じ操作になるので覚えておいてください


カメラビューと同じ見た目でメッシュが表示された状態になるかと思います
(今回は細かい調整をしないためUVの操作の話は割愛します)

マテリアルの作成

平面にマテリアルとテクスチャを割り当てます
Blenderはマテリアル(色や透明の設定)>マテリアルに関係するテクスチャ設定 という順の設定になります

まずプロパティエディタのマテリアル タブを表示して「新規」で新しいマテリアルを作成します
テクスチャそのままの見た目にしたいので シェーディングの「陰影なし」のチェックを入れます


次にテクスチャタブに移って 「新規」で新しいテクスチャを作成して
タイプのプルダウンが「画像または動画」    画像のパネルで部分を読み込み済の下絵画像を指定
マッピングパネルの 座標を「UV」に  マップをUVMapに指定します 


ここで一旦レンダリングしてみました
設定がうまくいっていれば 元の画像によく似た見た目の画像が生成されると思います

3Dビューをマテリアル表示にして回転させるとこんな感じです


もしかしたら 元の画像に比べて少し歪んで見えるかもしれませんね
面の分割をさらに細かくし直して UV展開を再度実行することで 誤差が小さくなって歪みが改善しますがちょっと面倒です

また、このままだと平面1枚なので、3Dソフトを使うからには凹凸をつけて立体的にしたいところですが
面を編集してUV展開を何度も繰り返すことになるのでやはり面倒ですね
ここから別の手法を使った作業を紹介していきます


モデファイアを使う

Blenderにはモデファイアという 便利な編集機能があり 元のポリゴン等の設定を変更することなく変形をかけたりできます


まずは「追加」のプルダウンから「細分割曲面」を選択します
これはレンダリング時にポリゴンを細かく分割してなめらかな曲面になるように計算させる機能ですが
今回は カメラマップの誤差を少なくするために使うので
なめらかな曲線にはしないような「シンプル」のオプションを選択しておきます


UV投影
UV展開の「プロジェクション」と 同じことができる機能です
オレンジ色のキューブのアイコンのある部分で 投影の元になるカメラを指定して
変更対象のUVマップを選択して
そのままでは縦横比1:1になってしまうので 縦横で長い方の比率を指定します


Blenderは入力ボックスに数式を入力して指定できるので
XYで長い方の辺の入力ボックスに”長辺の長さ/短辺の長さ”と入力するといいかと思います
(因みにこの機能は今あるUVマップに対して作用するのでUVマップを指定していない状態だと機能しません)
投影の元になる形状を選択したことからも分かるように
UV投影のモデファイアは必ずしもレンダリングで使っているカメラである必要はありませんが
形状をカメラビューで確認することが多いためモデリング時にはシーンのアクティブカメラの状態にした方がいいかと思います

ここで3Dビューの表示方式をマテリアルにしてみます
カメラビューで適当に平面を動かしてみると カメラから見たテクスチャはの位置は変わらず形状だけ移動するかと思います

これで何ができるかというと 
カメラ以外の方向からもテクスチャの適応結果を見ながらモデリングができるのです

今回のものでは 谷間の淵に合わせてナイフツールでエッジを作成して谷の方に押し出してみてます
リアルタイムで調整できてる感じが分かるでしょうか

ここまででBlenderのカメラマップについて使用している機能について解説してきましたが
画像も多く長くなったので記事を分けたいと思います
Blenderのカメラマップで背景動画の話(2) に続く

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でクリップボードの中身を制御する例としてアップしておきます