Three JS:CardboardEffect.jsを使ってVRコンテンツを作ってみた
                    人気WebGLライブラリのThree JSですが、バージョンアップ頻度が非常に早く2016年2月現在でr74となっております。r74では所々仕様が変わっており、新しいモジュールも増えています。色々と探っていたらeffectsフォルダにCardboardEffect.jsなるものを発見!!今回はこのCardboardEffect.jsを使って簡易的なVRコンテンツを作ってみようと思います。
今まではThree JSでのVRと言ったら、VREffect.jsやStereoEffect.jsがThree JSに付属されていました。しかしVREffect.jsはOculus RiftなどのHMD(ヘッドマウントディスプレイ)用のトラッキングデータを取得する本格的なものですし、StereoEffect.jsはVRに最適化されていませんでした。CardboardEffect.jsに関しては詳しいことは分かりませんが、カードボードエフェクトと言うくらいですからGoogle Cardboardに最適化されているのでしょう。現状、スマートフォンで見ることが出来る簡易的なVRコンテンツライブラリの最適解と言えると思います。
各ライブラリを準備しよう
それでは早速Three JS公式サイトより一式ダウンロードします。ちなみに現状のバージョンはr74です。
解凍したフォルダから使用するファイルは以下の5点です。適当に配置して読み込んでください。
| ファイル名 | 場所 | 役割 | 
| three.min.js | three.js/ build/ | Three JSメインファイル | 
| Detector.js | three.js/ examples/js/ | WebGL使用環境可否判別 | 
| CardboardEffect.js | three.js/ examples/js/effects/ | カードボード用エフェクト | 
| DeviceOrientation Controls.js | three.js/ examples/js/controls/ | スマートフォン用コントローラー | 
| OrbitControls.js | three.js/ examples/js/controls/ | PC用コントローラー | 
また、これから紹介するコードではjQueryも使用しておりますので用意してください。
CardboardEffect.jsの使い方
CardboardEffect.jsの使い方はとても簡単です。基本的に使うのは以下の3つのコードのみです。
| 
					 1 2 3 4 5 6  | 
						//初期化 effect = new THREE.CardboardEffect( レンダラー); //初期化時とリサイズ処理内 effect.setSize( canvasの横幅, canvasの高さ); //ループ処理内で effect.render( シーン, カメラ);//renderer.render( シーン, カメラ)はいらない  | 
					
このコードを通常のThree JS描画のコードに追加するだけで、自動的に画面分割・樽型エフェクト・視差効果などを付けてくれます。下の画像をクリックすると、このコードを追加したデモが表示されます。スマホの場合は色々と動かしてみてください。

