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のプリンシプルBSDFで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の読み込み const gltfLoader = new GLTFLoader(); gltfLoader.load('./iphonexs.glb',function(data){ const gltf = data; const obj = gltf.scene; scene.add(obj); }); //読み込んだシーンが暗いので、明るくする renderer.outputEncoding = THREE.GammaEncoding;
● MeshStandardMaterial
Three.jsでPBR(物理ベースレンダリング)を使用したい場合は、MeshStandardMaterialを使用します。
MeshStandardMaterialでテクスチャの他にノーマルマップを設定し、荒さ(roughness)を調整することで、アスファルトのデコボコを表現することができます。
● script.js
必要なライブラリを読み込みます。
<script src="js/preloadjs.min.js"></script> <script src="js/TweenMax.min.js"></script>
Three.js関連のライブラリはscript.jsからインポートするので、script.jsはtype="module"をつけて読み込みます。
<script src="js/script.js" type="module"></script>
最近ChromeのAddEventListenerOptionsのpassiveがデフォルトでtrueになり、OrbitControlsを使用するとpreventDefaultが効かないため、スマホでタッチした時に意図せず画面がスクロールしてしまいます。そこで、passiveをfalseにする処理を追加しました。
document.addEventListener('touchmove', function(e) {e.preventDefault();}, {passive: false});
基本的には「glTFをThree.jsで読み込み」と同じですが、完成したscript.jsです。
//=============================================================== // Import Library //=============================================================== import * as THREE from './lib/three_jsm/three.module.js'; import { OrbitControls } from './lib/three_jsm/OrbitControls.js'; import { GLTFLoader } from './lib/three_jsm/GLTFLoader.js'; //=============================================================== // Main //=============================================================== window.addEventListener('load',function(){ init(); }); let scene,camera,renderer; let orbitControls; let textureArray; function init(){ scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,1000); camera.position.set(0,0,9); scene.add(camera); renderer = new THREE.WebGLRenderer({antialias:true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth,window.innerHeight); renderer.setClearColor(new THREE.Color(0x000000)); renderer.physicallyCorrectLights = true; renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; const container = document.querySelector('#canvas_vr'); container.appendChild(renderer.domElement); window.addEventListener('resize',function(){ camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth,window.innerHeight); },false); setLoading(); } function setLoading(){ TweenMax.to('.loader',0.1,{opacity:1}); let gltfLoader = new GLTFLoader(); gltfLoader.load('./data/iPhonexs.glb', function(gltf){ const obj = gltf.scene; for(let i = 0; i < obj.children.length; i++){ let mesh = obj.children[i]; mesh.receiveShadow = true; mesh.castShadow = true; } scene.add(obj); obj.position.set(0.8,-1,0); obj.rotation.set(0,Math.PI * 0.025,0); }); gltfLoader.load('./data/iPhonexs.glb', function(gltf){ const obj = gltf.scene; for(let i = 0; i < obj.children.length; i++){ let mesh = obj.children[i]; mesh.receiveShadow = true; mesh.castShadow = true; } scene.add(obj); obj.position.set(-0.5,-1,-1.5); obj.rotation.set(0,Math.PI * 0.85,0); }); const nameArray = ['texture','normalmap']; const manifestArray = []; for(let i = 0; i < nameArray.length; i++){ let name = nameArray[i]; let path = './img/'+name+'.jpg'; manifestArray.push({id:name,src:path}) } const loadQueue = new createjs.LoadQueue(); loadQueue.on('progress',function(e){ const progress = e.progress; }); textureArray = []; loadQueue.on('complete',function(){ for(let i = 0; i < nameArray.length; i++){ let tempImage = loadQueue.getResult(nameArray[i]); let tempTexture = new THREE.Texture(tempImage); tempTexture.needsUpdate = true; textureArray.push(tempTexture); } TweenMax.to('#loader_wrapper',1,{ opacity:0, delay:1, onComplete: function(){ document.getElementById('loader_wrapper').style.display ='none'; } }); threeWorld(); setLight(); setController(); rendering(); }); loadQueue.loadManifest(manifestArray); } function threeWorld(){ let loader = new THREE.TextureLoader(); let texture,nrmTexture; let planeGeometry,planeMaterial,plane; for(let i=0; i<=3; i++){ texture = textureArray[0]; nrmTexture = textureArray[1]; 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); } 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); } renderer.outputEncoding = THREE.GammaEncoding; } function setLight(){ const ambientLight = new THREE.AmbientLight(0xFFFFFFF); scene.add(ambientLight); const positionArr = [ [0,7,3,1], [-3,3.5,3,2.5], [3,3.5,-3,1] ]; for(let i = 0; i < positionArr.length; i++){ let 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); //let helper = new THREE.DirectionalLightHelper( directionalLight, 1); //scene.add(helper); } } function setController(){ document.addEventListener('touchmove',function(e){e.preventDefault();},{passive:false}); orbitControls = new OrbitControls(camera,renderer.domElement); orbitControls.enableDamping = true; orbitControls.dampingFactor = 0.5; } function rendering(){ if(orbitControls){ orbitControls.update(); } requestAnimationFrame(rendering); renderer.render(scene,camera); }
完成したデモになります。glTFをThree.jsで読み込むデモなので、パソコンとスマホで見ることができるようにしました。