スマートフォンブラウザでAR（後半）

#はじめに
前回、ブラウザ上で3Dオブジェクトを表示する仕組みを作成しました。
続けてARの仕組みを作成します。
前半：https://qiita.com/NestVisual/items/a5030d4298cfbcb8b0af

#カメラの初期化
WebRTCのMediaDevices.getUserMediaメソッドを用いてカメラを初期化します。
ここではカメラを初期化後、実際のカメラ解像度を取得し、video要素に映像を割り当てています。

```ruby:sample2.html
<video id="myVideo" autoplay="true" muted playsinline="true" style="object-fit: cover;"></video>
	// （略）
	// カメラに要求するカメラ解像度
	var requireWidth = 480;
	var requireHeight = 640;
	var videoWidth;
	var videoHeight;
	var videoElement = document.getElementById('myVideo');

	var mode = "environment"; // インカメラを優先する場合 "user"
	var constraints = {
		video: {
			width: requireWidth,
			height: requireHeight,
			facingMode: mode,
		},
		audio: false
	};

	// getUserMedia成功時
	var onOpenStream = function(stream){
		// 実際のカメラ解像度を取得
		var track = stream.getTracks()[0];
		var setting = track.getSettings();
		videoWidth = setting.width;
		videoHeight = setting.height;
		videoElement.srcObject = stream;
		InitCanvas(); // 初期化処理の開始
	}

	// カメラの初期化
	if (navigator.mediaDevices.getUserMedia != null) {
		navigator.mediaDevices.getUserMedia(constraints)
			.then(function(stream) {
				onOpenStream(stream);
			})
			.catch(function(err){
				window.alert("カメラの初期化に失敗しました。端末およびブラウザの設定で、カメラへのアクセスが許可されているかを確認してください。");
			});
	}
	else{
		window.alert("このブラウザはWebRTCのカメラ初期化に対応していません。");
	}
```


#ARの初期化
まず、こちらのサイトでマーカー画像とパターンファイルを作成します。
https://jeromeetienne.github.io/AR.js/three.js/examples/marker-training/examples/generator.html
![screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626158/3c615807-d92b-98e7-ed7e-508ebed86ba6.png)

"Upload Marker"でマーカーとして使用する画像をアップロードし、
"Pattern Ratio"で、黒枠の太さを調整します。
pattern-marker.pattとpattern-marker.pngをダウンロードし、
pattファイルをhtmlから読める箇所に配置しておきます。
このスクリプトで、カメラがマーカー画像を読み込んだとき、オブジェクトが表示されるようにします。

```ruby:sample2.html
	// arの初期化
	var arToolkitContext = new THREEx.ArToolkitContext({
		cameraParametersUrl: THREEx.ArToolkitContext.baseURL + '../data/data/camera_para.dat',
		detectionMode: 'color_and_matrix',
		matrixCodeType: '4x4_BCH_13_9_3',
		patternRatio: 0.80, // マーカー作成時のpatternRatioに合わせる
		maxDetectionRate: 10,
		canvasWidth: videoWidth,
		canvasHeight: videoHeight,
	})
	arToolkitContext.init(function onCompleted(){
	})

	// マーカーが読み込まれると表示されるオブジェクトを、シーンに追加します。
	var markerRoot = new THREE.Group();
	scene.add(markerRoot);
	var markerControls = new THREEx.ArMarkerControls(arToolkitContext, markerRoot, {
		type : 'pattern',
		patternUrl : "pattern-marker.patt",
		changeMatrixMode : 'modelViewMatrix',
		minConfidence: 0.50
	})

	var obj;
	var loader = new THREE.GLTFLoader();
	var url = "cube.glb";
	loader.load( url, function(data) {
		obj = data.scene;
		markerRoot.add( obj ); // マーカーで表示されるオブジェクトの下に配置します。
	}, undefined, function ( error ) {
		console.error( error );
	} );
```

# サンプルコード
<details><summary>サンプルコード（折り畳み）</summary><div>

