Contents

Blender の頂点カラー (Color Attribute) に関する奇妙(?)な挙動について

何も考えずに頂点カラー塗って何も考えずにエクスポートすると結果的にうまくいくというある意味最悪な現象が起きてたんだ。 だからこの記事は VIPF のやつの内容の訂正も兼ねてたりする……

経緯

Blender で頂点カラーを設定してエクスポートした FBX ファイルを Unity で使う際に 実際の値 がどのように変換されたり渡されたりしているのかが気になったので一通り調べました。 問題が起きたときにどうすればいいかはともかく、どういう挙動をしてるのか日本語で言及されている記事などがほとんど見当たらなかったので……

結論

先に結論だけ書いておきます。

Blender 側の挙動

Blender (3.2 以降)では Color Attrubite ひいては頂点カラーの内部データは Linear 空間で保持されています。

まずオブジェクトに紐付く一般的なデータとして Attribute というのがあり、その一部として Color Attribute があります。 この Color Attribute というのは頂点カラーが一般化されているようなもので、もちろん複数定義することができます。

Color Attribute の設定としては以下の 2 つがあります。

  • Domain
    • Vertex (頂点単位)
    • Face Corner (面の頂点単位)
  • Data Type
    • Color (ARGB Float 相当)
    • Byte Color (RGBA32 相当。エクスポートすると精度が落ちる)

/posts/blender-weird-vertex-color/add-color-attribute.png
新規追加時の設定

Color Attribute のうち、上記の項目が Face Corner と Byte Color に設定されているものは特別扱いされます。 具体的にはスクリプトで color_attributes[x] からだけではなく vertex_colors[x] としても参照できるようになります。 おそらく互換性のためにこのような挙動になっているものと思われます。

Color Attribute が一つもない状態で Vertex Paint モードに入ると Color Attribute が自動で作成されますが、それの設定も Face Corner と Byte Color になっています。

Vertex Paint モードでカラーピッカーから選んだ値は sRGB 空間のものとして解釈されます。 つまり、実際に内部データに格納される値には sRGB to Linear 変換が適用されます。Unity のマテリアルの Color プロパティーみたいなもんですね。 さらに、vertex_colors[x] で読み書きされる値は sRGB 空間になるようです。 color_attributes[x] は Linear 空間なのにね。

plane = D.objects["Plane"].data
vertex_color = plane.vertex_colors[0].data
color_attribute = plane.color_attributes[0].data
color_attribute[0].color = (0.5, 0.0, 0.0, 1.0)

# Data Type: Byte Color なので精度落ちしている
tuple(color_attributes[0].color)
(0.5028864741325378, 0.0, 0.0, 1.0)

# Linear to sRGB 変換がかかった値が得られている
tuple(vertex_color[0].color)
(0.7372549176216125, 0.0, 0.0, 1.0)

FBX ファイルとしてエクスポートする際は、エクスポート設定の Geometry > Vertex Color の選択肢が文字通りに作用します。 内部的には Linear 空間で保持されているので、sRGB を選ぶ(デフォルト値)と Linear to sRGB 変換がかかった値が、 Linear を選ぶとそのままの値が FBX ファイルに出力されます。

Unity 側の挙動

Unity は頂点カラーの値に関与しないようです。 FBX ファイルをインポートする際に頂点カラーの値が sRGB ⇔ Linear 変換によって変化することはありません。 また、 Mesh アセットになった後シェーダーに COLORn セマンティクスとして渡される際にも同様の変換はかからないようです。

Linear 空間で厳密な頂点カラーを操作するにはどうすればよいか

あなたが Python を書ける、特に Blender のスクリプティングインターフェースに精通1しているならば Mesh.color_attributes から操作すればよいです。 エクスポート時に Vertex Color: Linear の設定をするのを忘れないようにしましょう。

外部のアドオンで Linear 空間で操作できそうなものは僕が探した限りではなさそうでした。まあ元々 だったので sRGB 空間で操作する方が直感的なのはそれはそうか。

調査ログ

当時の行動を雑に時系列順に書いておきます。

  1. 何も考えずに編集してエクスポートしたやつはそれっぽく動いてるけど、よく考えたら Linear で出力しなきゃダメじゃない?
    • 実際にそうして Unity でインポートしたら sRGB to Linear 変換がかかった値で出てきた
  2. GUI から編集してるからよくないのかもしれない、スクリプティングインターフェースから操作してみよう
    • 一般的に知られてる vertex_colors[0] 経由で操作しても GUI で編集したのと同じようになる……
  3. Color Attribute とかいうので適当に新しい頂点カラーデータ作ったら color_attributes[x] からしかアクセスできないっぽいぞ?
    • Face Corner と Data Type の設定を色々いじってたら Face Corner と Byte Color で設定したやつだけ vertex_colors[x] から見えるらしいのまでは判明した
    • vertex_colors[x] から見えるやつでも color_attributes[x] から操作した場合は Linear 空間で取得・設定できるじゃん!じゃあ内部的には全部 Linear なのか
  4. 実際に FBX ファイルに出力されてる値がエクスポート時の sRGB/Linear 指定でどうなるのかもチェックしないといけないじゃん
    • FBX ファイルにはそういえば ASCII 形式があったからそれに変換してそれっぽいのチェックしよ
    • Linear 指定でエクスポートしたら Linear 空間扱いの内部データがそのまま出てるのがわかった
  5. 最後に Unity がインポート時に何かしてないかチェックしよう
    • 記録されてる頂点カラーの値が Linear 空間であることが確認できてる FBX ファイルをインポートしてシェーダーからその値を取ってみる
    • 結果的にそのまま渡ってきたので Unity は何もしてこないっぽい

VIPF のときはなぜそれっぽく動いていたのか?

これら全てを総合すると、以下のようになりますね。

  1. 何も知らずに Vertex Paint モードのカラーピッカーで Linear なつもりの値を入力する
  2. Blender は Color Attribute に 1. の値を sRGB to Linear 変換をかけて格納する
  3. エクスポート時に何も知らずに Vertex Color: sRGB で実行する
  4. Blender は Color Attribute を Linear の値だと思い込んでいるので Linear to sRGB 変換をかけて出力する
  5. 結果的に FBX ファイルには 1. で Linear のつもりで設定した値がそのままの値で出てくる
  6. Unity は頂点カラーの値に何もしないので結果的に 1. の値がそのまま使えることになる

知るかよ!


  1. 僕がこの単語書くと別の意味に取られやすくて地味に困る ↩︎