You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					415 lines
				
				22 KiB
			
		
		
			
		
	
	
					415 lines
				
				22 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								<!DOCTYPE html><html lang="ko"><head>
							 | 
						||
| 
								 | 
							
								    <meta charset="utf-8">
							 | 
						||
| 
								 | 
							
								    <title>VR - Look to Select</title>
							 | 
						||
| 
								 | 
							
								    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
							 | 
						||
| 
								 | 
							
								    <meta name="twitter:card" content="summary_large_image">
							 | 
						||
| 
								 | 
							
								    <meta name="twitter:site" content="@threejs">
							 | 
						||
| 
								 | 
							
								    <meta name="twitter:title" content="Three.js – VR - Look to Select">
							 | 
						||
| 
								 | 
							
								    <meta property="og:image" content="https://threejs.org/files/share.png">
							 | 
						||
| 
								 | 
							
								    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
							 | 
						||
| 
								 | 
							
								    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    <link rel="stylesheet" href="/manual/resources/lesson.css">
							 | 
						||
| 
								 | 
							
								    <link rel="stylesheet" href="/manual/resources/lang.css">
							 | 
						||
| 
								 | 
							
								<!-- Import maps polyfill -->
							 | 
						||
| 
								 | 
							
								<!-- Remove this when import maps will be widely supported -->
							 | 
						||
