Help us understand the problem. What is going on with this article?

Blender から GLTF を出し、 Three.js で使うまでの覚書 (モデルメッシュ編)

Blenderは、GLTF(2.0)を出せます。

Three.jsは、GTLF(2.0)を、読めます。

以上。めでたしめでたし。

色々な情報が、ここで止まっています。
んで?その読み込んだブツは「使いものになるのかい?」

「3Dモデルを表示して終わり」なら上記でいいでしょうが、いわばインタラクティブなコンテンツというか、モデルを入力や状況によってああだこうだ動かしたい場合、上記情報を鵜呑みに「出来るんだぁ!」なんて思い込むと、痛い目を見ます。っていうか見ました。

Three.jsで「使うまで」ということで、Blednerでこうしたものは、Three.jsでこう行って、ここの設定に入る。だからこうすれば使える。逆に、こう入ってくるからそのままでは使えない。けど、こうすれば使える、というものを、自分なりにまとめました。

*この文では、「Blenderでのモデルの作り方」とか「Three.jsの使い方」とかは全くやりません。いじょ。

最初に結論

Blender側

  • 2.7

    • 「属性」欄は全部チェック入れろ。入って困るような数値はほぼない。
    • 日本語版使ってる場合は、「アーマチュア」とか「平面」とかの全角文字も、そのまま出力される。怖いようなら英語版にしろ。まぁ大抵そのままで問題はない(少なくともThree.jsでは)
    • モディファイヤ使ってるようなら全部外した(メッシュに適用させた&ボーンなどの名前も確定させた)出力用の別ファイルを作って、そっちでやったほうが幸せ。
    • モデル(メッシュ)のみ出力する場合は、Export Selectino only は使っても良し。ただ、↓のことでチェック外し忘れとかで無駄なストレスを感じないために、外しておこう。
    • というか出力用ファイルは、出力のたびに作ったほうがいい。JS側で苦労するより出力を直したほうが楽な場合が多い。
    • アニメーション(アクション)は、1モデルにつき1つしか出力されない。1モデルに複数アニメをセットする方法はあるから諦めるな。
    • アニメーションを出す場合、GLTF出力設定の Export Selectino only は、忘れろ。チェック入ってたら今すぐ外せ。ロクなことにならん。
    • 非表示のファイルがそのまま出力されて容量が増える?当然だ。だから出力専用ファイルを作って、そっちで無駄なメッシュ&オブジェクトは消そう。
  • 2.8

    • フォーマットはもちろん「glb」が幸せになれると思う。
    • 「ジオメトリ」欄は、「モデファイアを適用」以外は全部にチェック入れておいた方がよい。「適用」が入っていると、なんか結果が違う&不安定なので、↓の項のようにモデファイアを適用させよう。
    • 「トランスフォーム」の「+Yが上」のチェックを忘れないように(デフォで入っているが)。
    • 日本語版使ってる場合は、「アーマチュア」とか「平面」とかの全角文字も、そのまま出力される。怖いようなら英語版にしろ。まぁ大抵そのままで問題はない(少なくともThree.jsでは)
    • モディファイヤ使ってるようなら全部外した(メッシュに適用させた&ボーンなどの名前も確定させた)出力用の別ファイルを作って、そっちでやったほうが幸せ。
    • モデル(メッシュ)のみ出力する場合は、内容選択したオブジェクト は使っても良し。ただ、↓のことでチェック外し忘れとかで無駄なストレスを感じないために、外しておこう。
    • というか出力用ファイルは、出力のたびに作ったほうがいい。JS側で苦労するより出力を直したほうが楽な場合が多い。
      • 特にミラーモディファイアとか細分化とかな!!別ファイルに退避→モディファイア適用という手順で行おう。間違っても元ファイルにモデファイアを適用しないように。泣く羽目になる。
    • アニメーション(アクション)は、1モデルにつき1つしか出力されない(?)。1モデルに複数アニメをセットする方法はあるから諦めるな。
    • アニメーションを出す場合、内容 選択したオブジェクト は、忘れろ。チェック入ってたら今すぐ外せ。ロクなことにならん。
    • 非表示のファイルがそのまま出力されて容量が増える?当然だ。だから出力専用ファイルを作って、そっちで無駄なメッシュ&オブジェクトは消そう。

