Blender2.8でglTFを出力
半年前に「glTFをThree.jsで読み込み」でglTFをThree.jsで読み込みましたが、その頃は3DCGを始めたばかりでわからないことも多く、Three.jsで読み込んだとき色や質感が再現できませんでした。最近Blender2.8が正式リリースされ、標準でglTFが出力できるようになったので、「BlenderでiPhoneXSを制作(2)」で制作したiPhoneXSをglTFで出力して、Three.jsで読み込んでみました。
Blenderにも慣れてきて参考書もではじめてきたので、そろそろBlender2.8を使い始めようと思います。
Blender2.8からglTFを出力
● PBRマテリアルの設定
マテリアルとテクスチャを表示させるため、BlenderでPBRマテリアルを設定しました。PBR(物理ベースレンダリング)は現実の光学現象をシミュレートすることにより、リアルな質感を表現できるレンダリング方法です。
詳細は上記マニュアルに書いてありますが、プリンシプルBSDFを使用すると綺麗にglTFに出力することができます。
ただし、あくまでBlenderがglTFに変換してくれるということで、全ての設定に対応しているわけではありません(現時点ではベースカラー、メタリック、荒さ、ノーマルマップ、放射など)。テクスチャにも対応していますが、画像形式をPNGかJPGにする必要があります。
Blender2.79で制作したiPhoneXSをBlender2.8で開きます。
選択したオブジェクトのみglTFに出力することもできますが、今回は他のオブジェクトは削除して、iPhoneXSのみglTFに出力します。
また、マテリアルは全てプリンシプルBSDFで設定します。
● glTFを出力

Blender2.8で出力できるのはglTF2.0です。
トップバーの「ファイル > エクスポート > glTF2.0(.glb/.gltf)」をクリックします。

glTF2.0をエクスポートする画面が表示されます。
モディファイアーを適用をチェックして、右上のglTF2.0をエクスポートをクリックしてglTFを出力します。
出力されるのはシーンです。アニメーションも出力することができます。
Three.jsでglTFを読みこみ
● Three.jsで読み込む
出力したglTFは、THREE.GLTFLoaderで読み込むことができます。glTFで読み込んだオブジェクトの影のつけ方は、「glTFをThree.jsで読み込み」を参照してください。
//glTFの読み込み var loader = new THREE.GLTFLoader(); loader.load('./iphonexs.glb',function(data){ var gltf = data; var obj = gltf.scene; scene.add(obj); }); //読み込んだシーンが暗いので、明るくする renderer.gammaOutput = true;
● MeshStandardMaterial

