今日こそやるぞ5日目(いやもう休む)

今日こそやらないページ(サービス潰れて移転五回目)

頂点法線とオブジェクトスペースノーマルカラーを行き来するスプリクトが最低限度できた「Blender+Python」

追記8/29日付けの記事でスプリクト更新
糞重い機能追加されたけどそれ以外に間違ってた箇所の修正とかもあるので
使いたい奇特な人はそっちを推奨
こっちのページでは最下段にある想定した使い方のみ参照のこと


前回記事で書いてたスプリクトがとりあえずうまく行ったので貼っとく

カスタム法線を頂点カラーとしてオブジェクトスペースノーマルマップ色に出力できる
(そこからはblenderのベイク機能でTextureとしてだせるはずな想定)

逆にそうやってテクスチャ側でノーマルマップとして法線いじった後に
最後にカスタム法線としてノーマルマップを頂点カラー経由して
TEXから頂点カラーはこれまたベイク機能で出せるはずな想定)
焼き付けることができるという感じ
(オブジェクトスペースノーマルマップが使えないMMDとかに持ってく場合とかに使えると思いたい想定)

使い方みたいなのはスプリクトの更にしたの方に
(機能説明であってスプリクトの実行の仕方とかは他の人のページにあるのでメモらない)

たまにうまく行かないことがあるので
モデル側が悪いのかスプリクトが悪いのか調べてもうちょい更新しないと駄目かも

スプリクト前に現状の動作的な注意
8/26まだちょっと動作怪しいっぽい

1、なんか法線のを色として取ったり返したりする際に
ちゃんと値が法線として正規化されてるならいいけどそうでない場合はへんな感じになる?
たとえば試しに全頂点に(1,0,0)という法線を突っ込むとちゃんと全部同じ方向を向いてくれるが
(1,1,1)とか法線としてちょっと変な数字を入れると全部バラバラにねじれた感じの法線が入っちゃうみたいっていうのだけは実験済み
これは解決(でも下のスプリクトにまだ反映はしてない)
mathutilsとかいうのインポートしてそっちの機能で処理してやればよかったみたい

逆にその状態の法線から色を取ろうとすると頂点色が全部灰色になるのでやっぱ法線の正規化が鍵?
こっちが未解決
法線がノーマライズされてないからとかでなく
自分の作ったスプリクトで入れ込んだカスタム法線を読み込むことが出来ないっぽい?
(法線が取れずに全部0が帰ってきて、結果全部灰色になる)
blenderの機能で転写したカスタム法線ならまず普通に色がつかないことはないっぽい
要するに現状
法線->色->法線 まではいけるけどそこからもう一回
法線->色->法線->色 みたいにもう一回となると無理っぽい?
回避策として同じ形状を一度複製して
スプリクトで法線流し込んだほう->複製できたてホヤホヤのほうに再度転写してやれば
スプリクトでいじった法線を再度色化はできるっぽいのでとりあえずはいいか?

2、あとまたそれとは別に頂点色の取得と代入が上手くいかない時があって
(いま別で作成中のスプリクトのほうで特に)
なんかの条件でいろいろちがうっぽい?
これはまだ全然原因わからないので調べる


ってか相変わらずpythonに必要なインデントの高さがブログに貼り付けると全部勝手に消去されるので
アスキーアート枠使ってインデントの半角スペースを全部全角に置き換えてみて配置
コピペで実行するなら一度全部半角スペースに置き換えないと駄目かも?

8/22 22時頃 カスタム法線がない場合に通常の法線から色を取る処理を追加
import bpy


