2019年09月16日

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で読み込むデモなので、パソコンとスマホで見ることができるようにしました。

  • このエントリーをはてなブックマークに追加

関連記事

前の記事へ

BlenderでiPhoneXSを制作(2)

次の記事へ

Blender2.8で始める3DCG