Blender Pythonでテクスチャを頂点カラーにベイクする
概要
Blender内のPythonを利用して、メッシュの各頂点のUV座標からテクスチャの色を取得し、頂点カラーにベイクする方法を紹介します
Unity(VRChat)でメッシュを利用する場合、頂点カラーにはsRGB色空間ではなくLinear色空間の色をベイクする必要があることに注意してください
環境
- 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]
# 受け取ったsRGB色空間の色をLinearに変換して返す関数
def ColorSpaceConversion_sRGB_to_Linear(Color):
# 高速化のため近似式で色空間変換 (参考 https://tech.cygames.co.jp/archives/2339/
Color[0] = Color[0] ** 2.2 # Red
Color[1] = Color[1] ** 2.2 # Green
Color[2] = Color[2] ** 2.2 # Blue
return Color
# 選択されているメッシュを取得 (オブジェクト名から取得する場合は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座標からテクスチャの色を取得
# Unityなどの標準でリニアワークフローを採用しているゲームエンジンで使用する場合は
# テクスチャから拾った色の色空間をsRGBからLinearに変換する
#TextureColor = ColorSpaceConversion_sRGB_to_Linear(TextureColor)
# テクスチャから拾った色を頂点カラーに設定
VertexColors[loop_index].color = TextureColor
実行方法
-
例としてデフォルトキューブの各面を10分割し、適当な画像テクスチャを割り当てたモデルを用意します (下図のテクスチャはUV Checker Map Makerというサイトで生成しました)
-
「ウィンドウ」タブの「システムコンソール切り替え」をクリックし、スクリプトの実行結果やエラーが見えるようにする
-
「Sclipting」タブの「+新規」ボタンをクリックして以下のPythonスクリプトを貼り付ける
-
Cubeオブジェクトを選択した状態で「▶」ボタンをクリックし、スクリプトを実行する
実行結果
下図のようになれば成功です。
各頂点ごとにしか色を設定できないので、どうしても柄がぼやけてしまいます
色をはっきり分けたい場合は、色を分けたい境目に頂点を増やし、UV展開をし直す必要があります
注意点
Unity(VRChat)で、Blenderで作成したメッシュを使用する場合、頂点カラーにはsRGB色空間ではなくLinear色空間の色をベイクしておく必要があります (通常のテクスチャの色空間はsRGBです)。Pythonスクリプトの以下の行の先頭の#マークを消して、テクスチャから拾った色の色空間をsRGBからLinearに変換するようにしてください (Blender上での見た目は暗くなってしまいますが、気にしなくて大丈夫です)
変更前:
#TextureColor = ColorSpaceConversion_sRGB_to_Linear(TextureColor)
変更後:
TextureColor = ColorSpaceConversion_sRGB_to_Linear(TextureColor)
テクスチャの色をsRGB色空間のまま頂点カラーにベイクしたメッシュをUnityに持っていってしまうと、下図の左のモデルのように本来の色よりも明るく表示されてしまいます
色空間について解説されている記事
関連記事
Discussion