```ruby:sample2.html
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="utf-8">
	<title>モデル表示サンプル</title>
</head>

<body style="margin : 0px; overflow: hidden;">
	<video id="myVideo" autoplay="true" muted playsinline="true" style="object-fit: cover;"></video>
	<canvas id="myCanvas" style="position: absolute; left: 0px; top:0px; z-index:2; pointer-events: none; "></canvas>

	<!-- Three.js (CDN) -->
	<script src="https://unpkg.com/three@0.110.0/build/three.min.js"></script>
	<script src="https://unpkg.com/three@0.110.0/examples/js/controls/OrbitControls.js"></script>
	<script src="https://unpkg.com/three@0.110.0/examples/js/loaders/GLTFLoader.js"></script>
	<!-- <script src="https://raw.githack.com/AR-js-org/AR.js/master/three.js/build/ar.js"></script> -->
	<script src="three.js/build/ar.min.js"></script>
	
	<script>
		//////////////////////////////////////////////////////////////////////////////////
		//		カメラの初期化
		//////////////////////////////////////////////////////////////////////////////////
		// カメラに要求するカメラ解像度
		var requireWidth = 640;
		var requireHeight = 480;

		var videoWidth;
		var videoHeight;

		var videoElement = document.getElementById('myVideo');

		var mode = "environment"; // インカメラを優先する場合"user"
		var constraints = {
			video: {
				width: requireWidth,
				height: requireHeight,
				facingMode: mode,
			},
			audio: false
		};

		// getUserMedia成功時
		var onOpenStream = function(stream){
			// 実際のカメラ解像度を取得
			var track = stream.getTracks()[0];
			var setting = track.getSettings();
	        videoWidth = setting.width;
			videoHeight = setting.height;
			videoElement.srcObject = stream;
			InitCanvas();
		}

		// カメラの初期化
		if (navigator.mediaDevices.getUserMedia != null) {
			navigator.mediaDevices.getUserMedia(constraints)
				.then(function(stream) {
					onOpenStream(stream);
				})
				.catch(function(err){
					window.alert("カメラの初期化に失敗しました。端末およびブラウザの設定で、カメラへのアクセスが許可されているかを確認してください。");
				});
		}
		else{
			window.alert("このブラウザはWebRTCのカメラ初期化に対応していません。");
		}


		// キャンバスの初期化
		var InitCanvas = function(){
			//////////////////////////////////////////////////////////////////////////////////
			//		キャンバスの初期化
			//////////////////////////////////////////////////////////////////////////////////
			var canvasElement = document.querySelector('#myCanvas');
			//canvasElement.setSize( videoWidth, videoHeight );

			// renderer
			var renderer	= new THREE.WebGLRenderer({
				canvas: canvasElement,
				antialias: true,
				alpha: true
			});
			renderer.setClearColor(new THREE.Color('black'), 0)
			renderer.setPixelRatio(window.devicePixelRatio);
			renderer.setSize( videoWidth, videoHeight );
			
			//////////////////////////////////////////////////////////////////////////////////
			//		シーンの初期化
			//////////////////////////////////////////////////////////////////////////////////
			var scene = new THREE.Scene();
			
			//////////////////////////////////////////////////////////////////////////////////
			//		arの初期化
			//////////////////////////////////////////////////////////////////////////////////
			var arToolkitContext = new THREEx.ArToolkitContext({
				cameraParametersUrl: THREEx.ArToolkitContext.baseURL + '../data/data/camera_para.dat',
				detectionMode: 'color_and_matrix',
				matrixCodeType: '4x4_BCH_13_9_3',
				patternRatio: 0.80, // マーカー作成時のpatternRatioに合わせる
				maxDetectionRate: 10,
				canvasWidth: videoWidth,
				canvasHeight: videoHeight,
			})
			arToolkitContext.init(function onCompleted(){
			})

			//////////////////////////////////////////////////////////////////////////////////
			//		マーカーが読み込まれると表示されるオブジェクトを、シーンに追加します。
			//////////////////////////////////////////////////////////////////////////////////
			var markerRoot = new THREE.Group();
			scene.add(markerRoot);
			var markerControls = new THREEx.ArMarkerControls(arToolkitContext, markerRoot, {
				type : 'pattern',
				patternUrl : "pattern-marker.patt",
				changeMatrixMode : 'modelViewMatrix',
				minConfidence: 0.50
			})

			//////////////////////////////////////////////////////////////////////////////////
			//		カメラの作成
			//////////////////////////////////////////////////////////////////////////////////
			const camera = new THREE.PerspectiveCamera(60, videoWidth / videoHeight, 0.01, 100); // 今回は原点配置のまま
			
			//////////////////////////////////////////////////////////////////////////////////
			//		マウス操作（OrbitControls）
			//////////////////////////////////////////////////////////////////////////////////
			// controls = new THREE.OrbitControls( camera, renderer.domElement );
			// controls.target.set( 0, 0, 0 );
			// controls.update();
			
			//////////////////////////////////////////////////////////////////////////////////
			//		オブジェクトのロード
			//////////////////////////////////////////////////////////////////////////////////
			var obj;
			var loader = new THREE.GLTFLoader();
			var url = "cube.glb";
			loader.load( url, function(data) {
				obj = data.scene;
				markerRoot.add( obj ); // マーカーで表示されるオブジェクトの下に配置します。
			}, undefined, function ( error ) {
				console.error( error );
			} );
			
			//////////////////////////////////////////////////////////////////////////////////
			//		ライティング
			//////////////////////////////////////////////////////////////////////////////////
			// 平行光源を作成
			const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
			directionalLight.position.set(1, 1, 5);
			scene.add(directionalLight);
			// 環境光を追加
			const light = new THREE.HemisphereLight(0xFFFFFF, 0x333333, 2.0);
			light.position.set(1, 5, 1);
			scene.add(light);
			
			//////////////////////////////////////////////////////////////////////////////////
			//		毎フレームの描画処理
			//////////////////////////////////////////////////////////////////////////////////
			drawFrame();
			function drawFrame() {
			    // 映像を解析してARを実行
				arToolkitContext.update( videoElement );

				renderer.render(scene, camera);
				requestAnimationFrame(drawFrame);
			}
		}
	</script>
</body>

</html>
```
</div></details>

# まとめ
このようにして、スマホでARが出来るようになりました。
![screen2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626158/323c72d6-86ef-3548-0874-54dbf509529d.png)