| 
								 | 
							
								<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<script type="importmap">
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								  "imports": {
							 | 
						||
| 
								 | 
							
								    "three": "../../build/three.module.js"
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</script>
							 | 
						||
| 
								 | 
							
								    <link rel="stylesheet" href="/manual/ko/lang.css">
							 | 
						||
| 
								 | 
							
								  </head>
							 | 
						||
| 
								 | 
							
								  <body>
							 | 
						||
| 
								 | 
							
								    <div class="container">
							 | 
						||
| 
								 | 
							
								      <div class="lesson-title">
							 | 
						||
| 
								 | 
							
								        <h1>VR - Look to Select</h1>
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								      <div class="lesson">
							 | 
						||
| 
								 | 
							
								        <div class="lesson-main">
							 | 
						||
| 
								 | 
							
								          <p><strong>NOTE: 이 페이지의 예시에는 VR 지원 기기가 필요합니다. 
							 | 
						||
| 
								 | 
							
								VR 기기 없이는 동작하지 않으며 그 이유를 <a href="webxr.html">이전 글</a>
							 | 
						||
| 
								 | 
							
								에서 확인할 수 있습니다.</strong></p>
							 | 
						||
| 
								 | 
							
								<p><a href="webxr.html">이전 글</a>에서 우리는 three.js를 사용한 매우 간단한 VR 예제를 살펴보고 다양한 종류의 VR 시스템에 대해 이야기했습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>가장 간단하고 흔한 것은 기본적으로 5달러에서 50달러의 얼굴 마스크에 넣는 전화기인 VR 구글 카드 보드 스타일입니다.
							 | 
						||
| 
								 | 
							
								이런 종류의 VR에는 컨트롤러가 없기 때문에 사람들은 사용자 입력을 허용하기 위한 창의적인 해결책을 생각해 내야 합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>이때 가장 일반적인 해결책은 사용자가 무언가를 잠시 동안 가리킬 경우 그것이 선택되는 "Look to Select"입니다.</p>
							 | 
						||
| 
								 | 
							
								<p>"Look to Select"를 구현해봅시다! 먼저 <a href="webxr.html">이전 글의 예시</a>에서 시작해 <a href="picking.html">Three.js 피킹</a>에서 만든 <code class="notranslate" translate="no">PickHelper</code>를 추가할 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class PickHelper {
							 | 
						||
| 
								 | 
							
								  constructor() {
							 | 
						||
| 
								 | 
							
								    this.raycaster = new THREE.Raycaster();
							 | 
						||
| 
								 | 
							
								    this.pickedObject = null;
							 | 
						||
| 
								 | 
							
								    this.pickedObjectSavedColor = 0;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  pick(normalizedPosition, scene, camera, time) {
							 | 
						||
| 
								 | 
							
								    // restore the color if there is a picked object
							 | 
						||
| 
								 | 
							
								    if (this.pickedObject) {
							 | 
						||
| 
								 | 
							
								      this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
							 | 
						||
| 
								 | 
							
								      this.pickedObject = undefined;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // cast a ray through the frustum
							 | 
						||
| 
								 | 
							
								    this.raycaster.setFromCamera(normalizedPosition, camera);
							 | 
						||
| 
								 | 
							
								    // get the list of objects the ray intersected
							 | 
						||
| 
								 | 
							
								    const intersectedObjects = this.raycaster.intersectObjects(scene.children);
							 | 
						||
| 
								 | 
							
								    if (intersectedObjects.length) {
							 | 
						||
| 
								 | 
							
								      // pick the first object. It's the closest one
							 | 
						||
| 
								 | 
							
								      this.pickedObject = intersectedObjects[0].object;
							 | 
						||
| 
								 | 
							
								      // save its color
							 | 
						||
| 
								 | 
							
								      this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
							 | 
						||
| 
								 | 
							
								      // set its emissive color to flashing red/yellow
							 | 
						||
| 
								 | 
							
								      this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>해당 코드에 대한 설명은 <a href="picking.html">피킹에 대한 글</a>을 참조하십시오.</p>
							 | 
						||
| 
								 | 
							
								<p>이 기능을 사용하려면 인스턴스를 만들고 render loop에서 호출하기만 하면 됩니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const pickHelper = new PickHelper();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								...
							 | 
						||
| 
								 | 
							
								function render(time) {
							 | 
						||
| 
								 | 
							
								  time *= 0.001;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  ...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+  // 0, 0 is the center of the view in normalized coordinates.
							 | 
						||
| 
								 | 
							
								+  pickHelper.pick({x: 0, y: 0}, scene, camera, time);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>원래의 피킹 예시에서 우리는 마우스 좌표를 CSS 픽셀에서 캔버스를 가로질러 -1에서 +1로 가는 정규화된 좌표로 변환했습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>이 경우 우리는 항상 카메라가 마주 보고 있는 화면의 중심을 선택하기 때문에 정규화된 좌표의 중심인 x와 y 모두에 대해 0을 통과합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>그리고 우리가 그 물체들을 볼 때 그 물체들은 번쩍거릴 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						||
| 
								 | 
							
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/threejs-webvr-look-to-select.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/threejs-webvr-look-to-select.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>일반적으로 우리는 즉각적인 선택을 원하지 않습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>대신 우리는 실수로 어떤 것을 선택하지 않도록 하기 위해 몇 분 동안 카메라를 사용자가 선택하고자 하는 것에 고정시키도록 합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>그러기 위해서 사용자가 계속 보고 있었는지, 그리고 얼마나 오래 있었는지를 전달하기 위한 일종의 미터나 게이지나 방법이 필요합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>이를 위한 한 가지 쉬운 방법은 2가지 색상의 텍스처를 만들고 텍스처 오프셋을 사용하여 모델을 가로질러 텍스처를 이동시키는 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<p>VR 예제에 추가하기 전에 스스로 작동하는지 보도록 합시다.</p>
							 | 
						||
| 
								 | 
							
								<p>먼저 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>를 만들어 보겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = -2;    // Use values for left
							 | 
						||
| 
								 | 
							
								const right = 2;    // right, top and bottom
							 | 
						||
| 
								 | 
							
								const top = 1;      // that match the default
							 | 
						||
| 
								 | 
							
								const bottom = -1;  // canvas size.
							 | 
						||
| 
								 | 
							
								const near = -1;
							 | 
						||
| 
								 | 
							
								const far = 1;
							 | 
						||
| 
								 | 
							
								const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>그리고 캔버스의 크기가 변경되면 업데이트하는 것을 잊지 마십시오.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
							 | 
						||
| 
								 | 
							
								  time *= 0.001;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (resizeRendererToDisplaySize(renderer)) {
							 | 
						||
| 
								 | 
							
								    const canvas = renderer.domElement;
							 | 
						||
| 
								 | 
							
								    const aspect = canvas.clientWidth / canvas.clientHeight;
							 | 
						||
| 
								 | 
							
								+    camera.left = -aspect;
							 | 
						||
| 
								 | 
							
								+    camera.right = aspect;
							 | 
						||
| 
								 | 
							
								    camera.updateProjectionMatrix();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  ...
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>우리는 현재 중앙 위아래 두 유닛과 좌우 측면 유닛을 보여주는 카메라를 가지고 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>다음으로 2가지 색 텍스처를 만들어 봅시다. 몇 군데 <a href="indexed-textures.html">다른</a> <a href="post-processing-3dlut.html">곳에서</a> 사용했던 <a href="/docs/#api/ko/textures/DataTexture"><code class="notranslate" translate="no">DataTexture</code></a>를 사용할 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeDataTexture(data, width, height) {
							 | 
						||
| 
								 | 
							
								  const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
							 | 
						||
| 
								 | 
							
								  texture.minFilter = THREE.NearestFilter;
							 | 
						||
| 
								 | 
							
								  texture.magFilter = THREE.NearestFilter;
							 | 
						||
| 
								 | 
							
								  texture.needsUpdate = true;
							 | 
						||
| 
								 | 
							
								  return texture;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const cursorColors = new Uint8Array([
							 | 
						||
| 
								 | 
							
								  64, 64, 64, 64,       // dark gray
							 | 
						||
| 
								 | 
							
								  255, 255, 255, 255,   // white
							 | 
						||
| 
								 | 
							
								]);
							 | 
						||
| 
								 | 
							
								const cursorTexture = makeDataTexture(cursorColors, 2, 1);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>그 다음 <a href="/docs/#api/ko/geometries/TorusGeometry"><code class="notranslate" translate="no">TorusGeometry</code></a>에 있는 텍스처를 사용할 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const ringRadius = 0.4;
							 | 
						||
| 
								 | 
							
								const tubeRadius = 0.1;
							 | 
						||
| 
								 | 
							
								const tubeSegments = 4;
							 | 
						||
| 
								 | 
							
								const ringSegments = 64;
							 | 
						||
| 
								 | 
							
								const cursorGeometry = new THREE.TorusGeometry(
							 | 
						||
| 
								 | 
							
								    ringRadius, tubeRadius, tubeSegments, ringSegments);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const cursorMaterial = new THREE.MeshBasicMaterial({
							 | 
						||
| 
								 | 
							
								  color: 'white',
							 | 
						||
| 
								 | 
							
								  map: cursorTexture,
							 | 
						||
| 
								 | 
							
								  transparent: true,
							 | 
						||
| 
								 | 
							
								  blending: THREE.CustomBlending,
							 | 
						||
| 
								 | 
							
								  blendSrc: THREE.OneMinusDstColorFactor,
							 | 
						||
| 
								 | 
							
								  blendDst: THREE.OneMinusSrcColorFactor,
							 | 
						||
| 
								 | 
							
								});
							 | 
						||
| 
								 | 
							
								const cursor = new THREE.Mesh(cursorGeometry, cursorMaterial);
							 | 
						||
| 
								 | 
							
								scene.add(cursor);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>그 다음 <code class="notranslate" translate="no">render</code>에서 텍스처의 오프셋을 조정하도록 합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
							 | 
						||
| 
								 | 
							
								  time *= 0.001;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (resizeRendererToDisplaySize(renderer)) {
							 | 
						||
| 
								 | 
							
								    const canvas = renderer.domElement;
							 | 
						||
| 
								 | 
							
								    const aspect = canvas.clientWidth / canvas.clientHeight;
							 | 
						||
| 
								 | 
							
								    camera.left = -aspect;
							 | 
						||
| 
								 | 
							
								    camera.right = aspect;
							 | 
						||
| 
								 | 
							
								    camera.updateProjectionMatrix();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+  const fromStart = 0;
							 | 
						||
| 
								 | 
							
								+  const fromEnd = 2;
							 | 
						||
| 
								 | 
							
								+  const toStart = -0.5;
							 | 
						||
| 
								 | 
							
								+  const toEnd = 0.5;
							 | 
						||
| 
								 | 
							
								+  cursorTexture.offset.x = THREE.MathUtils.mapLinear(
							 | 
						||
| 
								 | 
							
								+      time % 2,
							 | 
						||
| 
								 | 
							
								+      fromStart, fromEnd,
							 | 
						||
| 
								 | 
							
								+      toStart, toEnd);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  renderer.render(scene, camera);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p><code class="notranslate" translate="no">THREE.MathUtils.mapLinear</code>는 <code class="notranslate" translate="no">fromStart</code>와 <code class="notranslate" translate="no">fromEnd</code> 사이의 값을 취하여 시작과 끝 사이의 값으로 매핑합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>위의 경우, 0에서 2까지의 값을 의미하는 <code class="notranslate" translate="no">time % 2</code>를 취하여 -0.5에서 0.5까지의 값에 매핑합니다.</p>
							 | 
						||
| 
								 | 
							
								<p><a href="textures.html">텍스처</a>는 0에서 1까지 정규화된 텍스처 좌표를 사용하여 geometry에 매핑됩니다.
							 | 
						||
| 
								 | 
							
								즉, 기본 래핑 모드인 <code class="notranslate" translate="no">THREE.ClampToEdge</code>로 설정된 2x1 픽셀 이미지를 의미하며,
							 | 
						||
| 
								 | 
							
								텍스처 좌표를 -0.5만큼 조정하면 전체 메시가 첫 번째 색상이 되고 텍스처 좌표를 +0.5만큼 조정하면 전체 메시가 두 번째 색상이 됩니다.
							 | 
						||
| 
								 | 
							
								필터링을 <code class="notranslate" translate="no">THREE.NearestFilter</code>로 설정하면 geometry를 통해 두 색상 간의 전환이 가능해집니다.</p>
							 | 
						||
| 
								 | 
							
								<p><a href="backgrounds.html">배경과 관련된 글</a>에서 다루었던 것처럼 배경의 질감을 더해봅시다.
							 | 
						||
| 
								 | 
							
								2x2 색상 셋을 사용하지만 텍스처의 반복 설정을 8x8 그리드로 설정할 수 있습니다.
							 | 
						||
| 
								 | 
							
								이렇게 하면 커서가 렌더링 되어 다른 색상과 대조하여 확인할 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const backgroundColors = new Uint8Array([
							 | 
						||
| 
								 | 
							
								+    0,   0,   0, 255,  // black
							 | 
						||
| 
								 | 
							
								+   90,  38,  38, 255,  // dark red
							 | 
						||
| 
								 | 
							
								+  100, 175, 103, 255,  // medium green
							 | 
						||
| 
								 | 
							
								+  255, 239, 151, 255,  // light yellow
							 | 
						||
| 
								 | 
							
								+]);
							 | 
						||
| 
								 | 
							
								+const backgroundTexture = makeDataTexture(backgroundColors, 2, 2);
							 | 
						||
| 
								 | 
							
								+backgroundTexture.wrapS = THREE.RepeatWrapping;
							 | 
						||
| 
								 | 
							
								+backgroundTexture.wrapT = THREE.RepeatWrapping;
							 | 
						||
| 
								 | 
							
								+backgroundTexture.repeat.set(4, 4);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const scene = new THREE.Scene();
							 | 
						||
| 
								 | 
							
								+scene.background = backgroundTexture;
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이제 이것을 실행하면 게이지와 같은 원을 얻을 수 있고 게이지 위치를 설정할 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						||
| 
								 | 
							
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/threejs-webvr-look-to-select-selector.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/threejs-webvr-look-to-select-selector.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>몇 가지 주목하고 <strong>시도해야 할 것들</strong>이 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<ul>
							 | 
						||
| 
								 | 
							
								<li><p>다음과 같이 <code class="notranslate" translate="no">cursorMaterial</code>의 <code class="notranslate" translate="no">blending</code>, <code class="notranslate" translate="no">blendSrc</code>, <code class="notranslate" translate="no">blendDst</code>
							 | 
						||
| 
								 | 
							
								속성을 설정합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate notranslate" translate="no">  blending: THREE.CustomBlending,
							 | 
						||
| 
								 | 
							
								  blendSrc: THREE.OneMinusDstColorFactor,
							 | 
						||
| 
								 | 
							
								  blendDst: THREE.OneMinusSrcColorFactor,
							 | 
						||
| 
								 | 
							
								</pre><p>이것은 효과의 <em>역</em> 타입으로 주어집니다.
							 | 
						||
| 
								 | 
							
								그 세 줄에 주석을 달면 차이를 알 수 있을 것입니다.
							 | 
						||
| 
								 | 
							
								저는 역효과가 가장 좋다고 생각하는데, 이렇게 하면 커서의 색깔에 상관없이 커서가 보일 수 있기 때문입니다.</p>
							 | 
						||
| 
								 | 
							
								</li>
							 | 
						||
| 
								 | 
							
								<li><p><a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>가 아닌 <a href="/docs/#api/ko/geometries/TorusGeometry"><code class="notranslate" translate="no">TorusGeometry</code></a>를 사용해 봅시다.</p>
							 | 
						||
| 
								 | 
							
								<p>어떤 이유로든 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>는 평평한 UV 매핑 방식을 사용합니다.
							 | 
						||
| 
								 | 
							
								이 때문에 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>를 사용하면 위에서처럼 링 주위가 아닌 수평으로 링을 가로질러 텍스처가 미끄러집니다.</p>
							 | 
						||
| 
								 | 
							
								<p>이걸 시도해 보고 <a href="/docs/#api/ko/geometries/TorusGeometry"><code class="notranslate" translate="no">TorusGeometry</code></a>를 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>(위 예시에서 설명한 대로)로 바꾸면 무슨 뜻인지 알 수 있을 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<p><em>적절한</em> 정의를 위한 <em>적절한</em> 할 것은 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>를 사용하되 링 주위를 돌도록 텍스처 좌표를 고정하는 것입니다.
							 | 
						||
| 
								 | 
							
								아니면, 자신만의 링 지오메트리를 생성하세요. 그래도 torus는 잘 작동합니다.
							 | 
						||
| 
								 | 
							
								<a href="/docs/#api/ko/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>과 함께 카메라 바로 앞에 배치하면 링과 똑같이 보이고 텍스처 좌표가 링 주위를 돌기 때문에 우리가 원하는 대로 작동합니다.</p>
							 | 
						||
| 
								 | 
							
								</li>
							 | 
						||
| 
								 | 
							
								</ul>
							 | 
						||
| 
								 | 
							
								<p>이제 이것을 위의 VR 코드와 통합해 봅시다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class PickHelper {
							 | 
						||
| 
								 | 
							
								-  constructor() {
							 | 
						||
| 
								 | 
							
								+  constructor(camera) {
							 | 
						||
| 
								 | 
							
								    this.raycaster = new THREE.Raycaster();
							 | 
						||
| 
								 | 
							
								    this.pickedObject = null;
							 | 
						||
| 
								 | 
							
								-    this.pickedObjectSavedColor = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+    const cursorColors = new Uint8Array([
							 | 
						||
| 
								 | 
							
								+      64, 64, 64, 64,       // dark gray
							 | 
						||
| 
								 | 
							
								+      255, 255, 255, 255,   // white
							 | 
						||
| 
								 | 
							
								+    ]);
							 | 
						||
| 
								 | 
							
								+    this.cursorTexture = makeDataTexture(cursorColors, 2, 1);
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    const ringRadius = 0.4;
							 | 
						||
| 
								 | 
							
								+    const tubeRadius = 0.1;
							 | 
						||
| 
								 | 
							
								+    const tubeSegments = 4;
							 | 
						||
| 
								 | 
							
								+    const ringSegments = 64;
							 | 
						||
| 
								 | 
							
								+    const cursorGeometry = new THREE.TorusGeometry(
							 | 
						||
| 
								 | 
							
								+        ringRadius, tubeRadius, tubeSegments, ringSegments);
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    const cursorMaterial = new THREE.MeshBasicMaterial({
							 | 
						||
| 
								 | 
							
								+      color: 'white',
							 | 
						||
| 
								 | 
							
								+      map: this.cursorTexture,
							 | 
						||
| 
								 | 
							
								+      transparent: true,
							 | 
						||
| 
								 | 
							
								+      blending: THREE.CustomBlending,
							 | 
						||
| 
								 | 
							
								+      blendSrc: THREE.OneMinusDstColorFactor,
							 | 
						||
| 
								 | 
							
								+      blendDst: THREE.OneMinusSrcColorFactor,
							 | 
						||
| 
								 | 
							
								+    });
							 | 
						||
| 
								 | 
							
								+    const cursor = new THREE.Mesh(cursorGeometry, cursorMaterial);
							 | 
						||
| 
								 | 
							
								+    // add the cursor as a child of the camera
							 | 
						||
| 
								 | 
							
								+    camera.add(cursor);
							 | 
						||
| 
								 | 
							
								+    // and move it in front of the camera
							 | 
						||
| 
								 | 
							
								+    cursor.position.z = -1;
							 | 
						||
| 
								 | 
							
								+    const scale = 0.05;
							 | 
						||
| 
								 | 
							
								+    cursor.scale.set(scale, scale, scale);
							 | 
						||
| 
								 | 
							
								+    this.cursor = cursor;
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    this.selectTimer = 0;
							 | 
						||
| 
								 | 
							
								+    this.selectDuration = 2;
							 | 
						||
| 
								 | 
							
								+    this.lastTime = 0;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  pick(normalizedPosition, scene, camera, time) {
							 | 
						||
| 
								 | 
							
								+    const elapsedTime = time - this.lastTime;
							 | 
						||
| 
								 | 
							
								+    this.lastTime = time;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								-    // restore the color if there is a picked object
							 | 
						||
| 
								 | 
							
								-    if (this.pickedObject) {
							 | 
						||
| 
								 | 
							
								-      this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
							 | 
						||
| 
								 | 
							
								-      this.pickedObject = undefined;
							 | 
						||
| 
								 | 
							
								-    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+    const lastPickedObject = this.pickedObject;
							 | 
						||
| 
								 | 
							
								+    this.pickedObject = undefined;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // cast a ray through the frustum
							 | 
						||
| 
								 | 
							
								    this.raycaster.setFromCamera(normalizedPosition, camera);
							 | 
						||
| 
								 | 
							
								    // get the list of objects the ray intersected
							 | 
						||
| 
								 | 
							
								    const intersectedObjects = this.raycaster.intersectObjects(scene.children);
							 | 
						||
| 
								 | 
							
								    if (intersectedObjects.length) {
							 | 
						||
| 
								 | 
							
								      // pick the first object. It's the closest one
							 | 
						||
| 
								 | 
							
								      this.pickedObject = intersectedObjects[0].object;
							 | 
						||
| 
								 | 
							
								-      // save its color
							 | 
						||
| 
								 | 
							
								-      this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
							 | 
						||
| 
								 | 
							
								-      // set its emissive color to flashing red/yellow
							 | 
						||
| 
								 | 
							
								-      this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+    // show the cursor only if it's hitting something
							 | 
						||
| 
								 | 
							
								+    this.cursor.visible = this.pickedObject ? true : false;
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    let selected = false;
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    // if we're looking at the same object as before
							 | 
						||
| 
								 | 
							
								+    // increment time select timer
							 | 
						||
| 
								 | 
							
								+    if (this.pickedObject && lastPickedObject === this.pickedObject) {
							 | 
						||
| 
								 | 
							
								+      this.selectTimer += elapsedTime;
							 | 
						||
| 
								 | 
							
								+      if (this.selectTimer >= this.selectDuration) {
							 | 
						||
| 
								 | 
							
								+        this.selectTimer = 0;
							 | 
						||
| 
								 | 
							
								+        selected = true;
							 | 
						||
| 
								 | 
							
								+      }
							 | 
						||
| 
								 | 
							
								+    } else {
							 | 
						||
| 
								 | 
							
								+      this.selectTimer = 0;
							 | 
						||
| 
								 | 
							
								+    }
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    // set cursor material to show the timer state
							 | 
						||
| 
								 | 
							
								+    const fromStart = 0;
							 | 
						||
| 
								 | 
							
								+    const fromEnd = this.selectDuration;
							 | 
						||
| 
								 | 
							
								+    const toStart = -0.5;
							 | 
						||
| 
								 | 
							
								+    const toEnd = 0.5;
							 | 
						||
| 
								 | 
							
								+    this.cursorTexture.offset.x = THREE.MathUtils.mapLinear(
							 | 
						||
| 
								 | 
							
								+        this.selectTimer,
							 | 
						||
| 
								 | 
							
								+        fromStart, fromEnd,
							 | 
						||
| 
								 | 
							
								+        toStart, toEnd);
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    return selected ? this.pickedObject : undefined;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>위의 코드를 보시면 커서 형상, 텍스처, 매테리얼을 만들기 위해 모든 코드를 추가한 것을 볼 수 있습니다.
							 | 
						||
| 
								 | 
							
								그리고 카메라의 자식으로 추가해서 항상 카메라 앞에 놓이게 합니다.
							 | 
						||
| 
								 | 
							
								커서가 렌더링 되지 않을 경우 카메라를 scene에 추가해야 합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+scene.add(camera);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이 다음 이번에 피킹 할 것이 지난번과 같은지 확인합니다.
							 | 
						||
| 
								 | 
							
								타이머에 경과 시간을 추가하고 타이머가 한계치에 도달하면 선택한 항목을 반환합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>이제 큐브들을 고르는데 그것을 사용해 봅시다.
							 | 
						||
| 
								 | 
							
								간단한 예로 3개의 구도 추가하겠습니다.
							 | 
						||
| 
								 | 
							
								큐브를 선택하여 큐브를 숨기고 해당 구의 숨기기를 취소합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>먼저 구면 geometry를 만들어보겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const boxWidth = 1;
							 | 
						||
| 
								 | 
							
								const boxHeight = 1;
							 | 
						||
| 
								 | 
							
								const boxDepth = 1;
							 | 
						||
| 
								 | 
							
								-const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
							 | 
						||
| 
								 | 
							
								+const boxGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+const sphereRadius = 0.5;
							 | 
						||
| 
								 | 
							
								+const sphereGeometry = new THREE.SphereGeometry(sphereRadius);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>그리고 세 쌍의 박스와 구 <a href="/docs/#api/ko/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a>를 만들어 봅시다. 각 <a href="/docs/#api/ko/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a>를 파트너와 연결할 수 있도록 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"><code class="notranslate" translate="no">맵</code></a>을 사용할 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const cubes = [
							 | 
						||
| 
								 | 
							
								-  makeInstance(geometry, 0x44aa88,  0),
							 | 
						||
| 
								 | 
							
								-  makeInstance(geometry, 0x8844aa, -2),
							 | 
						||
| 
								 | 
							
								-  makeInstance(geometry, 0xaa8844,  2),
							 | 
						||
| 
								 | 
							
								-];
							 | 
						||
| 
								 | 
							
								+const meshToMeshMap = new Map();
							 | 
						||
| 
								 | 
							
								+[
							 | 
						||
| 
								 | 
							
								+  { x:  0, boxColor: 0x44aa88, sphereColor: 0xFF4444, },
							 | 
						||
| 
								 | 
							
								+  { x:  2, boxColor: 0x8844aa, sphereColor: 0x44FF44, },
							 | 
						||
| 
								 | 
							
								+  { x: -2, boxColor: 0xaa8844, sphereColor: 0x4444FF, },
							 | 
						||
| 
								 | 
							
								+].forEach((info) => {
							 | 
						||
| 
								 | 
							
								+  const {x, boxColor, sphereColor} = info;
							 | 
						||
| 
								 | 
							
								+  const sphere = makeInstance(sphereGeometry, sphereColor, x);
							 | 
						||
| 
								 | 
							
								+  const box = makeInstance(boxGeometry, boxColor, x);
							 | 
						||
| 
								 | 
							
								+  // hide the sphere
							 | 
						||
| 
								 | 
							
								+  sphere.visible = false;
							 | 
						||
| 
								 | 
							
								+  // map the sphere to the box
							 | 
						||
| 
								 | 
							
								+  meshToMeshMap.set(box, sphere);
							 | 
						||
| 
								 | 
							
								+  // map the box to the sphere
							 | 
						||
| 
								 | 
							
								+  meshToMeshMap.set(sphere, box);
							 | 
						||
| 
								 | 
							
								+});
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>큐브를 회전하는 <code class="notranslate" translate="no">render</code>에서 <code class="notranslate" translate="no">cubes</code> 대신 <code class="notranslate" translate="no">meshToMeshMap</code>를 반복해야 합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-cubes.forEach((cube, ndx) => {
							 | 
						||
| 
								 | 
							
								+let ndx = 0;
							 | 
						||
| 
								 | 
							
								+for (const mesh of meshToMeshMap.keys()) {
							 | 
						||
| 
								 | 
							
								  const speed = 1 + ndx * .1;
							 | 
						||
| 
								 | 
							
								  const rot = time * speed;
							 | 
						||
| 
								 | 
							
								-  cube.rotation.x = rot;
							 | 
						||
| 
								 | 
							
								-  cube.rotation.y = rot;
							 | 
						||
| 
								 | 
							
								-});
							 | 
						||
| 
								 | 
							
								+  mesh.rotation.x = rot;
							 | 
						||
| 
								 | 
							
								+  mesh.rotation.y = rot;
							 | 
						||
| 
								 | 
							
								+  ++ndx;
							 | 
						||
| 
								 | 
							
								+}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이제 새로운 <code class="notranslate" translate="no">PickHelper</code> 구현을 사용하여 개체 중 하나를 선택할 수 있습니다. 이 옵션을 선택하면 개체를 숨기고 그 파트너를 드러냅니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// 0, 0 is the center of the view in normalized coordinates.
							 | 
						||
| 
								 | 
							
								-pickHelper.pick({x: 0, y: 0}, scene, camera, time);
							 | 
						||
| 
								 | 
							
								+const selectedObject = pickHelper.pick({x: 0, y: 0}, scene, camera, time);
							 | 
						||
| 
								 | 
							
								+if (selectedObject) {
							 | 
						||
| 
								 | 
							
								+  selectedObject.visible = false;
							 | 
						||
| 
								 | 
							
								+  const partnerObject = meshToMeshMap.get(selectedObject);
							 | 
						||
| 
								 | 
							
								+  partnerObject.visible = true;
							 | 
						||
| 
								 | 
							
								+}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>그리고 이를 통해 우리는 꽤 괜찮은 <em>look to select</em>를 구현해야 합니다.</p>
							 | 
						||
| 
								 | 
							
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						||
| 
								 | 
							
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/threejs-webvr-look-to-select-w-cursor.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/threejs-webvr-look-to-select-w-cursor.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>이 예제가 구글 카드 보드 레벨 UX의 "look to select"를 구현하는 방법에 대한 아이디어를 주었기를 바랍니다.
							 | 
						||
| 
								 | 
							
								텍스쳐 좌표 오프셋을 사용한 슬라이딩 텍스쳐도 일반적으로 유용한 기법입니다.</p>
							 | 
						||
| 
								 | 
							
								<p>다음으로는 <a href="webxr-point-to-select.html">VR 컨트롤러가 있는 사용자가 사물을 가리키고 이동할 수 있는 방법을 알아보겠습니다.</a>.</p>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        </div>
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								    </div>
							 | 
						||
| 
								 | 
							
								  
							 | 
						||
| 
								 | 
							
								  <script src="/manual/resources/prettify.js"></script>
							 | 
						||
| 
								 | 
							
								  <script src="/manual/resources/lesson.js"></script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</body></html>
							 |