JS(three.js)側

  • 来るのは「シーン」であることを忘れるな。モデルやアニメだけ使いたい場合は、ちょっと一工夫必要になる。
  • アニメーションを組み合わせて使うような場合や、1モデルに複数アニメーションをさせたい場合は、がんばればなんとかなるから、頑張れ。
  • Blender側でマテリアルを分けたものは、Three.js側では「メッシュが分かれる」ことに留意すべし。アニメーションをさせたい場合は特に注意。

以下駄文の長文。

やりたかったこと

:複数モデルを用意。このモデルは、モデルデータのみ。アニメーションは含まない。
:アニメーションだけのファイルを読み込み、プログラム側で読み込んだモデルに対してアニメーションをセットする。
:別途、モデルには頂点モーフも用意した(表情とかね)。
:それをJSでThree.js使ってブラウザで使いたい。

Blender側での構成

まず当然ですが、BlenderのGLTFエクスポーターが必要になります。頑張ってセットしましょう。
え!?Bleder2.8だと、最初からGLTFのエクスポート機能があるだって!?なんてステキな。みんな乗り換えようぜ。

ということで、「ファイル」→「エクスポート」→「gltf 2.0 」を選択することによって、GLTFへのエクスポートメニューが出ます。

  • 素体モデルの編集用ファイル
    • これには、モデル(頂点&マテリアル)のほか、ボーンデータ(もちろんウェイト)も入っている。
  • 素体を動かすアニメーションのファイル
    • 素体モデルをリンクとして読み込み、アニメーションだけさせたファイル。
    • アクションとして、1ファイル内に複数アクションを設定した。
  • 顔用モデル
    • これには、「頂点モーフ(シェイプ)」をセットした。

マテリアルについて

  • 2.7の場合
    • ひとまず「GLTF出力用の、PBRマテリアルをやれば間違いはない」です。が、PBRマテリアルについては未検証(プログラム側で)なので、あんまり鵜呑みにしないでください。
  • 2.8の場合
    • 自分の場合は、「プリンシプルBSDF」を選択し、テクスチャ指定以外はノードは増やさないことで、うまいこと行っている気がします。

以下、順番がいろいろ前後しますが、用途関係なしに汎用的に使えそうな情報から。

モデル情報の出され方

モデルの出力には、Blenderの出力で「Export GLTF」を選択します。GLTFとGLBがありますが、GLBで出すほうがいいと思います(軽いしファイルは1つにまとまってるし)
なお、下記は全て、Blenderでの設定を「こうした」場合です。

2.7
export_set.png

2.8は、こう
image.png

three.js に読ませる前に

出力した後、Three.jsで読ませることができなかった場合、「そもそも出力に失敗しているのでは?」ということがあります。
この場合、Windowsを使っている人ならば、作成したGLTFファイルを右クリック→「プログラムから開く」→「3D Builder」で開くと、Three.jsで読めるようなモデルの場合は、表示することができます。逆にコレで開けない場合の多くは、Three.jsでも開けないので、その場合は出力を見直しましょう。
尚、この3D Builder以外のツールで開けなくても、3D Builder で開ければ大抵大丈夫です。
image.png

その1・最も単純なオブジェクト構成の場合(オブジェクト1つ、マテリアル1つ、ボーン&アーマチュアなし)

 GLTFをThree.jsで、GLTF Loaderを使って読んだ場合、まずは「Scene」の形式で入って来ます。そのため、モデルデータだけ欲しい場合は、こうなります。


const loader = new THREE.GLTFLoader();
loader.load('gltfObject.glb', (data) => {

  const gltf = data;
  const object = gltf.scene;
  scene.add(object);
});

この場合ですが、上記の「 object 」には、 THREE.mesh の形式で入ってきます。そのため、scene にそのまま add すれば、なんの問題もなく表示してくれます。
ここまではいいですね。サンプル通りです。

