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.
		
		
		
		
			
				
					602 lines
				
				37 KiB
			
		
		
			
		
	
	
					602 lines
				
				37 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								<!DOCTYPE html><html lang="ko"><head>
							 | 
						||
| 
								 | 
							
								    <meta charset="utf-8">
							 | 
						||
| 
								 | 
							
								    <title>에서 .OBJ 파일 불러오기</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 – 에서 .OBJ 파일 불러오기">
							 | 
						||
| 
								 | 
							
								    <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>에서 .OBJ 파일 불러오기</h1>
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								      <div class="lesson">
							 | 
						||
| 
								 | 
							
								        <div class="lesson-main">
							 | 
						||
| 
								 | 
							
								          <p>Three.js로 프로젝트를 진행할 때, 3D 모델 파일을 불러와 사용하는 것은
							 | 
						||
| 
								 | 
							
								아주 흔한 일입니다. 오늘은 주로 사용하는 파일 형식인 .OBJ 파일을
							 | 
						||
| 
								 | 
							
								불러오는 법에 대해 알아보겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>인터넷을 검색해 <a href="https://www.blendswap.com/blends/view/69174">CC-BY-NC 3.0 풍자 3D 모델</a>을
							 | 
						||
| 
								 | 
							
								하나 가져왔습니다(작가: <a href="https://www.blendswap.com/user/ahedov">ahedov</a>).</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img src="../resources/images/windmill-obj.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>다운받은 파일 형식이 .blend네요. <a href="https://blender.org">블렌더(Blender)</a>로
							 | 
						||
| 
								 | 
							
								파일을 열어 .OBJ 형식으로 변환하겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 827px;" src="../resources/images/windmill-export-as-obj.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<blockquote>
							 | 
						||
| 
								 | 
							
								<p>블렌더는 다른 프로그램과 다른 점이 많아 낯설게 느껴질 수 있습니다.
							 | 
						||
| 
								 | 
							
								블렌더를 처음 접한다면, 글 읽기를 잠시 멈추고 블렌더의 기본 UI 가이드를
							 | 
						||
| 
								 | 
							
								먼저 읽어보길 권장합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>추가로 보통 3D 프로그램은 수천 가지 기능을 지원하는 거대 함선과 같습니다.
							 | 
						||
| 
								 | 
							
								프로그램들 중에서도 복잡하기로 유명하죠. 1996년, 제가 3D Studio Max를 처음
							 | 
						||
| 
								 | 
							
								배우기 시작했을 때 저는 하루에 몇 시간씩 3주를 들여 공식 매뉴얼의 70% 정도를
							 | 
						||
| 
								 | 
							
								정독했습니다. 그리고 그게 몇 년 뒤 마야(Maya)를 배울 때 도움이 많이 됐죠. 3D
							 | 
						||
| 
								 | 
							
								모델을 만들든, 기존 모델을 수정하든, 3D 프로그램으로 무언가를 하고 싶다면 강의나
							 | 
						||
| 
								 | 
							
								튜토리얼에 따로 시간을 투자하기 바랍니다.</p>
							 | 
						||
| 
								 | 
							
								</blockquote>
							 | 
						||
| 
								 | 
							
								<p>특별한 일이 없다면 저는 파일을 내보낼 때 아래의 옵션을 사용합니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 239px;" src="../resources/images/windmill-export-options.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>자 이제 한 번 화면에 띄워보죠!</p>
							 | 
						||
| 
								 | 
							
								<p><a href="lights.html">조명에 관한 글</a>에서 썼던 예제를 가져와 이 예제를
							 | 
						||
| 
								 | 
							
								반구광(hemisphere light) 예제와 합칩니다. 그러면 장면에는 <a href="/docs/#api/ko/lights/HemisphereLight"><code class="notranslate" translate="no">HemisphereLight</code></a>
							 | 
						||
| 
								 | 
							
								하나, <a href="/docs/#api/ko/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> 하나가 있는 셈입니다. 또 GUI 관련 코드와 정육면체,
							 | 
						||
| 
								 | 
							
								구체 관련 코드도 지웁니다.</p>
							 | 
						||
| 
								 | 
							
								<p>다음으로 먼저 <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a> 모듈을 스크립트에 로드합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p><a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a>의 인스턴스를 생성한 뒤 .OBJ 파일의 경로와 콜백 함수를 넘겨
							 | 
						||
| 
								 | 
							
								<code class="notranslate" translate="no">load</code> 메서드를 실행합니다. 그리고 콜백 함수에서 불러온 모델을 장면에
							 | 
						||
| 
								 | 
							
								추가합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						||
| 
								 | 
							
								  const objLoader = new OBJLoader();
							 | 
						||