ソースコード紹介
それでは全体のソースコードを紹介します。通常の3D描画のコードに加え、影の設定とスマホ用のコントローラー処理をくわえております。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  | 
						<!DOCTYPE html> <html lang="ja"> <head> 	<meta charset="UTF-8"> 	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, minimal-ui"> 	<title>CardboardEffect.js TEST | matorel</title> 	<style> 		body,html{ 			margin: 0; 			padding: 0; 		} 	</style> </head> <body> 	<div id="canvas-area"> 	</div> 	<script src="script/jquery.min.js"></script> 	<script src="script/three.min.js"></script> 	<script src="script/Detector.js"></script> 	<script src="script/CardboardEffect.js"></script> 	<script src="script/DeviceOrientationControls.js"></script> 	<script src="script/OrbitControls.js"></script> 	<script src="script/main.js"></script> </body> </html>  | 
					
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121  | 
						(function(){ 	var canW = window.innerWidth; 	var canH = window.innerHeight; 	var scene, camera, renderer, controls, effect, container, mesh;  	function init(){ 		container = document.getElementById('canvas-area');   		if(!Detector.webgl)Detector.addGetWebGLMessage({ parent: container});//WebGL環境確認 		scene = new THREE.Scene(); 		scene.matrixAutoUpdate = false; 		// カメラ:透視投影 		camera = new THREE.PerspectiveCamera( 60, canW / canH, 0.001, 2000); 		scene.add(camera); 		camera.position.set( 0, 0, 20); 		camera.lookAt( new THREE.Vector3() ); 		//レンズの焦点距離(この値によって視差の大小が決まる?) 		camera.focalLength = camera.position.distanceTo( new THREE.Vector3() ); 		//camera.focalLength = 2; 		// ライト:環境光 + ディレクションライト 		var ambientLight = new THREE.AmbientLight( 0x888888 ); 		var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 ); 		directionalLight.position.set( 1, 2, 2 ); 		// 影の設定 		directionalLight.castShadow = true; 		directionalLight.shadow.camera.near = -30; 		directionalLight.shadow.camera.far = 30; 		directionalLight.shadow.camera.left = -20; 		directionalLight.shadow.camera.right = 20;  		directionalLight.shadow.camera.top = 20;  		directionalLight.shadow.camera.bottom = -8;  		scene.add(ambientLight,directionalLight); 		// レンダラー 		renderer = new THREE.WebGLRenderer({antialias: true}); 		renderer.setSize( canW, canH); 		renderer.setPixelRatio( window.devicePixelRatio); 		renderer.shadowMap.enabled = true; 		container.appendChild( renderer.domElement); 		// オブジェクト 		var geometry = new THREE.TorusKnotGeometry( 5, 1, 80, 60); 		var material = new THREE.MeshLambertMaterial({color: 0xF99845}); 		mesh = new THREE.Mesh( geometry, material); 		mesh.position.set( 0, 0, 0);  		mesh.castShadow = true; 		scene.add(mesh); 		// 地面を配置 		var geometry = new THREE.PlaneGeometry( 100, 100); 		var material = new THREE.MeshLambertMaterial({color: 0xFFFFFF}); 		var back = new THREE.Mesh(geometry, material); 		back.rotation.set( -Math.PI/2, 0, 0);  		back.position.set( 0, -8, 0);  		back.receiveShadow = true; 		scene.add(back); 		// カードボード用エフェクト 		effect = new THREE.CardboardEffect( renderer); 		effect.setSize( canW, canH); 		//コントローラー 		controls = new THREE.OrbitControls(camera); 		controls.enablePan = false; 		controls.enableZoom = false; 		controls.maxPolarAngle = Math.PI * 0.48; 		function setOrientationControls(e){ 			if(!e.alpha)return; 			controls = new THREE.DeviceOrientationControls(camera, true); 			controls.connect(); 			controls.update(); 			renderer.domElement.addEventListener('click', fullscreen,false); 			window.removeEventListener('deviceorientation', setOrientationControls, true); 		} 		window.addEventListener('deviceorientation', setOrientationControls, true); 		window.addEventListener('resize', resize, false); 		setTimeout(resize, 1); 		rendering(); 	} 	function rendering(){ 		mesh.rotation.y += 0.01; 		mesh.rotation.x += 0.01; 		requestAnimationFrame(rendering); 		controls.update(); 		effect.render( scene, camera); 	} 	function resize(){ 		var width = window.innerWidth; 		var height = window.innerHeight; 		camera.aspect = width / height; 		camera.updateProjectionMatrix(); 		effect.setSize(width, height); 	} 	function fullscreen(){ 		if(container.requestFullscreen){ 			container.requestFullscreen(); 		} 		else if(container.msRequestFullscreen){ 			container.msRequestFullscreen(); 		} 		else if(container.mozRequestFullScreen){ 			container.mozRequestFullScreen(); 		} 		else if(container.webkitRequestFullscreen){ 			container.webkitRequestFullscreen(); 		} 	} 	$(function(){ 		init(); 	}); }());  | 
					
これまでのコードのサンプルを、下のリンク先で紹介しております。出来ればスマートフォンを横にして見て色々と動かしてみてください。カードボードを持っている方は使っていただければ立体的に見えるはずです。
まとめ
上記のコードですと、視差があまりなく立体感に乏しいかと思います。20行目のcamera.focalLengthの値を小さくすることにより視差が大きくなります。カメラレンズの焦点距離の関係なのですが、まだ私の知識が乏しくいまいち分かっていないので、これから試行錯誤していこうかと思います。こうした方が良いなどありましたらコメントいただければ幸いです。