class NormalColorImput(bpy.types.Operator):

    #ボタン押して呼び出すときの名前
    bl_idname = "object.normal_imput_operator"

    #ボタンに表示するときの名前
    bl_label = "Normal -> Color"

    def execute(self, context):
        
        #選択中オブジェクトを順番に処理するためのfor?
        for object in bpy.context.selected_objects:
            #選択オブジェクトがメッシュかどうか判定
            if object.type == 'MESH':
        #選択オブジェクト取得
                bpy.context.scene.objects.active = object 

                
                
                #新規頂点カラーグループを追加
                bpy.ops.mesh.vertex_color_add()

                #作成したばかりのグループを現在の名称(Col)からNormalColorに変更
                bpy.context.object.data.vertex_colors["Col"].name = "NormalColor"
                
                #取得したオブジェクトのメッシュをさらに取得?
                #メッシュ取得しないと各種頂点情報取れない?
                mesh = object.data
                
                #頂点カラー取得して色をセットする準備
                VertexNormalColor = mesh.vertex_colors["NormalColor"]                

            
            #カスタム法線がある場合はそちらから取得
            if mesh.has_custom_normals == True:
            
                #ポリゴンを一つ一つ処理?                
                for poly in mesh.polygons:
                
                    #ポリゴンの頂点を一つ一つ処理?                      
                    for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):

                        #blnederではXが左右、Yが前後、Zが上下なことに注意
                        #カスタム法線はmesh経由のnormalでなくloops経由で取れる
                        #カスタム法線セットがない場合は旧カスタム法線だとかの色を持ってくる感じ?
                        #とにかくエラー的な動作になる
                        VertexNormalColor.data[id].color.r = 0.5 - (mesh.loops[id].normal[0] / 2)
                        VertexNormalColor.data[id].color.b = 0.5 - (mesh.loops[id].normal[1] / 2)
                        VertexNormalColor.data[id].color.g = 0.5 + (mesh.loops[id].normal[2] / 2)
                        
            #カスタム法線がない場合は通常の法線から取得
            else:
                
                for poly in mesh.polygons:                    
                    for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):

                        VertexNormalColor.data[id].color.r = 0.5 - (poly.normal[0] / 2)
                        VertexNormalColor.data[id].color.b = 0.5 - (poly.normal[1] / 2)
                        VertexNormalColor.data[id].color.g = 0.5 + (poly.normal[2] / 2)
                    

        return {'FINISHED'}



class NormalColorSmooth(bpy.types.Operator):
    bl_idname = "object.normal_smooth_operator"
    bl_label = "ColorSmooth"
    
    def execute(self, context):
    
        for object in bpy.context.selected_objects:
        
            target_obj = object.data
            
            #頂点カラーがハードエッジ的につく場合のスムース            
            bpy.context.scene.objects.active = object            
            bpy.ops.object.mode_set(mode='VERTEX_PAINT')
            target_obj .use_paint_mask = False
            bpy.ops.paint.vertex_color_smooth()
            target_obj.update()
            bpy.ops.object.mode_set(mode='OBJECT')
    
        return {'FINISHED'}



class NormalColorOutput(bpy.types.Operator):
    bl_idname = "object.normal_output_operator"
    bl_label = "Color -> Normal"

    def execute(self, context):
        
        object_normal_set = []
        
        for object in bpy.context.selected_objects:
            if object.type == 'MESH':
                bpy.context.scene.objects.active = object 
                mesh = object.data
                

            #頂点カラーグループがあるかどうか、ある場合はその格納
            vcolor_array= mesh.vertex_colors.active.data if len(mesh.vertex_colors) else None
                
                
            for poly in mesh.polygons:
                
                for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):
                    
                    vcolor= vcolor_array[id].color
                    
                    VerR = ( vcolor[0] * -2 ) + 1
                    VerG = ( vcolor[1] * 2 ) - 1        
                    VerB = ( vcolor[2] * -2 ) + 1

                    #カスタム法線にセットするためのリストにAdd                    
                    object_normal_set.append*1

            #カスタム法線にimport        
            object.data.normals_split_custom_set(object_normal_set)
    
        return {'FINISHED'}





class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Normal_Color_Panell"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_category = "Tools"


    def draw(self, context):
        layout = self.layout
        
        row = layout.row()
        layout.operator("object.normal_imput_operator")
        
        row = layout.row()
        layout.operator("object.normal_smooth_operator")
        
        row = layout.row()
        layout.operator("object.normal_output_operator")

def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.utils.register_class(NormalColorImput)
    bpy.utils.register_class(NormalColorSmooth)
    bpy.utils.register_class(NormalColorOutput)
    

def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    bpy.utils.unregister_class(NormalColorImput)
    bpy.utils.unregister_class(NormalColorSmooth)
    bpy.utils.unregister_class(NormalColorOutput)
    
if __name__ == "__main__":
    register()




想定した使い方的なメモ



まずスプリクト実行前に通常機能で法線の転送
(このへんは他の人の記事でわかりやすく載ってるかも)


法線を転送したい側モデル(今回は髪の毛)と送る側(今回は球体)を用意して



位置合わせ
この際どちらのモデルもトランスフォームの位置、回転、拡大縮小の値は初期化しとかないと駄目っぽい?



で法線を受け取る側(今回は髪の毛)、送る側の順(今回は球体)にオブジェクトを選んでから
データ転送の「データ」
「カスタム法線」



こうなる
自動スムースにチェック入ってないとカスタム法線でのシェーディングにはならないので注意
(たまにガタついた法線が転送される場合があるんだけど自分のほうだとまだいまいち原因がわからない)

たまに転写法線がガタつく場合があるんだけどその場合は
面コーナーマッピングのオプションを色々変えてみるとよさ気っぽい
自分の場合は投影面の補完にしたら滑らかになった