その2・マテリアルを複数セットしたモデルを出力する場合

この場合は、上記のソースコードで読んだ場合、構成がこうなります

const object は、
THREE.Group
    - Mesh (with マテリアル1)
    - Mesh (with マテリアル2)

マテリアル毎にメッシュが分かれます。 THREE.Group 形式のまま THREE.Scene に突っ込んでも表示されるので、何もしない場合はこれでも問題ないように思えます。
ただ、マテリアルに対して何かしたい(シェーダー自力で書きたいマン)となった場合、「どれが自分がいじりたいマテリアル(が、入っているMeshか)」を、判別しなくてはなりません。
その場合は、親切なことに Mesh.material.name に、Blender側でマテリアルにセットした名前が入っているので、それを探すことになります。
こんなメソッドを用意するといいでしょう。

/// 指定したマテリアル名がセットされている[ mesh ] を返します
function getMatbyName(_o, _name){
    let refO = null;

    if(_o.material && _o.material.name.indexOf(_name) > -1 ){
        return _o;
    }

    if(_o.materials){
        for(let i =0; i < _o.materials.length; i++){
            refO = getMatbyName(_o.materials[i], _name );
            if(refO) {return refO;}
        }
    }

    for(let i =0; i < _o.children.length; i++){
        refO = getMatbyName(_o.children[i], _name );
        if(refO) {return refO; }
    }

    return refO; 
}

const targetMesh = getMatbyName(object, "mat1");

その3・頂点モーフ(シェイプ)を使う場合

頂点モーフ(シェイプ・・・以下モーフだけ)は、思いのほかすんなり出力してくれます・・いや嘘です。すんなりじゃないかも。
モーフは、 THree.Mesh の中に、morphTargetDictionary および morphTargetInfluences の配列の中に入ってきて、どちらかでもいいので、その配列の値を 0 ~ 1.0 で変化させれば、モーフィングしてくれます。
ただ、この Mesh の中、というのが厄介であり、上記のとおり、 Mesh がどこに入っているかは、マテリアルが複数かどうかで分岐&分かれているメッシュ全部に対してモーフ値をセットしないと狙った変化はしてくれないので、取り扱いがやや面倒です。
特に、名前で管理をしたいところですが、名前は morphTargetDictionary のキーとなっており、配列アクセスまでが一工夫です。

// Blender側で、 smile と名前を付けたシェイプキーを適用したい場合
// まず、[Group] から [mesh]を探す必要があります。
function getMeshByName(_o, _name ){
    let refO = null;

    if(_o.type.toLowerCase().indexOf("mesh") > -1 && _o.name.indexOf(_name) > -1 ){
        return _o;
    }

    for(let i =0; i < _o.children.length; i++){
        refO = getMeshByName(_o.children[i], _name );
        if(refO) {break;}
    }
    return refO; 
}

const faceObj = getMeshByName(object, "face");

// モーフキーの名前で列挙
const mopthKeys = Object.keys(faceObj.morphTargetDictionary);
let smileKeyIndex = 0;

for(let i =0; i < mopthKeys.length;i++){
  if(mopthKeys[i] == "smile"){
    smileKeyIndex = i;
    break;
  }
}

// モーフを適用
for(let i =0; i < object.childlen.length;i++){
  if(object.childlen[i].morphTargetInfluences && object.childlen[i].morphTargetInfluences.length > 0){
     object.childlen[i].morphTargetInfluences[smileKeyIndex] = 1.0;
  }
}

誰だよ「すんなり」とか言った奴。

思いのほか長くなった&情報量が多くなったので、アニメーションは 後編の アニメーション編(仮) また来世。

adrs2002
読み方は「じゃいあん」です。巻き舌気味でお願いします。日本の秘境「トチギー」の僻地で、 Three.jsとWebGL使って同人ゲーム作ろうとしてます。  本業はゲームとは関係ないC#いじってます。
http://www001.upp.so-net.ne.jp/adrs2002/teststage.html
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした