2019年03月03日 - WebVR・Three.js

glTFをThree.jsで読み込み

glTFをThree.jsで読み込み

Blenderで3DCG制作」で制作したドーナツをglTFに出力して、Three.jsで読み込んでみました。

BlenderからglTFを出力

● glTFとは

glTF (GL Transmission Format)は、Khronos Groupによって提唱された、JSON形式の3Dファイルフォーマットです。もともとOpenGL(特にWebGL)での利用が想定され、現在のバージョンはglTF2.0です。

glTF

● glTF Exporterの準備

BlenderでglTFを出力するためには、アドオンのglTF Exporterが必要です。

GitHubからデータをダウンロードして、「scripts/addon/io_scene_gltf2」を Blenderのアドオンディレクトリに追加します。アドオンディレクトリは、Macの場合、Blenderアプリを右クリックして、「パッケージの内容を表示」から、「Contents/Resources/2.79/scripts/addons」です。

アドオンのglTF Exporterを有効化

「ユーザー設定 > アドオン」の「Import-Export:glTF 2.0 format」にチェックを入れて更新し、アドンを有効化します。

● PBRマテリアルの設定

マテリアルとテクスチャを表示させるため、BlenderでPBRマテリアルを設定しました。PBR(物理ベースレンダリング)は現実の光学現象をシミュレートすることにより、リアルな質感を表現できるレンダリング方法です。

GitHubからダウンロードしたデータの「pbr_node」中に「glTF2.blend」があるので、「ファイル > リンク > glTF2.blend」を選択します。その中の「NodeTree > glTF Metallic Roughness」を選択すると、ノードエディターでglTF Metallic Roughnessが使用できるようになります。

ノードエディターの「追加 > グループ」の中にglTF Metallic Roughnessが追加されます。マテリアルカラーを設定したい場合は、BaseColorFactorのカラーを設定します。テクスチャを設定したい場合は、Image TextureをBaseColorにつなげます。詳細は、英語ですが下記ページに書いてあります。

PBRマテリアルの設定

● glTFを出力

準備ができたら「ファイル > エクスポート > glTF2.0 (.gltf)」で出力します。設定はデフォルトのままで問題ありません。

glTFを出力する設定

必要であれば、glTFを出力するさい、モディファイアーを適用しておきます。

Three.jsでglTFを読みこみ

● Three.jsで読み込む

出力したglTFは、THREE.GLTFLoaderで読み込むことができます。

//glTFの読み込み
const gltfLoader = new GLTFLoader();

gltfLoader.load('./donut.gltf',function(data){
	const gltf = data;
	const obj = gltf.scene;
	scene.add(obj);
});

//読み込んだシーンが暗いので、明るくする
renderer.outputEncoding = THREE.GammaEncoding;

マテリアルとテクスチャが反映され、ドーナツが表示されました。

ただ、皿と机に影ができていません。また、ライティングはThree.jsで設定するので、ライティングと影を調整します。影のつけ方は、「Three.jsでオブジェクトを選択」で説明していますが、皿と机に影をつけるためには、それぞれMeshを取得する必要があります。

ブラウザにドーナッツを表示

glTFはJSONデータで、読み込んだglTFの中のMeshは、下記のように「.children」で取得することができます。Meshの名前はBlenderで設定したものが入っています。

//glTFの読み込み
const gltfLoader = new GLTFLoader();

gltfLoader.load('./donut.gltf',function(data){
	const gltf = data;
	const obj = gltf.scene;

	for(let i = 0; i < obj.children.length; i++){

		let mesh = obj.children[i];

		//コンソールにMeshの名前一覧を出力。
		//console.log(i,mesh.name);
	}
	scene.add(obj);
});

//読み込んだシーンが暗いので、明るくする
renderer.outputEncoding = THREE.GammaEncoding;

まだまだわからないことが多く、もっと質感やクオリティはあげられると思いますが、一旦ライティングと影を調節して完成です。

Three.jsでglTFを表示しました

● script.js

必要なライブラリを読み込みます。

<script src="js/TweenMax.min.js"></script>

Three.js関連のライブラリはscript.jsからインポートするので、script.jsはtype="module"をつけて読み込みます。

<script src="js/script.js" type="module"></script>

完成した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;

function init(){
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,1000);
    camera.position.set(0,6,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});

	const gltfLoader = new GLTFLoader();
	gltfLoader.load('./data/donut.gltf',

		function(gltf){
			const obj = gltf.scene;
			for(let i = 0; i < obj.children.length; i++){

				let mesh = obj.children[i];
				if(i >= 1000){
					if(i==1014 || i==1019){
						mesh.receiveShadow = true;
					}
					mesh.castShadow = true;
				}
			}
			scene.add(obj);

			TweenMax.to('#loader_wrapper',1,{
                opacity:0,
                delay:1,
                onComplete: function(){
                    document.getElementById('loader_wrapper').style.display ='none';
                }
            });
            threeWorld();
			setLight();
			setController();
			rendering();
		});
}

function threeWorld(){
	renderer.outputEncoding = THREE.GammaEncoding;
}

function setLight(){
	const ambientLight = new THREE.AmbientLight(0x666666);
	scene.add(ambientLight);

	const positionArr = [
			[0,5,0,2],
			[-5,3,2,2],
			[5,3,2,2],
			[0,3,5,1],
			[0,3,-5,2]
		];

	for(let i = 0; i < positionArr.length; i++){
		const directionalLight = new THREE.DirectionalLight(0xFFFFFF, positionArr[i][3]);
		directionalLight.position.set( positionArr[i][0], positionArr[i][1], positionArr[i][2]);

		if(i == 0 || i == 2 || i == 3){
			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);

		//const 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);
}

完成したデモになります。Oculus Goで確認したところ、スプリンクルの数が多すぎて負荷がかかりすぎているようなので、パソコンとスマホのみで見ることができるようにしました。

※「Blender2.8でglTFを出力」で、Blender2.8でPBRマテリアルを使用して、より綺麗にglTFを出力する方法を更新しました。(2019年9月16日)