でここからがスプリクトで


スプリクト実行するとでてくる画面左側の
Normal_Color_Panelの
「Normal -> Color」をポチると
カスタム法線から頂点色にオブジェクトスペースノーマルマ

ップの色をつけてくれる
(新規にNormalColorという名前の頂点色セットが作られる、これはスプリクトが勝手にやる)
(ただ押すたびにNormalColorをつくっちゃってNormalColor1、NormalColor2とか増えていっちゃうので逐一手動で消してあげてください)
(画像は頂点色表示するマテリアル適応済み、これはスプリクトで自動でやるわけではないので)


8/22追加処理分の説明画像
(スクショは後から撮り直した画像だけどオプションよくわかってなくて髪の毛への法線転写がなんか上手くいかなかった時に取ったので髪の毛側の色がちょっとガタついてる)


今回の場合は体はまだ法線転写とかしてないのだけどその場合はカスタム法線でなく通常法線からとる
色がこんな具合にハードエッジ的につくので「ColorSmooth」ってボタン押すと



頂点カラーが全体的に一段スムースがかかる

これとりあえず上手く動いているようでもっとがっつりボカシたいときに
再度押してもボケ感が広がっていってくれないので後で調べる

これで色あいの方向性が画像みたくならない場合は
オブジェクトの回転や位置、スケールに変な値入っている可能性あるので
それぞれ「適用」処理してから移動回転なら0、スケールなら1にする
(転写元のオブジェクトの方も要確認)

転写法線と通常の法線もある程度ブレンドしたいときの素材取りに使えるはず
といっても転写元と通常法線の単純ブレンドまでならモデファイアのほうのカスタム法線使えばいいっぽいけども
自分の場合なら顔まではカスタム法線でいろいろ弄りたいけど首から下はそのままの法線使いたいのでそのための素材取りに使う予定


ここからテクスチャへの出力はblender標準機能のベイク機能でいけるはずだけどまだ試してない
試した普通に行けた良かった


ベイクモードで「頂点色」を選択
出力先の画像指定は「UV/画像エディター」の方で行う(マテリアルの設定ではない)
(これを理解してなくて他の時にベイクしようとしても何度もベイク上手くいかなかったりした)
これも他の人のページのほうがいろいろ詳しく載ってると思う




こんなかんじに出力される

でこういうので何個かノーマルマップの素材を出してフォトショップとかで調整して
再度ベイク機能で逆ベイクして頂点色としてそれを持ち込む(予定)
簡単にそっちも試したけど先に頂点カラーグループは作っておかないと駄目
焼きこむ元になるテクスチャの方は今度はマテリアルに貼られたテクスチャ
ここまではちゃんと出来たのでおいおい思ってたことが出来ないよと全て徒労に終わる可能性はなくなった(先に試しておけという話がある)

9/6追記
この時使用するUVMAPではシャープかける部分はUVの島として切り離して置かないとややこしくなる
UV上で同一頂点にはならないようにしなきゃダメということ)
ちょっと考えれば当たり前なんだけどTEXから頂点カラーとしてベイクし直す時
シャープなら面ごとに別の色を読み込んで欲しいところ、同じピクセルの色を拾ってこざるを得なくなるので
どーしてもスムース的な色合いになってシャープ的にパキッとわかれなくなる
上の体のUVは本村式ラインとかやってた都合上そのへん意図せずそうなってたんだけど
他の箇所の作業しててそうなってなかった箇所があったので念のためメモ追記






で画像のようにカスタム法線セットなし、頂点色セットにNormalColorというセット(これにテクスチャからベイクしなおしたオブジェクトスペースなノーマルマップの色が入っている)が有る状態で「Color -> Normal」をポチると



頂点色からカスタム法線が生成される



これでどういうことやっていきたいか?とかは具体的にはこのモデルでの作業終わったら経過報告とともにまたメモっていくとおもうのでそっちの記事でかくけどとりあえず今のところとしては




こういう感じでシェーディングの影は単純化
複雑な影は手書きテクスチャで同じ色が使われて馴染むぞ!っていうギルティギアだとかポケモンだとか多分プリキュアEDとかでもやってる気がする手法
ぶっちゃけ後頭部の髪の毛ならこれだけでいい気がするけど
顔だとかはさすがにいろいろ細かく調整しないと行けないので
マスクとか作ってあれこれするの考えると一度テクスチャで作業したかったという為の機能
実際それで作業してうまくいくかはやってみないとわからんのだけど

あとやっぱ上の画像、自分の手書き影が下手すぎて上手いこと行ってない気がするなぁ……
やりたいことは一応実装できてるけどクオリティがいかんとも……


*1:VerR,VerB,VerG