🐵

Blender Pythonでテクスチャを頂点カラーにベイクする

2024/02/17に公開

概要

Blender内のPythonを利用して、メッシュの各頂点のUV座標からテクスチャの色を取得し、頂点カラーにベイクする方法を紹介します

環境

  • Blender 3.6

Pythonスクリプト

import bpy
import numpy as np


# UV座標からテクスチャの色を返す関数
def tex2D(TexBufferList, UvPos):
    # 参考 https://blender.stackexchange.com/questions/139384/get-rgb-value-of-texture-from-face-on-mesh
    
    # UV座標値を0.0~1.0の範囲にクランプ
    UvPos = np.clip(UvPos, 0.0, 1.0)
    
    # UV座標がテクスチャ上のどこのピクセルに該当するか計算
    UvPixcelPos_X = round(UvPos[0] * (TexSize_X - 1))       # [px] ※0始まり
    UvPixcelPos_Y = round(UvPos[1] * (TexSize_Y - 1))       # [px] ※0始まり
    
    # ピクセル座標からテクスチャの色が格納されたバッファリストのインデックスを計算
    ListIndex = 4 * ((TexSize_X * UvPixcelPos_Y) + UvPixcelPos_X)
    
    # 色を取得
    r = TexBufferList[ListIndex + 0]                        # Red
    g = TexBufferList[ListIndex + 1]                        # Green
    b = TexBufferList[ListIndex + 2]                        # Blue
    a = TexBufferList[ListIndex + 3]                        # Alpha

    return [r, g, b, a]


# 選択されているメッシュを取得 (オブジェクト名から取得する場合はbpy.data.objects["オブジェクト名"]
Mesh = bpy.context.view_layer.objects.active
UvLayer = Mesh.data.uv_layers[0].data                       # 0番目のUVレイヤを取得
Material = Mesh.material_slots[0].material                  # 0番目のマテリアルを取得

# マテリアルに割り当てられているテクスチャを取得
Texture = Material.node_tree.nodes["Image Texture"].image

# テクスチャサイズを取得
TexSize_X = Texture.size[0]                                 # X方向 [px]
TexSize_Y = Texture.size[1]                                 # Y方向 [px]

# テクスチャの色が格納されたバッファ配列(1次元)をリストとして取得
# テクスチャの左下を原点にして、X方向優先に1ピクセル目のR,G,B,A、2ピクセル目のR,G,B,A...と
# 連続して色がfloatで格納されている。X方向が終端まで行ったら次の+Y行目に進んでいく。
# リストにしないと色の取得が凄く遅い (参考 https://blender.stackexchange.com/questions/124264/how-to-read-a-pixel-from-an-image-texture-with-python
TexBufferList = list(Texture.pixels)


# 頂点カラーを取得。なかったら追加する
if not Mesh.data.vertex_colors:
    # Blender3.3以前の場合はbpy.ops.mesh.vertex_color_add()と書く
    Mesh.data.vertex_colors.new()
VertexColors = Mesh.data.vertex_colors[0].data


# メッシュの各ポリゴンごとにループ
for poly in Mesh.data.polygons:

    # 現在何ポリゴン目を処理しているか表示
    print("Polygon progress: %d / %d" % (poly.index + 1, len(Mesh.data.polygons)))
    
    # 現在のポリゴンの各頂点ごとにループ
    for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
        
        UvPos = UvLayer[loop_index].uv                      # 現在の頂点のUV座標を取得        
        TextureColor = tex2D(TexBufferList, UvPos)          # UV座標からテクスチャの色を取得

        # テクスチャから拾った色を頂点カラーに設定
        VertexColors[loop_index].color = TextureColor

実行方法

  1. 例としてデフォルトキューブの各面を10分割し、適当な画像テクスチャを割り当てたモデルを用意します (下図のテクスチャはUV Checker Map Makerというサイトで生成しました)

  2. 「ウィンドウ」タブの「システムコンソール切り替え」をクリックし、スクリプトの実行結果やエラーが見えるようにする

  3. 「Sclipting」タブの「+新規」ボタンをクリックして以下のPythonスクリプトを貼り付ける

  4. Cubeオブジェクトを選択した状態で「▶」ボタンをクリックし、スクリプトを実行する

実行結果

下図のようになれば成功です。
各頂点ごとにしか色を設定できないので、どうしても柄がぼやけてしまいます

色をはっきり分けたい場合は、色を分けたい境目に頂点を増やし、UV展開をし直す必要があります

エクスポート時の注意点

Unity(VRChat)向けにメッシュを出力する場合、エクスポート設定で頂点カラーに"リニア"を設定してください

応用例

VRChatのQuestアバターでよく使われるToonLitやStandard Liteシェーダーの頂点カラーを乗算する仕様を利用して、マテリアル数1、テクスチャメモリ0.00MBの超軽量Quest対応アバターを作れます
https://x.com/Hyper_Mesh/status/1757008225021669410

関連記事

https://zenn.dev/dimebag29/articles/61811779c3a13b
https://qiita.com/dimebag29/items/75ce6a5c847a36b93f77

Discussion