ホーム < ゲームつくろー! < FBX習得編

その7 テクスチャはマテリアルと共に


 3Dモデルにテクスチャはかかせません。そのテクスチャには色々な種類があり、それは「マテリアル」とリンクしています。この章ではマテリアルとテクスチャをFBXから取得する方法について見ていく事にします。



@ FBXの主なマテリアル情報

 まずはマテリアルの情報から見ていきます。FBXにおける「マテリアル」というのは表面の質を表す属性を指します。表面の質とは身の回りの物を見て頂くとわかるように、色味や照り輝き具合などです。これらはすべて「光の反射」が成す技です。FBXには例えば次のようなマテリアル属性があります:

マテリアルの種類 マテリアルの属性 データ型 説明
Phong Lambert アンビエント(環境光) カラー 環境光ライトに対する反射色
ディフューズ(拡散反射光) カラー ある点に当たった光が四方八方に反射する強度。強いほど良く反射。
エミッシブ(放射光) カラー ライトに関係なく自身が放射する光。真っ暗な世界でも光れます。
バンプ ベクトル 表面の凸凹の強度。テクスチャとワンセットが普通。
透過度 スカラー 光の透過具合。値が大きいほど不透明。
スペキュラ(鏡面反射光) カラー ある点に当たった光が特定の方向に反射する強度。鏡面のような効果。
光沢(Shininess) スカラー 光沢感を表す
反射 スカラー 環境マップの反射度。値が大きいほど環境マップを強く反射するので鏡のようになる。
*他にも色々あります

 FBXはLambert(ランバート)とPhong(フォン)という2種類のマテリアルをサポートしています。Lambertはスペキュラや光沢、反射を考慮しないマットな質感を持った基本的なマテリアルです。Phongは拡散、鏡面反射性、光沢の3つの特徴を用いてモデルの表面の質を決める方法で、てかてかしたモデルを表現できます。LambertもPhongも上記の情報で表現でき、PhongはLambertの情報をすべて包含しています。

 上にあるマテリアルの属性について以下に簡単にまとめました。

○ Ambient(アンビエント)、Ambient Factor

 アンビエント(環境光)はモデル対して全方向から当たる特殊な光です。光が当たってる部分は均等に明るくなります。Ambient Factorというのはその強度(Weight)の事で、普通0〜1の間を取ります。あるピクセルに対してアンビエントカラーは次のように作用します:

 Color += Global_Ambient + Ambient * Ambient_Factor * Light_Ambient

Global_Ambientというのは全宇宙を照らす明かりの事で、目に見えるすべてのものを照らします。Light_Ambientはライトが持つアンビエント色で、アンビエントカラーの色味に掛け算されます。光が当たっているか否かで色味が二極化する特殊なライトなんです。


○ Diffuse(ディフューズ)、Diffuse Factor

 モデルが本来持っている色味を表すのがディフューズ(拡散反射光)です。この色は入力されてきた光の角度によってその強度が変わる性質を持っています。ある点に対して光がど真正面(0度)で入ってきた場合は最大の跳ね返りを見せるので「ディフューズ色×入力された光の色」でその点が表現されます。しかし角度が変わると跳ね返る光の量が減ってしまうため暗く見えるようになります。一般にそれを加味した計算式は次のようになります:

 Color += ( Diffuse * Input_Color ) * dot( Normal, -Input_Vector) * Diffuse_Factor

dotとは「内積」です。上では法線と入力光の逆方向の内積を取っています。DiffuseとInput_Colorの掛け算で色味が決まり、内積の部分でその強度が決まっているわけです。Diffse Factorは強度を微調整します。


○ Specular(スペキュラ)、Specular Factor

 スペキュラ(鏡面反射光)はモデルの艶を表現する時に使用します。スペキュラが無いとプラスチックのようなのっぺりとした面になりますが、スペキュラが高いと磨き上げた金属のような表面になります。スペキュラの計算式はかなり面倒なのですが、こんな感じです:

 HalfWay_Vector = normalize( normalize( Vertex_Pos - Camera_Pos ) + Light_Pos - Vertex_Pos )
  Color += Specular * Light_Specular * { dot( HalfWay_Vector, Normal )^Power } * Specular_Factor

ハーフウェイベクトルというのはカメラと光の方向を決めるベクトルのようなもので、下段の式でそれと法線の内積が取られています。さらにそれをPowerでべき乗する事で角度が出てきた場合の減衰量を大きくします。このPowerが大きい程スペキュラがきつくなるので、モデルは鏡面のようになっていきます。