| 
								 | 
							
								  objLoader.load('resources/models/windmill/windmill.obj', (root) => {
							 | 
						||
| 
								 | 
							
								    scene.add(root);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</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/load-obj-no-materials.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/load-obj-no-materials.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>뭔가 성공한 듯하지만 재질(materials)이 없어 오류가 납니다. .OBJ 파일에도
							 | 
						||
| 
								 | 
							
								재질이 없고 따로 재질을 지정하지도 않았기 때문이죠.</p>
							 | 
						||
| 
								 | 
							
								<p>위에서 생성한 .OBJ 로더에는 이름 : 재질 쌍을 객체로 지정할 수 있습니다.
							 | 
						||
| 
								 | 
							
								.OBJ 파일을 불러올 때, 이름이 지정되었다면 로더에 지정한 재질 중에 이름(키)과
							 | 
						||
| 
								 | 
							
								일치하는 재질을 찾아 사용하고, 재질을 찾지 못했다면 기본 재질을 사용하죠.</p>
							 | 
						||
| 
								 | 
							
								<p>.OBJ 파일을 생성할 때 재질에 대한 데이터를 담은 .MTL 파일이 같이 생성되기도
							 | 
						||
| 
								 | 
							
								합니다. 방금의 경우에도 .MTL 파일이 같이 생성되었죠. MTL 파일은 ASCII 인코딩이므로
							 | 
						||
| 
								 | 
							
								일반 텍스트 파일처럼 열어볼 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-mtl" translate="no"># Blender MTL File: 'windmill_001.blend'
							 | 
						||
| 
								 | 
							
								# Material Count: 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								newmtl Material
							 | 
						||
| 
								 | 
							
								Ns 0.000000
							 | 
						||
| 
								 | 
							
								Ka 1.000000 1.000000 1.000000
							 | 
						||
| 
								 | 
							
								Kd 0.800000 0.800000 0.800000
							 | 
						||
| 
								 | 
							
								Ks 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ke 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ni 1.000000
							 | 
						||
| 
								 | 
							
								d 1.000000
							 | 
						||
| 
								 | 
							
								illum 1
							 | 
						||
| 
								 | 
							
								map_Kd windmill_001_lopatky_COL.jpg
							 | 
						||
| 
								 | 
							
								map_Bump windmill_001_lopatky_NOR.jpg
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								newmtl windmill
							 | 
						||
| 
								 | 
							
								Ns 0.000000
							 | 
						||
| 
								 | 
							
								Ka 1.000000 1.000000 1.000000
							 | 
						||
| 
								 | 
							
								Kd 0.800000 0.800000 0.800000
							 | 
						||
| 
								 | 
							
								Ks 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ke 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ni 1.000000
							 | 
						||
| 
								 | 
							
								d 1.000000
							 | 
						||
| 
								 | 
							
								illum 1
							 | 
						||
| 
								 | 
							
								map_Kd windmill_001_base_COL.jpg
							 | 
						||
| 
								 | 
							
								map_Bump windmill_001_base_NOR.jpg
							 | 
						||
| 
								 | 
							
								map_Ns windmill_001_base_SPEC.jpg
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>파일을 살펴보면 2개의 재질과 5개의 jpg 텍스처가 보이는데, 텍스처 파일은
							 | 
						||
| 
								 | 
							
								디렉토리 내에 보이지 않습니다. 대체 어디에 있는 걸까요?</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 757px;" src="../resources/images/windmill-exported-files.png"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>생성된 거라고는 .OBJ 파일 하나와 .MTL 파일 하나 뿐입니다.</p>
							 | 
						||
| 
								 | 
							
								<p>사실 방금 사용한 모델의 텍스처는 .blend 파일에 포함되어 있습니다.
							 | 
						||
| 
								 | 
							
								<strong>File->External Data->Unpack All Into Files</strong>를 선택하고</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 828px;" src="../resources/images/windmill-export-textures.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p><strong>Write Files to Current Directory</strong>를 선택해 텍스처를 별도 파일로
							 | 
						||
| 
								 | 
							
								내보낼 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 828px;" src="../resources/images/windmill-overwrite.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>이러면 .blend 파일과 같은 경로의 <strong>textures</strong> 폴더 안에 텍스처 파일이
							 | 
						||
| 
								 | 
							
								생성됩니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 758px;" src="../resources/images/windmill-exported-texture-files.png"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>내보낸 텍스처를 복사해 .OBJ 파일과 같은 경로에 두겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 757px;" src="../resources/images/windmill-exported-files-with-textures.png"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>이제 .MTL 파일에서 사용할 텍스처를 생성했으니 .MTL 파일을 불러오도록 합시다.</p>
							 | 
						||
| 
								 | 
							
								<p><a href="/docs/#examples/loaders/MTLLoader"><code class="notranslate" translate="no">MTLLoader</code></a> 모듈을 불러옵니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
							 | 
						||
| 
								 | 
							
								import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
							 | 
						||
| 
								 | 
							
								import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
							 | 
						||
| 
								 | 
							
								+import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>우선 .MTL 파일을 불러와 <code class="notranslate" translate="no">MtlObjBridge</code>로 재질을 만듭니다. 그리고 <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a>
							 | 
						||
| 
								 | 
							
								인스턴스에 방금 만든 재질을 추가한 뒤 .OBJ 파일을 불러옵니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						||
| 
								 | 
							
								+  const mtlLoader = new MTLLoader();
							 | 
						||
| 
								 | 
							
								+  mtlLoader.load('resources/models/windmill/windmill.mtl', (mtl) => {
							 | 
						||
| 
								 | 
							
								+    mtl.preload();
							 | 
						||
| 
								 | 
							
								+    objLoader.setMaterials(mtl);
							 | 
						||
| 
								 | 
							
								    objLoader.load('resources/models/windmill/windmill.obj', (root) => {
							 | 
						||
| 
								 | 
							
								      scene.add(root);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								+  });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<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/load-obj-materials.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/load-obj-materials.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>얼핏 제대로 불러온 것 같지만 아직 부족한 점이 있습니다. 모델을 이리저리
							 | 
						||
| 
								 | 
							
								회전시켜 보면 풍차의 날개 뒷면이 사라지는 것을 볼 수 있을 겁니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 528px;" src="../resources/images/windmill-missing-cloth.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p><a href="materials.html">재질에 관한 글</a>을 읽었다면 원인이 무엇인지 알
							 | 
						||
| 
								 | 
							
								겁니다. 일단 풍차의 날개 양면을 모두 렌더링하도록 설정해야 겠네요. .MTL
							 | 
						||
| 
								 | 
							
								파일을 직접 수정하기는 어렵습니다. 그렇다면 쉽게 떠올릴 수 있는 방법은
							 | 
						||
| 
								 | 
							
								3가지 정도죠.</p>
							 | 
						||
| 
								 | 
							
								<ol>
							 | 
						||
| 
								 | 
							
								<li><p>모든 재질을 불러온 뒤 반복문으로 처리한다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate notranslate" translate="no"> const mtlLoader = new MTLLoader();
							 | 
						||
| 
								 | 
							
								 mtlLoader.load('resources/models/windmill/windmill.mtl', (mtl) => {
							 | 
						||
| 
								 | 
							
								   mtl.preload();
							 | 
						||
| 
								 | 
							
								   for (const material of Object.values(mtl.materials)) {
							 | 
						||
| 
								 | 
							
								     material.side = THREE.DoubleSide;
							 | 
						||
| 
								 | 
							
								   }
							 | 
						||
| 
								 | 
							
								   ...
							 | 
						||
| 
								 | 
							
								</pre><p>문제가 해결되긴 하겠지만, 양면 렌더링은 단면 렌더링에 비해 성능이 느립니다.
							 | 
						||
| 
								 | 
							
								양면일 필요가 있는 재질만 양면으로 렌더링하는 게 이상적이겠죠.</p>
							 | 
						||
| 
								 | 
							
								</li>
							 | 
						||
| 
								 | 
							
								<li><p>특정 재질을 골라 설정한다.</p>
							 | 
						||
| 
								 | 
							
								<p>.MTL 파일에는 <code class="notranslate" translate="no">"windmill"</code>, <code class="notranslate" translate="no">"Material"</code> 2개의 재질이 있습니다. 여러 번의 시도와
							 | 
						||
| 
								 | 
							
								에러 끝에 날개가 <code class="notranslate" translate="no">"Material"</code>이라는 이름의 재질을 쓴다는 것을 알아낸 뒤, 이 재질에만
							 | 
						||
| 
								 | 
							
								양면 속성을 설정할 수도 있을 겁니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate notranslate" translate="no"> const mtlLoader = new MTLLoader();
							 | 
						||
| 
								 | 
							
								 mtlLoader.load('resources/models/windmill/windmill.mtl', (mtl) => {
							 | 
						||
| 
								 | 
							
								   mtl.preload();
							 | 
						||
| 
								 | 
							
								   mtl.materials.Material.side = THREE.DoubleSide;
							 | 
						||
| 
								 | 
							
								   ...
							 | 
						||
| 
								 | 
							
								</pre></li>
							 | 
						||
| 
								 | 
							
								<li><p>.MTL 파일의 한계에 굴복하고 직접 재질을 만든다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate notranslate" translate="no"> objLoader.load('resources/models/windmill/windmill.obj', (root) => {
							 | 
						||
| 
								 | 
							
								   const materials = {
							 | 
						||
| 
								 | 
							
								     Material: new THREE.MeshPhongMaterial({...}),
							 | 
						||
| 
								 | 
							
								     windmill: new THREE.MeshPhongMaterial({...}),
							 | 
						||
| 
								 | 
							
								   };
							 | 
						||
| 
								 | 
							
								   root.traverse(node => {
							 | 
						||
| 
								 | 
							
								     const material = materials[node.material?.name];
							 | 
						||
| 
								 | 
							
								     if (material) {
							 | 
						||
| 
								 | 
							
								       node.material = material;
							 | 
						||
| 
								 | 
							
								     }
							 | 
						||
| 
								 | 
							
								   })
							 | 
						||
| 
								 | 
							
								   scene.add(root);
							 | 
						||
| 
								 | 
							
								 });
							 | 
						||
| 
								 | 
							
								</pre></li>
							 | 
						||
| 
								 | 
							
								</ol>
							 | 
						||
| 
								 | 
							
								<p>뭘 선택하든 그건 여러분의 선택입니다. 1번이 가장 간단하고, 3번이 가장
							 | 
						||
| 
								 | 
							
								확장성이 좋죠. 2번은 그 중간입니다. 지금은 2번 해결책을 사용하도록 하죠.</p>
							 | 
						||
| 
								 | 
							
								<p>해결책을 적용하면 날개가 제대로 보일 겁니다. 하지만 문제가 하나 더 남았습니다.
							 | 
						||
| 
								 | 
							
								모델을 확대해보면 텍스처가 굉장히 각져 보일 거예요.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 700px;" src="../resources/images/windmill-blocky.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>뭐가 문제일까요?</p>
							 | 
						||
| 
								 | 
							
								<p>텍스처 파일 중에는 NOR, 법선 맵(NORmal map)이라는 이름이 붙은 파일이 있습니다.
							 | 
						||
| 
								 | 
							
								이 파일이 바로 법선 맵이죠. 범프 맵(bump map)이 흑백이라면 법선 맵은 보통
							 | 
						||
| 
								 | 
							
								자주색을 띱니다. 범프 맵이 표면의 높이를 나타낸다면 법선 맵은 표면의 방향을
							 | 
						||
| 
								 | 
							
								나타내죠.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 256px;" src="../examples/resources/models/windmill/windmill_001_base_NOR.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p><a href="https://github.com/mrdoob/three.js/blob/1a560a3426e24bbfc9ca1f5fb0dfb4c727d59046/examples/js/loaders/MTLLoader.js#L432">MTLLoader의 소스 코드</a>를
							 | 
						||
| 
								 | 
							
								살펴보면 법선 맵의 키(key)가 <code class="notranslate" translate="no">norm</code>이어야 한다고 합니다. 간단히 .MTL 파일을
							 | 
						||
| 
								 | 
							
								수정해보죠.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-mtl" translate="no"># Blender MTL File: 'windmill_001.blend'
							 | 
						||
| 
								 | 
							
								# Material Count: 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								newmtl Material
							 | 
						||
| 
								 | 
							
								Ns 0.000000
							 | 
						||
| 
								 | 
							
								Ka 1.000000 1.000000 1.000000
							 | 
						||
| 
								 | 
							
								Kd 0.800000 0.800000 0.800000
							 | 
						||
| 
								 | 
							
								Ks 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ke 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ni 1.000000
							 | 
						||
| 
								 | 
							
								d 1.000000
							 | 
						||
| 
								 | 
							
								illum 1
							 | 
						||
| 
								 | 
							
								map_Kd windmill_001_lopatky_COL.jpg
							 | 
						||
| 
								 | 
							
								-map_Bump windmill_001_lopatky_NOR.jpg
							 | 
						||
| 
								 | 
							
								+norm windmill_001_lopatky_NOR.jpg
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								newmtl windmill
							 | 
						||
| 
								 | 
							
								Ns 0.000000
							 | 
						||
| 
								 | 
							
								Ka 1.000000 1.000000 1.000000
							 | 
						||
| 
								 | 
							
								Kd 0.800000 0.800000 0.800000
							 | 
						||
| 
								 | 
							
								Ks 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ke 0.000000 0.000000 0.000000
							 | 
						||
| 
								 | 
							
								Ni 1.000000
							 | 
						||
| 
								 | 
							
								d 1.000000
							 | 
						||
| 
								 | 
							
								illum 1
							 | 
						||
| 
								 | 
							
								map_Kd windmill_001_base_COL.jpg
							 | 
						||
| 
								 | 
							
								-map_Bump windmill_001_base_NOR.jpg
							 | 
						||
| 
								 | 
							
								+norm windmill_001_base_NOR.jpg
							 | 
						||
| 
								 | 
							
								map_Ns windmill_001_base_SPEC.jpg
							 | 
						||
| 
								 | 
							
								</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/load-obj-materials-fixed.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/load-obj-materials-fixed.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>다른 파일도 불러와봅시다.</p>
							 | 
						||
| 
								 | 
							
								<p>인터넷을 뒤져 <a href="https://creativecommons.org/licenses/by-nc/4.0/">CC-BY-NC</a>
							 | 
						||
| 
								 | 
							
								풍차 3D 모델을 발견했습니다(작가: <a href="http://www.gerzi.ch/">Roger Gerzner / GERIZ.3D Art</a>).</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img src="../resources/images/windmill-obj-2.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>.OBJ 형식으로 다운 받을 수 있으므로, 해당 형식으로 받아 불러오겠습니다(잠깐
							 | 
						||
| 
								 | 
							
								.MTL 로더를 제거했습니다).</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-  objLoader.load('resources/models/windmill/windmill.obj', ...
							 | 
						||
| 
								 | 
							
								+  objLoader.load('resources/models/windmill-2/windmill.obj', ...
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<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/load-obj-wat.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/load-obj-wat.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>음, 아무것도 나타나지 않습니다. 뭐가 문제일까요? 모델의 원래 크기 때문일까요?
							 | 
						||
| 
								 | 
							
								Three.js로부터 모델 사이즈를 구해 카메라를 한 번 업데이트해보겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>먼저 Three.js가 방금 불러온 모델을 감싸는 육면체를 계산해 모델의 크기와 중심점을
							 | 
						||
| 
								 | 
							
								구하는 코드를 작성합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">objLoader.load('resources/models/windmill_2/windmill.obj', (root) => {
							 | 
						||
| 
								 | 
							
								  scene.add(root);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+  const box = new THREE.Box3().setFromObject(root);
							 | 
						||
| 
								 | 
							
								+  const boxSize = box.getSize(new THREE.Vector3()).length();
							 | 
						||
| 
								 | 
							
								+  const boxCenter = box.getCenter(new THREE.Vector3());
							 | 
						||
| 
								 | 
							
								+  console.log(boxSize);
							 | 
						||
| 
								 | 
							
								+  console.log(boxCenter);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p><a href="debugging-javascript.html">자바스크립트 콘솔</a>을 확인해보면 아래와
							 | 
						||
| 
								 | 
							
								같은 결과가 보일 겁니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">size 2123.6499788469982
							 | 
						||
| 
								 | 
							
								center p { x: -0.00006103515625, y: 770.0909731090069, z: -3.313507080078125 }
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이 카메라는 현재 <code class="notranslate" translate="no">near</code> 0.1, <code class="notranslate" translate="no">far</code>가 100 이므로 약 100칸 정도를 투사합니다.
							 | 
						||
| 
								 | 
							
								땅도 40x40칸인데 이 모델은 2000칸이죠. 카메라의 시야보다 훨씬 크니 절두체 영역
							 | 
						||
| 
								 | 
							
								밖에 있는 게 당연합니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 280px;" src="../resources/images/camera-inside-windmill.svg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>수작업으로 고칠 수도 있지만, 카메라가 장면의 크기를 자동으로 감지하도록 만들어보겠습니다.
							 | 
						||
| 
								 | 
							
								방금 모델의 크기를 구할 때 썼던 육면체를 이용하면 되겠네요. 카메라의 위치를 정하는 데
							 | 
						||
| 
								 | 
							
								<em>정해진</em> 방법은 없습니다. 경우에 따라 카메라의 방향과 위치가 다르니 그때 그때 상황에
							 | 
						||
| 
								 | 
							
								맞춰 방법을 찾아야 하죠.</p>
							 | 
						||
| 
								 | 
							
								<p><a href="cameras.html">카메라에 관해 배운 내용</a>을 떠올려봅시다. 카메라를 만들려면
							 | 
						||
| 
								 | 
							
								절두체를 정의해야 하죠. 절두체는 <code class="notranslate" translate="no">fov(시야각, field of view)</code>, <code class="notranslate" translate="no">near</code>, <code class="notranslate" translate="no">far</code> 속성을
							 | 
						||
| 
								 | 
							
								지정해 정의합니다. 시야각이 얼마이든, 절두체가 무한히 늘어난다고 가정할 때, 장면을
							 | 
						||
| 
								 | 
							
								둘러싼 육면체가 절두체 안에 들어오게 하려면 카메라를 얼마나 멀리 보내야 할까요? 그러니까
							 | 
						||
| 
								 | 
							
								<code class="notranslate" translate="no">near</code>가 0.00000001이고 <code class="notranslate" translate="no">far</code>가 무한대라 가정했을 때 말이죠.</p>
							 | 
						||
| 
								 | 
							
								<p>다행히 시야각과 육면체의 크기를 아니 다음 그림과 같은 삼각형을 사용할 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 600px;" src="../resources/images/camera-fit-scene.svg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>그림에서 왼쪽은 카메라이고, 카메라에서 뻗어나온 파란 절두체가 풍차를 투사합니다.
							 | 
						||
| 
								 | 
							
								방금 풍차를 둘러싼 육면체의 위치값을 계산했죠. 이제 얼마나 카메라를 멀리 보내야
							 | 
						||
| 
								 | 
							
								육면체가 절두체 안에 들어올지 계산해야 합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>절두체의 시야각과 육면체의 크기를 구했으니, 기본 삼각함수와 <a href="https://www.google.com/search?q=SOHCAHTOA">*SOHCAHTOA</a>를
							 | 
						||
| 
								 | 
							
								이용해 카메라와 육면체의 <em>거리(distance)</em>를 구할 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>※ SOH-CAH-TOA: 한국에서 삼각함수를 배울 때 얼싸안코와 비슷한 식으로 외우듯,
							 | 
						||
| 
								 | 
							
								영미권에도 삼각함수를 배울 때 Sin = Opposite(대변) 나누기 Hypotenuse(빗변),
							 | 
						||
| 
								 | 
							
								Cos = Adjacent(밑변) 나누기 Hypotenuse, Tan = Oppsite 나누기 Adjacent와 같은
							 | 
						||
| 
								 | 
							
								식으로 외웁니다. 이를 줄여서 SOH-CAH-TOA(소-카-토아)라고 부릅니다. 역주.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 600px;" src="../resources/images/field-of-view-camera.svg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>그림을 기반으로 계산식을 짜보겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">distance = halfSizeToFitOnScreen / tangent(halfFovY) // 거리 = 화면 크기의 반 / 탄젠트(시야각의 절반)
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이제 위 계산식을 코드로 옮겨야 합니다. 먼저 <code class="notranslate" translate="no">distance(거리)</code>를 구한 뒤
							 | 
						||
| 
								 | 
							
								카메라를 육면체의 중심에서 <code class="notranslate" translate="no">distance</code>값만큼 옮깁니다. 그리고 카메라가 육면체의
							 | 
						||
| 
								 | 
							
								<code class="notranslate" translate="no">center(중심)</code>을 바라보게 설정합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
							 | 
						||
| 
								 | 
							
								  const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
							 | 
						||
| 
								 | 
							
								  const halfFovY = THREE.MathUtils.degToRad(camera.fov * .5);
							 | 
						||
| 
								 | 
							
								  const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // 육면체의 중심에서 카메라가 있는 곳으로 향하는 방향 벡터를 계산합니다
							 | 
						||
| 
								 | 
							
								  const direction = (new THREE.Vector3()).subVectors(camera.position, boxCenter).normalize();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // 방향 벡터에 따라 카메라를 육면체로부터 일정 거리에 위치시킵니다
							 | 
						||
| 
								 | 
							
								  camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // 육면체를 투사할 절두체를 near와 far값으로 정의합니다
							 | 
						||
| 
								 | 
							
								  camera.near = boxSize / 100;
							 | 
						||
| 
								 | 
							
								  camera.far = boxSize * 100;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  camera.updateProjectionMatrix();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // 카메라가 육면체의 중심을 바라보게 합니다
							 | 
						||
| 
								 | 
							
								  camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이 함수는 <code class="notranslate" translate="no">boxSize</code>와 <code class="notranslate" translate="no">sizeToFitOnScreen</code>, 두 개의 크기값을 매개변수로 받습니다.
							 | 
						||
| 
								 | 
							
								<code class="notranslate" translate="no">boxSize</code> 값으로 <code class="notranslate" translate="no">sizeToFitOnScreen</code> 값을 대체할 수도 있지만, 이러면 육면체가
							 | 
						||
| 
								 | 
							
								화면에 꽉 차게 됩니다. 조금 여유가 있는 편이 보기 편하므로 조금 더 큰 값을 넘겨주도록
							 | 
						||
| 
								 | 
							
								하겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						||
| 
								 | 
							
								  const objLoader = new OBJLoader();
							 | 
						||
| 
								 | 
							
								  objLoader.load('resources/models/windmill_2/windmill.obj', (root) => {
							 | 
						||
| 
								 | 
							
								    scene.add(root);
							 | 
						||
| 
								 | 
							
								+    // 모든 요소를 포함하는 육면체를 계산합니다
							 | 
						||
| 
								 | 
							
								+    const box = new THREE.Box3().setFromObject(root);
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    const boxSize = box.getSize(new THREE.Vector3()).length();
							 | 
						||
| 
								 | 
							
								+    const boxCenter = box.getCenter(new THREE.Vector3());
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    // 카메라가 육면체를 완전히 감싸도록 설정합니다
							 | 
						||
| 
								 | 
							
								+    frameArea(boxSize * 1.2, boxSize, boxCenter, camera);
							 | 
						||
| 
								 | 
							
								+
							 | 
						||
| 
								 | 
							
								+    // 마우스 휠 이벤트가 큰 크기에 대응하도록 업데이트합니다
							 | 
						||
| 
								 | 
							
								+    controls.maxDistance = boxSize * 10;
							 | 
						||
| 
								 | 
							
								+    controls.target.copy(boxCenter);
							 | 
						||
| 
								 | 
							
								+    controls.update();
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>위 예제에서는 <code class="notranslate" translate="no">boxSize * 1.2</code> 값을 넘겨주어 20% 정도 빈 공간을 더 만들었습니다.
							 | 
						||
| 
								 | 
							
								또 카메라가 장면의 중심을 기준으로 회전하도록 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>도 업데이트했죠.</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/load-obj-auto-camera.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/load-obj-auto-camera.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>성공했습니다. 마우스로 장면을 드래그하면 풍차가 보일 거예요. 하지만 카메라가 풍차의
							 | 
						||
| 
								 | 
							
								정면이 아닌 아래쪽을 먼저 보여줍니다. 이는 풍차가 너무 커서 육면체의 중심이 약
							 | 
						||
| 
								 | 
							
								(0, 770, 0)인데, 카메라를 육면체의 중심에서 기존 위치 (0, 10, 20) 방향으로 <code class="notranslate" translate="no">distance</code>만큼
							 | 
						||
| 
								 | 
							
								옮겼기에 풍차의 아래쪽에 카메라가 위치하게 된 것입니다.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 360px;" src="../resources/images/computed-camera-position.svg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>카메라의 기존 위치에 상관없이 육면체의 중심을 기준으로 카메라를 배치해보겠습니다.
							 | 
						||
| 
								 | 
							
								단순히 카메라와 육면체 간 벡터의 <code class="notranslate" translate="no">y</code> 요소를 0으로 만들면 됩니다. <code class="notranslate" translate="no">y</code> 요소를 0으로
							 | 
						||
| 
								 | 
							
								만든 뒤 벡터를 정규화(normalize)하면, XZ 면에 평행한 벡터, 그러니까 바닥에 평행한
							 | 
						||
| 
								 | 
							
								벡터가 되겠죠.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-// 육면체의 중심에서 카메라가 있는 곳으로 향하는 방향 벡터를 계산합니다
							 | 
						||
| 
								 | 
							
								-const direction = (new THREE.Vector3()).subVectors(camera.position, boxCenter).normalize();
							 | 
						||
| 
								 | 
							
								+// 카메라와 육면체 사이의 방향 벡터를 항상 XZ 면에 평행하게 만듭니다
							 | 
						||
| 
								 | 
							
								+const direction = (new THREE.Vector3())
							 | 
						||
| 
								 | 
							
								+    .subVectors(camera.position, boxCenter)
							 | 
						||
| 
								 | 
							
								+    .multiply(new THREE.Vector3(1, 0, 1))
							 | 
						||
| 
								 | 
							
								+    .normalize();
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>풍차의 아랫면을 보면 작은 정사각형이 하나 보일 겁니다. 원래 땅으로 썼던 평면이죠.</p>
							 | 
						||
| 
								 | 
							
								<div class="threejs_center"><img style="width: 365px;" src="../resources/images/tiny-ground-plane.jpg"></div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p>원래 땅은 40x40칸이었으니 풍차에 비해 훨씬 작은 것이 당연합니다. 풍차의 크기는
							 | 
						||
| 
								 | 
							
								2000칸이 넘습니다. 땅을 풍차에 맞게 키워야 겠네요. 또 크기만 키우면 체크무늬가
							 | 
						||
| 
								 | 
							
								너무 작아 확대하지 않는 한 보기가 어려울 테니 체스무늬 한 칸의 크기도 키우겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const planeSize = 40;
							 | 
						||
| 
								 | 
							
								+const planeSize = 4000;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const loader = new THREE.TextureLoader();
							 | 
						||
| 
								 | 
							
								const texture = loader.load('resources/images/checker.png');
							 | 
						||
| 
								 | 
							
								texture.wrapS = THREE.RepeatWrapping;
							 | 
						||
| 
								 | 
							
								texture.wrapT = THREE.RepeatWrapping;
							 | 
						||
| 
								 | 
							
								texture.magFilter = THREE.NearestFilter;
							 | 
						||
| 
								 | 
							
								-const repeats = planeSize / 2;
							 | 
						||
| 
								 | 
							
								+const repeats = planeSize / 200;
							 | 
						||
| 
								 | 
							
								texture.repeat.set(repeats, repeats);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<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/load-obj-auto-camera-xz.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/load-obj-auto-camera-xz.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>이제 재질을 다시 붙여봅시다. 이전 모델과 마찬가지로 텍스처에 대한 데이터를 담은
							 | 
						||
| 
								 | 
							
								.MTL 파일이 보입니다. 하지만 동시에 다른 문제도 보이네요.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-shell" translate="no"> $ ls -l windmill
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff       299 May 20  2009 windmill.mtl
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff    142989 May 20  2009 windmill.obj
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff  12582956 Apr 19  2009 windmill_diffuse.tga
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff  12582956 Apr 20  2009 windmill_normal.tga
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff  12582956 Apr 19  2009 windmill_spec.tga
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>어마어마하게 큰 TARGA (.tga) 파일이 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>THREE.js에 TGA 로더가 있기는 하나 대부분의 경우 이를 사용하는 건 좋지 않습니다.
							 | 
						||
| 
								 | 
							
								아주 소수의 경우, 예를 들어 사용자가 임의의 3D 모델 파일을 불러와 확인할 수 있는
							 | 
						||
| 
								 | 
							
								뷰어를 만든다거나 하는 경우라면 TGA 파일을 사용할 수도 있죠.(<a href="#loading-scenes">*</a>)</p>
							 | 
						||
| 
								 | 
							
								<p>TGA 파일의 문제점 중 하나는 압축을 거의 하지 않는다는 점입니다. TGA는 아주 간단한
							 | 
						||
| 
								 | 
							
								압축만 지원하죠. 파일의 크기가 모두 같을 확률은 매우 희박하니, 위 파일들은 아예
							 | 
						||
| 
								 | 
							
								압축이 되지 않았다고 볼 수 있습니다. 게다가 파일 하나당 무려 12 메가바이트!! 저
							 | 
						||
| 
								 | 
							
								파일을 그대로 사용한다면 사용자는 풍차 하나를 보기 위해 36MB의 데이터를 다운받아야
							 | 
						||
| 
								 | 
							
								하는 셈이 됩니다.</p>
							 | 
						||
| 
								 | 
							
								<p>또한 브라우저가 TGA를 지원하지 않기에, .JPG나 .PNG 파일보다 로딩 시간이 훨씬 느릴
							 | 
						||
| 
								 | 
							
								겁니다.</p>
							 | 
						||
| 
								 | 
							
								<p>확신하건데, 이 경우 .JPG 파일로 변환하는 게 가장 좋은 선택입니다. TGA 파일은 알파값이
							 | 
						||
| 
								 | 
							
								없는 RGB 3개의 채널로 구성되죠. JPG도 채널 3개만 사용하니 딱 적당합니다. 또 JPG는 손실
							 | 
						||
| 
								 | 
							
								압축을 사용하기에 파일 용량을 훨씬 많이 줄일 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>파일을 열어보니 각각 해상도가 2048x2048입니다. 쓰기에 따라 다르겠지만, 저는 이게 다소
							 | 
						||
| 
								 | 
							
								낭비라는 생각에 해상도를 1024x1024로 낯추고 포토샵의 퀄리티 설정을 50%로 지정했습니다.
							 | 
						||
| 
								 | 
							
								다시 파일 구조를 살펴보죠.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-shell" translate="no"> $ ls -l ../threejs.org/manual/examples/resources/models/windmill
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff     299 May 20  2009 windmill.mtl
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff  142989 May 20  2009 windmill.obj
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff  259927 Nov  7 18:37 windmill_diffuse.jpg
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff   98013 Nov  7 18:38 windmill_normal.jpg
							 | 
						||
| 
								 | 
							
								 -rw-r--r--@ 1 gregg  staff  191864 Nov  7 18:39 windmill_spec.jpg
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>36MB에서 0.55MB가 되었네요! 물론 디자너이너의 생각은 다를 수 있으니 절충안을
							 | 
						||
| 
								 | 
							
								찾기 전에 상의를 하는 것이 좋습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>이제 .MTL 파일을 열어 .TGA 파일 경로를 .JPG 파일로 바꿉니다. 다행히 .MTL 파일은
							 | 
						||
| 
								 | 
							
								텍스트라 수정이 어렵지 않습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">newmtl blinn1SG
							 | 
						||
| 
								 | 
							
								Ka 0.10 0.10 0.10
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Kd 0.00 0.00 0.00
							 | 
						||
| 
								 | 
							
								Ks 0.00 0.00 0.00
							 | 
						||
| 
								 | 
							
								Ke 0.00 0.00 0.00
							 | 
						||
| 
								 | 
							
								Ns 0.060000
							 | 
						||
| 
								 | 
							
								Ni 1.500000
							 | 
						||
| 
								 | 
							
								d 1.000000
							 | 
						||
| 
								 | 
							
								Tr 0.000000
							 | 
						||
| 
								 | 
							
								Tf 1.000000 1.000000 1.000000 
							 | 
						||
| 
								 | 
							
								illum 2
							 | 
						||
| 
								 | 
							
								-map_Kd windmill_diffuse.tga
							 | 
						||
| 
								 | 
							
								+map_Kd windmill_diffuse.jpg
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								-map_Ks windmill_spec.tga
							 | 
						||
| 
								 | 
							
								+map_Ks windmill_spec.jpg
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								-map_bump windmill_normal.tga 
							 | 
						||
| 
								 | 
							
								-bump windmill_normal.tga 
							 | 
						||
| 
								 | 
							
								+map_bump windmill_normal.jpg 
							 | 
						||
| 
								 | 
							
								+bump windmill_normal.jpg
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>텍스처의 용량을 최적화했으니 이제 불러올 일만 남았습니다. 먼저 아까 했던 것처럼
							 | 
						||
| 
								 | 
							
								재질을 불러와 <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a>에 지정합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
							 | 
						||
| 
								 | 
							
								+  const mtlLoader = new MTLLoader();
							 | 
						||
| 
								 | 
							
								+  mtlLoader.load('resources/models/windmill_2/windmill-fixed.mtl', (mtl) => {
							 | 
						||
| 
								 | 
							
								+    mtl.preload();
							 | 
						||
| 
								 | 
							
								+    const objLoader = new OBJLoader();
							 | 
						||
| 
								 | 
							
								+    objLoader.setMaterials(mtl);
							 | 
						||
| 
								 | 
							
								    objLoader.load('resources/models/windmill/windmill.obj', (root) => {
							 | 
						||
| 
								 | 
							
								      root.updateMatrixWorld();
							 | 
						||
| 
								 | 
							
								      scene.add(root);
							 | 
						||
| 
								 | 
							
								      // 모든 요소를 포함하는 육면체를 계산합니다
							 | 
						||
| 
								 | 
							
								      const box = new THREE.Box3().setFromObject(root);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const boxSize = box.getSize(new THREE.Vector3()).length();
							 | 
						||
| 
								 | 
							
								      const boxCenter = box.getCenter(new THREE.Vector3());
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // 카메라가 육면체를 완전히 감싸도록 설정합니다
							 | 
						||
| 
								 | 
							
								      frameArea(boxSize * 1.2, boxSize, boxCenter, camera);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // 마우스 휠 이벤트가 큰 크기에 대응하도록 업데이트합니다
							 | 
						||
| 
								 | 
							
								      controls.maxDistance = boxSize * 10;
							 | 
						||
| 
								 | 
							
								      controls.target.copy(boxCenter);
							 | 
						||
| 
								 | 
							
								      controls.update();
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								+  });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>결과를 확인했는데 문제가 발생했습니다. 여러분에게 직접 보여주기보다 하나하나
							 | 
						||
| 
								 | 
							
								짚어보도록 하죠.</p>
							 | 
						||
| 
								 | 
							
								<p>문제 #1: 3개의 <a href="/docs/#examples/loaders/MTLLoader"><code class="notranslate" translate="no">MTLLoader</code></a>가 각각 디퓨즈(diffuse) 색과 디퓨즈 텍스처 맵으로
							 | 
						||
| 
								 | 
							
								혼합하는 재질을 만듬.</p>
							 | 
						||
| 
								 | 
							
								<p>이는 유용한 기능이지만, .MTL 파일의 디퓨즈 색상은 0입니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">Kd 0.00 0.00 0.00
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>(텍스처 맵 * 0 = 검정)이죠. 모델링 프로그램에서는 디퓨즈 텍스처 맵과 디퓨즈 색을 혼합하지
							 | 
						||
| 
								 | 
							
								않아도 모델이 제대로 보입니다. 이 풍차를 만든 디자이너 입장에서는 이 파일이 문제가
							 | 
						||
| 
								 | 
							
								없다고 생각하는 것이 당연하죠.</p>
							 | 
						||
| 
								 | 
							
								<p>.MTL 파일을 다음과 같이 수정해 문제를 해결할 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">Kd 1.00 1.00 1.00
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>(텍스처 맵 * 1 = 텍스처 맵)이니까요.</p>
							 | 
						||
| 
								 | 
							
								<p>문제 #2: 스페큘러(specular) 색이 검정임.</p>
							 | 
						||
| 
								 | 
							
								<p><code class="notranslate" translate="no">Ks</code>로 시작하는 줄은 스페큘러 색을 나타냅니다. 이 역시 디자이너가 사용한 모델링
							 | 
						||
| 
								 | 
							
								프로그램이 디퓨즈 맵처럼 뭔가 다른 처리를 해주었을 겁니다. Three.js는 스페큘러
							 | 
						||
| 
								 | 
							
								색을 얼마나 많이 반사할지 결정할 때 스페큘러 맵의 빨강(red) 채널만 사용하기는
							 | 
						||
| 
								 | 
							
								하나, 3가지 색상 채널을 모두 지정하긴 해야 합니다.</p>
							 | 
						||
| 
								 | 
							
								<p>디퓨즈 색과 마찬가지로 .MTL 파일을 다음과 같이 수정하겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">-Ks 0.00 0.00 0.00
							 | 
						||
| 
								 | 
							
								+Ks 1.00 1.00 1.00
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>문제 #3: <code class="notranslate" translate="no">windmill_normal.jpg</code>가 법선 맵이 아닌 범프 맵임.</p>
							 | 
						||
| 
								 | 
							
								<p>마찬가지로 .MTL 파일을 수정해줍니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">-map_bump windmill_normal.jpg 
							 | 
						||
| 
								 | 
							
								-bump windmill_normal.jpg 
							 | 
						||
| 
								 | 
							
								+norm windmill_normal.jpg
							 | 
						||
| 
								 | 
							
								</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/load-obj-materials-windmill2.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/load-obj-materials-windmill2.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>모델을 불러올 때 주의해야 하는 점을 몇 가지만 적어보겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<ul>
							 | 
						||
| 
								 | 
							
								<li><p>크기를 알아야 한다</p>
							 | 
						||
| 
								 | 
							
								<p>예제에서는 카메라가 장면 전체를 감싸도록 했지만, 이게 항상 최적의 해결책이 될 수는 없습니다.
							 | 
						||
| 
								 | 
							
								직접 모델을 만들거나, 모델을 다운받아 3D 프로그램으로 크기를 조절하는 것이 더 이상적인 방법입니다.</p>
							 | 
						||
| 
								 | 
							
								</li>
							 | 
						||
| 
								 | 
							
								<li><p>잘못된 방향축</p>
							 | 
						||
| 
								 | 
							
								<p>Three.js에서는 보통 Y축이 위쪽입니다. 모델링 프로그램에서는 Z축이 위쪽인 경우, Y축이 위쪽인 경우, 직접
							 | 
						||
| 
								 | 
							
								설정할 수 있는 경우 등 경우가 다양하죠. 모델을 불러왔는데 방향이 잘못되었다면, 모델을 불러온 후 방향을
							 | 
						||
| 
								 | 
							
								바꾸거나(권장하지 않음), 3D 프로그램이나 커맨드 라인 프로그램으로 모델을 원하는 방향으로 맞출 수 있습니다.
							 | 
						||
| 
								 | 
							
								브라우저에서 이미지를 쓸 때와 마찬가지로, 이미지를 수정하는 코드를 넣는 것보다는 이미지를 다운받아 이미지
							 | 
						||
| 
								 | 
							
								자체를 편집하는 게 더 나을 겁니다. 블렌더에서는 아예 파일을 내보낼 때 방향을 바꿀 수 있습니다.</p>
							 | 
						||
| 
								 | 
							
								</li>
							 | 
						||
| 
								 | 
							
								<li><p>.MTL 파일이 없거나 재질 또는 지원하지 않는 값이 있는 경우</p>
							 | 
						||
| 
								 | 
							
								<p>위 예제를 만들 때 .MTL 파일 덕에 재질을 만드는 수고는 덜었지만, 몇 가지 문제가 있었습니다. 문제를 해결하기
							 | 
						||
| 
								 | 
							
								위해 직접 .MTL 파일을 수정했고요. 파일을 열어 .OBJ 파일 안에 어떤 재질이 있는지 확인하거나, Three.js로
							 | 
						||
| 
								 | 
							
								.OBJ 파일을 불러와 재질을 전부 출력하도록 하는 것은 꽤 자주 있는 일입니다. 그런 후에 .MTL 파일 대신 직접
							 | 
						||
| 
								 | 
							
								재질을 만들어 적절한 이름/재질 쌍의 객체로 로더에 넘겨주거나, 장면을 렌더링한 뒤 테스트하면서 문제를 수정하는
							 | 
						||
| 
								 | 
							
								것이죠.</p>
							 | 
						||
| 
								 | 
							
								</li>
							 | 
						||
| 
								 | 
							
								<li><p>고용량 텍스처</p>
							 | 
						||
| 
								 | 
							
								<p>3D 모델은 주로 건축, 영화나 광고, 게임 등에서 사용합니다. 건축이나 영화 같은 분야라면 텍스처의 용량을
							 | 
						||
| 
								 | 
							
								신경 쓸 필요는 없죠. 반면 게임의 경우는 메모리도 제한적이고 로컬 환경에서 구동되기에 신경을 꽤 써야 합니다.
							 | 
						||
| 
								 | 
							
								웹 페이지의 경우는 빠르게 불러와야 하니 용량이 퀄리티가 너무 떨어지지 않는 선에서 최대한 작은 게 좋죠.
							 | 
						||
| 
								 | 
							
								첫 번째로 쓴 풍차의 경우, 실제로 사용하려면 텍스처를 손볼 필요가 있습니다. 지금은 총 용량이 무려 10MB가
							 | 
						||
| 
								 | 
							
								넘거든요!!!</p>
							 | 
						||
| 
								 | 
							
								<p>또한 <a href="textures.html">텍스처에 관한 글</a>에서 말했듯, 텍스처의 해상도도 고려해야 합니다. 50KB짜리
							 | 
						||
| 
								 | 
							
								4096x4096 JPG 이미지는 불러오는 속도는 빠를지 몰라도 굉장히 많은 메모리를 차지할 테니까요.</p>
							 | 
						||
| 
								 | 
							
								</li>
							 | 
						||
| 
								 | 
							
								</ul>
							 | 
						||
| 
								 | 
							
								<p>마지막으로 풍차가 돌아가는 것을 보여주고 싶지만, .OBJ 파일에는 계층 구조가 없습니다. 다시 말해 풍차의 모든
							 | 
						||
| 
								 | 
							
								요소를 기본적으로 1개의 mesh로 취급한다는 것이죠. 풍차의 날개를 건물에서 분리할 수 없기에 날개를 회전시킬
							 | 
						||
| 
								 | 
							
								수가 없습니다.</p>
							 | 
						||
| 
								 | 
							
								<p>이런 이유로 .OBJ는 그다지 좋은 파일 형식이라고 하기 어렵습니다. 추측하건데 .OBJ 형식을 자주 사용하는 이유는
							 | 
						||
| 
								 | 
							
								사용법이 간단하고, 복잡한 기능이 필요 없는 경우가 많기 때문일 겁니다. 예를 들어 건축 디자인을 하는 경우,
							 | 
						||
| 
								 | 
							
								대부분 애니메이션이 필요 없기에 장면에 정적 요소를 추가하는 게 더 좋을 수 있죠.</p>
							 | 
						||
| 
								 | 
							
								<p>.gLTF는 .OBJ보다 더 많은 기능을 지원합니다. 다음 글에서는 이 gLTF 장면을 불러오는 법에 대해 알아보겠습니다.</p>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        </div>
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								    </div>
							 | 
						||
| 
								 | 
							
								  
							 | 
						||
| 
								 | 
							
								  <script src="/manual/resources/prettify.js"></script>
							 | 
						||
| 
								 | 
							
								  <script src="/manual/resources/lesson.js"></script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</body></html>
							 |