Three.jsでPBR(物理ベースレンダリング)を使用したい場合は、MeshStandardMaterialを使用します。
MeshStandardMaterialでテクスチャの他にノーマルマップを設定し、荒さ(roughness)を調整することで、アスファルトのデコボコを表現することができます。
● script.js
必要なライブラリを読み込みます。
<script src="js/lib/preloadjs.min.js"></script> <script src="js/lib/TweenMax.min.js"></script> <script src="js/lib/three_vr/three.min.js"></script> <script src="js/lib/three_vr/OrbitControls.js"></script> <script src="js/lib/three_vr/GLTFLoader.js"></script> <script src="js/script.js"></script>
最近ChromeのAddEventListenerOptionsのpassiveがデフォルトでtrueになり、OrbitControlsを使用するとpreventDefaultが効かないため、スマホでタッチした時に意図せず画面がスクロールしてしまいます。そこで、passiveをfalseにする処理を追加しました。
document.addEventListener('touchmove', function(e) {e.preventDefault();}, {passive: false});
基本的には「glTFをThree.jsで読み込み」と同じですが、完成したscript.jsです。
(function () { window.addEventListener("load", function () { startLoading(); }); var scene,camera,renderer; var texture; //ローディング処理 function startLoading(){ TweenMax.to(".loader",0.1,{opacity:1}); var nameArray = ['iPhonexs','texture','normalmap']; var manifestArray = []; var path; for(var i = 0; i < nameArray.length; i++){ var name = nameArray[i]; if(i==0){ path = 'data/'+name+'.glb'; }else{ path = 'img/'+name+'.jpg'; } manifestArray.push({id:name,src:path}) } var loadQueue = new createjs.LoadQueue(); loadQueue.on('progress',function(e){ var progress = e.progress; }); var imageArray = []; textureArray = []; loadQueue.on('complete',function(){ for(var i = 0; i < nameArray.length; i++){ var tempImage = loadQueue.getResult(nameArray[i]); var tempTexture = new THREE.Texture(tempImage); tempTexture.needsUpdate = true; imageArray.push(tempImage); textureArray.push(tempTexture); } TweenMax.to("#loader_wrapper" , 1 , {opacity:0}); init(); initObject(); initLight(); }); loadQueue.loadManifest(manifestArray); } //シーン、カメラ、レンダラー生成 function init(){ scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000); camera.position.set(0, 0, 10); scene.add(camera); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); renderer.render(scene,camera); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.addEventListener('touchmove', function(e) {e.preventDefault();}, {passive: false}); var orbitControls = new THREE.OrbitControls(camera); var container = document.createElement('div'); document.body.appendChild(container); container.appendChild(renderer.domElement); render(); window.addEventListener('resize',onWindowResize,false); function onWindowResize(){ camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth,window.innerHeight); } } //オブジェクト生成 function initObject(){ //壁、床の設置 var loader = new THREE.TextureLoader(); var texture,nrmTexture; var planeGeometry,planeMaterial,plane; for(var i=0; i<=3; i++){ texture = loader.load('img/texture.jpg'); nrmTexture = loader.load('img/normalmap.jpg'); texture.wrapS = nrmTexture.wrapS = THREE.MirrorRepeatWrapping; texture.wrapT = nrmTexture.wrapT = THREE.MirrorRepeatWrapping; if(i==0){ texture.repeat.set(2,2); nrmTexture.repeat.set(2,2); planeGeometry = new THREE.PlaneGeometry(20,20); }else{ texture.repeat.set(2,1); nrmTexture.repeat.set(2,1); planeGeometry = new THREE.PlaneGeometry(20,10); } //PBRマテリアル planeMaterial = new THREE.MeshStandardMaterial({ map:texture, color: 0x666666, roughness : 0.6, normalMap: nrmTexture, normalScale: new THREE.Vector2( 2, -2), side:THREE.DoubleSide, }); plane = new THREE.Mesh(planeGeometry,planeMaterial); plane.receiveShadow = true; if(i==0){ plane.position.set(0,-1,0); plane.rotation.set(-Math.PI/2,0,0); }else if(i==1){ plane.position.set(0,4,-10); plane.rotation.set(0,0,0); }else if(i==2){ plane.position.set(10,4,0); plane.rotation.set(0,-Math.PI/2,0); }else if(i==3){ plane.position.set(-10,4,0); plane.rotation.set(0,-Math.PI/2,0); } scene.add(plane); } //glTF(iPhoneXS)の読み込み var loader = new THREE.GLTFLoader(); loader.load('data/iPhonexs.glb',function(data){ var gltf = data; var obj = gltf.scene; for(var i = 0; i < obj.children.length; i++){ var mesh = obj.children[i]; //console.log(i,mesh.name); mesh.receiveShadow = true; mesh.castShadow = true; } scene.add(obj); obj.position.set(0.8,-1,0); obj.scale.set(1,1,1); obj.rotation.set(0,Math.PI * 0.025,0); }); //glTF(iPhoneXS)の読み込み loader.load('data/iPhonexs.glb',function(data){ var gltf = data; var obj = gltf.scene; for(var i = 0; i < obj.children.length; i++){ var mesh = obj.children[i]; mesh.receiveShadow = true; mesh.castShadow = true; } scene.add(obj); obj.position.set(-0.5,-1,-1.5); obj.scale.set(1,1,1); obj.rotation.set(0,Math.PI * 0.85,0); }); renderer.gammaOutput = true; } //ライト生成 function initLight(){ var ambientLight = new THREE.AmbientLight(0x666666); scene.add(ambientLight); var positionArr = [ [0,7,3,1], [-3,3.5,3,2.5], [3,3.5,-3,1] ]; for(var i = 0; i < positionArr.length; i++){ var directionalLight = new THREE.DirectionalLight(0xCCCCCC, positionArr[i][3]); directionalLight.position.set( positionArr[i][0], positionArr[i][1], positionArr[i][2]); directionalLight.castShadow = true; directionalLight.shadow.camera.top = 50; directionalLight.shadow.camera.bottom = -50; directionalLight.shadow.camera.right = 50; directionalLight.shadow.camera.left = -50; directionalLight.shadow.mapSize.set(4096,4096); scene.add(directionalLight); var helper = new THREE.DirectionalLightHelper( directionalLight, 1); //_this.scene.add(helper); } } //アニメーション function render() { requestAnimationFrame(render); renderer.render(scene, camera); } })();
完成したscript.jsを調整したデモになります。glTFをThree.jsで読み込むデモなので、パソコンとスマホで見ることができるようにしました。
関連記事