○ Emissive(エミッシブ)、Emissive Factor

 エミッシブ(放射光)はモデル自身が放つ光を表します。太陽や電球などは自分で光っているため真っ暗闇でも明るく見えます。この光は問答無用の光です。世界が真っ暗でもモデルは光るので、ある点の色は単にエミッシブの色そのものとなります:

 Color += Emmisive


○Shininess(シャイネス)

 シャイネス(光沢)はスペキュラと似ています。色々と調べたのですがちょっと不明です(^-^;;。計算式を含め知っている方はご教授下さい。

(2012. 5. 6追記)
 us51966さんより「Powerの値ですよ」と教えて頂きました(KFbxSurfaceMaterialのKFbxSurfacePhongのKFbxPropertyDouble1 Shininess)。ありがとうございます〜。


○ NormalMap(法線マップ)

 法線マップとはモデルの表面に貼り付けるテクスチャの1つで、各テクセルにモデル表面の法線の向きが刻印されています。これによって1テクセル単位で反射する光の強さを制御する事ができるようになります。


○ BumpMap(バンプマップ)

 バンプマップは法線マップと同様にモデルの表面の凹凸をテクセル単位で表すテクスチャです。法線マップがテクセル一つ一つに向きを定義するのに対し、バンプマップは一般にグレースケールでモデルの凹凸強度を表します。これはいわゆるハイトマップ(高さマップ)と呼ばれるものです。つまり、バンプマップには向きの概念がありません。バンプマップの向きを決めるのは法線マップです。この違い、微妙ですが重要です。


○ Transparent(透過度)、Transparent Factor

 トランスペアレント(透過度)はその名の通りモデルの透明度を決めます。アルファ成分にのみ作用する値です:

 Alpha *= Transparent * Transparent Factor


○ Reflection(反射)、Reflection Factor

 リフレクション(反射)は鏡のように物体を映し出す効果の事です。これは一般には「環境マップ」と呼ばれるモデルの周りの風景を投影したテクスチャに対して効果を発揮する値です。リフレクションが強ければ周りの風景をより強力に反射し、低ければ反射せずに吸収してしまいます。計算式は簡単に言えば入力する光の色を反射するだけです:

 Color += Input_Environment_Color * Reflection * Reflection_Factor


 このように、様々な表面の質の情報を用いてリアリティのあるモデルを表現するわけです。ただ、これはあくまでも表面の質でポリゴングループ単位で適用される値です。これにさらにテクスチャが加わるとテクセル単位で上記の属性を設定でき、表現の幅が一気に増します。



A テクスチャ情報

 テクスチャと言うと3Dモデルの上に貼り付けられる色味である「ディフューズテクスチャ」が一番使われます。しかし、昨今の3Dモデルはそれだけでなくスペキュラテクスチャやバンプマップ(バンプテクスチャ)など上記の属性それぞれを表すテクスチャを何枚も貼り付けるようになりました。これにより、3Dモデルのリアリティはとてつもなく向上する事になったんです。

 Xファイルを卒業し、FBXを使いたい理由の一つが、この複数のテクスチャ情報をサポートしてくれている点にあります。デフォルトのXファイルは1つのマテリアルに1つのテクスチャ(ディフューズテクスチャ)しかサポートしていません。これが昨今のゲームではもう表現不足になっているわけです。FBXでは上記に列挙した属性のすべてに対してテクスチャをアタッチすることが可能です。

 FBXファイルに格納されているテクスチャ情報を得るためには、まずマテリアルの情報が必要です。そこで、次の節でマテリアルの情報を得る所から始め、続けてテクスチャ情報を取得します。



B マテリアル情報を取得する

 モデルに適用されているマテリアルの情報を得るには、まずKFbxMesh::GetNodeメソッドでメッシュのルートノードを得てから、KFbxNode::GetMaterialCountメソッドでマテリアルの数を調べます。続いてKFbxNode::GetMaterialメソッドでマテリアル情報であるKFbxSurfaceMaterialオブジェクトを取得します。後はここから各種情報を取得できます。とりあえず、ソースで見てみましょう:

マテリアルオブジェクトを取得する
KFbxNode* node = mesh->GetNode();
if ( node == 0 ) {
   return;
}

// マテリアルの数
materialNum_ = node->GetMaterialCount();
if ( materialNum_ == 0 ) {
   return;
}

// マテリアル情報を取得
for( int i = 0; i < materialNum_; ++i ) {
   KFbxSurfaceMaterial* material = node->GetMaterial( i );
   if ( material != 0 ) {
      // マテリアル解析
      ...
   }
}

 ここからわかるように、マテリアル情報は複数取得される事があります。これは例えば1つのモデルのあるポリゴングループにマテリアル0番を、別のポリゴングループにマテリアル1番を設定する場合があるためです。

 さて、取得したDFbxSurfaceMaterialオブジェクトからマテリアルの情報を得ます。これは結構クセがあります。@の表にあるように、FBXのマテリアルには「Lambert」と「Phong」があります。FBX SDKにはそれらに対応するKFbxSurfaceLambertクラスとKFbxSurfacePhongクラスが用意されています。実はこれらのクラスは双方ともKFbxSurfaceMaterialクラスを親に持っています。さらにKFbxSurfacePhongクラスの親がKFbxSurfaceLambertクラスになっています:


 ルートノードから取得したKFbxSurfaceMaterialオブジェクトは、実はその子であるLambertかPhongのいずれかであることがわかっています。そこで、まずどちらであるかを判定し、子クラスにダウンキャストします。ダウンキャストする事でアクセスできるメソッドが増えます。これにより、各マテリアル固有の情報をゲットできるわけです。ソースを見てみましょう:

マテリアル情報を取得する
// materialはKFbxSurfaceMaterialオブジェクト

// LambertかPhongか
if ( material->GetClassId().Is( KFbxSurfaceLambert::ClassId ) ) {

   // Lambertにダウンキャスト
   KFbxSurfaceLambert* lambert = (KFbxSurfaceLambert*)material;
   setLambertInfo( lambert );

} else if ( material->GetClassId().Is( KFbxSurfacePhong::ClassId ) ) {

   // Phongにダウンキャスト
   KFbxSurfacePhong* phong = (KFbxSurfacePhong*)material;
}


 ランバートマテリアルが持つ情報は次のように取得します:

ランバートマテリアルの情報を取得する
// アンビエント
ambient_.r = (float)lambert->GetAmbientColor().Get()[ 0 ];
ambient_.g = (float)lambert->GetAmbientColor().Get()[ 1 ];
ambient_.b = (float)lambert->GetAmbientColor().Get()[ 2 ];

// ディフューズ
diffuse_.r = (float)lambert->GetDiffuseColor().Get()[ 0 ];
diffuse_.g = (float)lambert->GetDiffuseColor().Get()[ 1 ];
diffuse_.b = (float)lambert->GetDiffuseColor().Get()[ 2 ];

// エミッシブ
emissive_.r = (float)lambert->GetEmissiveColor().Get()[ 0 ];
emissive_.g = (float)lambert->GetEmissiveColor().Get()[ 1 ];
emissive_.b = (float)lambert->GetEmissiveColor().Get()[ 2 ];

// バンプ
bump_.x = (float)lambert->GetBump().Get()[ 0 ];
bump_.y = (float)lambert->GetBump().Get()[ 1 ];
bump_.z = (float)lambert->GetBump().Get()[ 2 ];

// 透過度
transparency_ = (float)lambert->GetTransparencyFactor().Get();


 整然としていますのでアンビエントを例に説明します。KFbxSurfaceLambert::GetAmbientColorメソッドは要素数が3のdouble型の配列を返してくれます(実際はFBX SDKが用意している配列テンプレートです)。配列は配列参照演算子([ ] )で要素にアクセスできるため、上のような書き方になっています。0番にR、1番にGそして2番にBがあります。ソースではそれをfloat型に変換して格納しています。ちなみに、これらの色味は0.0〜1.0の浮動小数点表現です(この範囲以外もあります)。

 同様にしてディフューズやエミッシブは専用のメソッドからその色味を取得できます。透過度のように値が1つ(スカラー)の物については配列参照演算子を使わずに値を取得します。


 PhongマテリアルはKFbxSurfaceLambertクラスの子クラスなので、メソッドをすべて継承しています。さらにPhongマテリアル独自の属性の取得メソッドが追加されています:

Phongマテリアルの情報を取得する
setLambertInfo( phong ); // ランバート情報を取得

// スペキュラ
specular_.r = (float)phong->GetSpecularColor().Get()[ 0 ];
specular_.g = (float)phong->GetSpecularColor().Get()[ 1 ];
specular_.b = (float)phong->GetSpecularColor().Get()[ 2 ];

// 光沢
shininess_ = (float)phong->GetShininess().Get();

// 反射
reflectivity_ = (float)phong->GetReflectionFactor().Get();


 このようにして、FBXファイルに保持されているマテリアル情報にアクセスできます。



C テクスチャ情報を取得する

 さて、マテリアルの属性にはテクスチャがアタッチされている事があります。ディフューズ属性に対するテクスチャが一番使用頻度が高く「ディフューズマップ」などと呼ばれます。もちろん、すべてのマテリアル属性にテクスチャがアタッチされているわけではありませんが、複数毎のテクスチャが付いている事はざらです。FBXファイルからテクスチャ情報、すなわちテクスチャへのファイルパスを取得するには、ちょっと面倒な手順を踏みます。ソースで説明します:

テクスチャ情報を取得する
KFbxSurfaceMaterial* material;  // 先のソースで取得するものです

// ディフューズプロパティを検索
KFbxProperty property = material->FindProperty( KFbxSurfaceMaterial::sDiffuse );

// プロパティが持っているレイヤードテクスチャの枚数をチェック
int layerNum = property.GetSrcObjectCount( KFbxLayeredTexture::ClassId );

// レイヤードテクスチャが無ければ通常テクスチャ
if ( layerNum == 0 ) {
   // 通常テクスチャの枚数をチェック
   int numGeneralTexture = property.GetSrcObjectCount( KFbxTexture::ClassId );

   // 各テクスチャについてテクスチャ情報をゲット
   for ( int i = 0; i < numGeneralTexture; ++i ) {
      // i番目のテクスチャオブジェクト取得
      KFbxTexture* texture = KFbxCast<KFbxTexture>( property.GetSrcObject( KFbxTexture::ClassId, i ) );

      // テクスチャファイルパスを取得(フルパス)
      const char* fileName = texture->GetFileName();

      size_t size = strlen( fileName );
      memcpy( outTexName, fileName, size );
      outTexName[ size ] = '\0';

      break; // とりあえず今は1枚だけサポート
   }
}

 何だかな〜という感じなのですが、どうもこうしないとテクスチャファイル名まで辿り着けないようです。まず、先に取得したKFbxSurfaceMaterialが持っているFindPropertyメソッドでディフューズについてのプロパティ(KFbxProperty)を得ます。プロパティというのは何らかの情報の塊です。取得したプロパティから「ディフューズのレイヤードテクスチャの枚数」をKFbxProperty::GetSrcObjectCount( KFbxLayerdTexture::ClassId )でチェックします。レイヤードテクスチャというのは1つのマテリアル属性に複数あてがったテクスチャの事で、これが1枚以上あるとレイヤードテクスチャが有効と判断されます。今回はレイヤードテクスチャは考えません。

 レイヤードテクスチャが0枚の時、マテリアルに1枚だけ適用される通常テクスチャが有効である可能性があります。先のプロパティから続いてKFbxTexture::ClassIdを使って通常テクスチャの枚数をチェックします。もしこれが0枚ならば、そのマテリアル属性にテクスチャが適用されていません。1枚以上の場合、上の赤文字で示しているようにKFbxTextureオブジェクトを取得できます(書式は上の通りで鉄板です)。KFbxTextureオブジェクトが取得できたら、後はKFbxTexture::GetFileNameメソッドでテクスチャへのフルパス情報を得ることができます。KFbxTextureクラスは他にもいくつかメソッドを持っていますので、必要な情報をそこから取得して活用します。

 と、このような感じで、テクスチャへのパスを取得するのに何とも面倒なプロセスを経なければなりません。


 上はディフューズテクスチャへのパスを取得する方法です。この属性は一番最初のKFbxSurfaceMaterial::sDiffuseというフラグが決めています。これは他に次のような物が用意されています:

・KFbxSurfaceMaterial::ShadingModel
・KFbxSurfaceMaterial::MultiLayer
・KFbxSurfaceMaterial::Emissive
・KFbxSurfaceMaterial::EmissiveFactor
・KFbxSurfaceMaterial::Ambient
・KFbxSurfaceMaterial::AmbientFactor
・KFbxSurfaceMaterial::Diffuse
・KFbxSurfaceMaterial::DiffuseFactor
・KFbxSurfaceMaterial::Specular
・KFbxSurfaceMaterial::SpecularFactor
・KFbxSurfaceMaterial::Shininess
・KFbxSurfaceMaterial::Bump
・KFbxSurfaceMaterial::NormalMap
・KFbxSurfaceMaterial::TransparentColor
・KFbxSurfaceMaterial::TransparencyFactor
・KFbxSurfaceMaterial::Reflection
・KFbxSurfaceMaterial::ReflectionFactor

意味は…マテリアルの属性そのものですね。



 これで、マテリアルの情報とそれに対応するテクスチャの情報を取得することができました。この章までで、頂点座標、頂点インデックス、法線、UV、マテリアルそしてテクスチャと、3Dモデルを描画するのに必要な情報が実は揃いました!次章からはモデルの位置を表す情報や、アニメーションの情報の取得に移りたいと思います。