3DモデルをwebでカスタマイズするためのTIPS
December 29, 2019
振り返ってみたら2019年に一度もブログを書いていませんでした。 何も書かずに終わるのもさみしいので、今年の後半にwebglを使って3Dモデルをいじるアプリを作っていた時に溜まった知見をまとめておきます。
やりたいことベースでの記述となるため、適宜必要な知識は公式サイトや参考にしたサイトにて補完してください。
やりたいこと
一言でいえば、服のカスタマイズアプリです。 シャツなり下着なりの3Dモデルが手元にある前提で、そのデザインをカスタマイズするアプリです。 これだけであれば、そもそも3Dでやる必要性は低いです。 開発開始時点ではマネキンなり実際の人間の3Dモデルに合わせることを考えていたので3Dで実現することにしました。
利用したライブラリ
three.js を利用しました。 javascript で3D処理を行うためのライブラリです。 3D処理に必要なライブラリはこれだけです。
参考にしたサイト
three.jsの公式サイトです。
https://threejsfundamentals.org/
three.jsの解説サイトです。
最新APIの書き方ではない可能性がありますが、3D処理の概念的なものを学ぶために読みました。 Basics -> Fundamentals で何となく雰囲気を理解出来ます。 3Dモデルファイルをwebアプリ上にインポートする方法は Solutions -> Load an .Obj file が参考になります。 服の柄や色をカスタマイズするためには3Dモデルのテクスチャをいじる必要があるので、Fundamentals > Materials, Textures 辺りも読みました。
https://ics.media/tutorial-three/
日本語で書かれた解説サイトです。ググってるとよくたどり着きます。
テクスチャをカスタマイズする方法
3Dモデルをロードしたら、まずはコンソールに読みこんだ結果を出力してみましょう。
const objLoader = new OBJLoader();
objLoader.load('objのパス', (root) => {
console.log(root);
});
objファイルによりけりですが、children
という属性に Mesh の集合があるのではないでしょうか。
Mesh は形状データの Geometry と見た目のデータの Material をまとめたものです。
この集合の中をさらに見て、カスタム対象の Mesh を特定します。
3Dモデルの作り手に依頼ができるのであれば、 Mesh にわかりやすい名前をつけておいてもらいましょう。
見た目をカスタマイズするためには、Material を変更します。 今回やりたかったのはMaterialの色、またはテクスチャ画像の変更です。
色の場合は、 setValues
メソッドで色を指定し、テクスチャ画像の場合は map
属性を更新します。
以下は、一部は黒塗り、一部は画像を展開する場合の実装例です。
wrapS
と wrapT
は画像の繰り返し方です。とにかく一面に広げたいときは THREE.RepeatWrapping
でOKです。
repeat.set(2,2)
というのは、繰り返し回数です。ここでは2回繰り返すようにしています。
root.children.forEach(function(mesh, index) {
const material = mesh.material;
if(mesh.name === '識別するための名前1') {
const loader = new THREE.TextureLoader();
const texture = loader.load('画像パス',
() => { /* loading終了時の処理 */ }
);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);
material.map = texture;
} else {
// 単純に色を設定するときは属性に色を指定するだけでOK
material.setValues({color: 'black'});
}
});
テクスチャをクリック操作でカスタマイズする方法
Raycaster
を使います。
Raycaster
を使うと、3D空間上に設置されたカメラから直線で光を発した時に交差するオブジェクトを取得できます。
この機能を使って、画面クリックまたはタップされたオブジェクトを特定します。
メラからクリックされた位置へと飛ばした光線と交差するオブジェクトを intersectObjects
メソッドを使って取得します。
intersectObjects
には取得対象になるオブジェクトの集合を渡します。
今回はobjファイルから読み込んだMeshの集合を渡します。
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
window.addEventHandler('click', (event) => {
event.preventDefault();
/* クリックされた位置の取得 */
const element = event.currentTarget;
const x = event.clientX - element.offsetLeft;
const y = event.clientY - element.offsetTop;
const w = element.offsetWidth;
const h = element.offsetHeight;
mouse.x = ( x / w ) * 2 - 1;
mouse.y = - ( y / h ) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(root.children);
if(intersects.length > 0) {
/* 複数ヒットするケースは無視する */
const mesh = intersects[0];
const loader = new THREE.TextureLoader();
const texture = loader.load('画像パス',
() => { /* loading終了時の処理 */ }
);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);
mesh.material.map = texture;
mesh.material.needsUpdate = true;
}
});
needsUpdate
に true
を設定するのを忘れずに。これを忘れると、画面上に反映されません。
色を変えるときにはまったこと
このやり方で色を変えるときに解決できない事象が発生しました。
既にテクスチャ画像が展開されている Material に対して setValues
で色を設定すると、元のテクスチャを残したまま色が反映されます。
また、その逆に色がのみが設定されている Material に対してテクスチャ画像を設定しても、同じように両方が反映された状態になります。
これを回避するために setValues
で色を null
にしても、色は消えませんでした。
私は色自体も画像ファイルとして用意して単なるテクスチャ画像の差し替えという処理にすることで最終的には回避しましたが、正しいソリューションなのかは自信ありません。