load-obj.html 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>Loading a .OBJ File</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js – Loading a .OBJ File">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. </head>
  21. <body>
  22. <div class="container">
  23. <div class="lesson-title">
  24. <h1>Loading a .OBJ File</h1>
  25. </div>
  26. <div class="lesson">
  27. <div class="lesson-main">
  28. <p>One of the most common things people want to do with three.js
  29. is to load and display 3D models. A common format is the .OBJ
  30. 3D format so let's try loading one.</p>
  31. <p>Searching the net I found <a href="https://www.blendswap.com/blends/view/69174">this CC-BY-NC 3.0 windmill 3D model</a> by <a href="https://www.blendswap.com/user/ahedov">ahedov</a>.</p>
  32. <div class="threejs_center"><img src="../resources/images/windmill-obj.jpg"></div>
  33. <p>I downloaded the .blend file from that site, loaded it into <a href="https://blender.org">Blender</a>
  34. and exported it as an .OBJ file.</p>
  35. <div class="threejs_center"><img style="width: 827px;" src="../resources/images/windmill-export-as-obj.jpg"></div>
  36. <blockquote>
  37. <p>Note: If you've never used Blender you might be in for a surprise
  38. in that Blender does things differently than just about every
  39. other program you've ever used. Just be aware you might need to
  40. set aside some time to read some basic UI navigation for Blender.</p>
  41. <p>Let me also add that 3D programs in general are giant beasts with
  42. 1000s of features. They are some of the most complicated software there
  43. is. When I first learned 3D Studio Max in 1996 I read through 70% of the
  44. 600 page manual spending a few hours a day for around 3 weeks. That paid
  45. off in that when I learned Maya a few years later some of the lessons
  46. learned before were applicable to Maya. So, just be aware that if you
  47. really want to be able to use 3D software to either build 3D assets
  48. or to modify existing ones put it on your schedule and clear sometime
  49. to really go through some lessons.</p>
  50. </blockquote>
  51. <p>In any case I used these export options</p>
  52. <div class="threejs_center"><img style="width: 239px;" src="../resources/images/windmill-export-options.jpg"></div>
  53. <p>Let's try to display it!</p>
  54. <p>I started with the directional lighting example from
  55. <a href="lights.html">the lights article</a> and I combined it with
  56. the hemispherical lighting example so I ended up with one
  57. <a href="/docs/#api/en/lights/HemisphereLight"><code class="notranslate" translate="no">HemisphereLight</code></a> and one <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a>. I also removed all the GUI stuff
  58. related to adjusting the lights. I also removed the cube and sphere
  59. that were being added to the scene.</p>
  60. <p>From that the first thing we need to do is include the <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a> loader in our script.</p>
  61. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {OBJLoader} from 'three/addons/loaders/OBJLoader.js';
  62. </pre>
  63. <p>Then to load the .OBJ file we create an instance of <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a>,
  64. pass it the URL of our .OBJ file, and pass in a callback that adds
  65. the loaded model to our scene.</p>
  66. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  67. const objLoader = new OBJLoader();
  68. objLoader.load('resources/models/windmill/windmill.obj', (root) =&gt; {
  69. scene.add(root);
  70. });
  71. }
  72. </pre>
  73. <p>If we run that what happens?</p>
  74. <p></p><div translate="no" class="threejs_example_container notranslate">
  75. <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>
  76. <a class="threejs_center" href="/manual/examples/load-obj-no-materials.html" target="_blank">click here to open in a separate window</a>
  77. </div>
  78. <p></p>
  79. <p>Well it's close but we're getting errors about materials since we haven't
  80. given the scene any materials and .OBJ files don't have material
  81. parameters. </p>
  82. <p>The .OBJ loader can be passed an
  83. object of name / material pairs. When it loads the .OBJ file,
  84. any material name it finds it will look for the corresponding material
  85. in the map of materials set on the loader. If it finds a
  86. material that matches by name it will use that material. If
  87. not it will use the loader's default material.</p>
  88. <p>Sometimes .OBJ files come with a .MTL file that defines
  89. materials. In our case the exporter also created a .MTL file.
  90. .MTL format is plain ASCII so it's easy to look at. Looking at it here</p>
  91. <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no"># Blender MTL File: 'windmill_001.blend'
  92. # Material Count: 2
  93. newmtl Material
  94. Ns 0.000000
  95. Ka 1.000000 1.000000 1.000000
  96. Kd 0.800000 0.800000 0.800000
  97. Ks 0.000000 0.000000 0.000000
  98. Ke 0.000000 0.000000 0.000000
  99. Ni 1.000000
  100. d 1.000000
  101. illum 1
  102. map_Kd windmill_001_lopatky_COL.jpg
  103. map_Bump windmill_001_lopatky_NOR.jpg
  104. newmtl windmill
  105. Ns 0.000000
  106. Ka 1.000000 1.000000 1.000000
  107. Kd 0.800000 0.800000 0.800000
  108. Ks 0.000000 0.000000 0.000000
  109. Ke 0.000000 0.000000 0.000000
  110. Ni 1.000000
  111. d 1.000000
  112. illum 1
  113. map_Kd windmill_001_base_COL.jpg
  114. map_Bump windmill_001_base_NOR.jpg
  115. map_Ns windmill_001_base_SPEC.jpg
  116. </pre>
  117. <p>We can see there are 2 materials referencing 5 jpg textures
  118. but where are the texture files?</p>
  119. <div class="threejs_center"><img style="width: 757px;" src="../resources/images/windmill-exported-files.png"></div>
  120. <p>All we got was an .OBJ file and an .MTL file.</p>
  121. <p>At least for this model it turns out the textures are embedded
  122. in the .blend file we downloaded. We can ask blender to
  123. export those files to by picking <strong>File-&gt;External Data-&gt;Unpack All Into Files</strong></p>
  124. <div class="threejs_center"><img style="width: 828px;" src="../resources/images/windmill-export-textures.jpg"></div>
  125. <p>and then choosing <strong>Write Files to Current Directory</strong></p>
  126. <div class="threejs_center"><img style="width: 828px;" src="../resources/images/windmill-overwrite.jpg"></div>
  127. <p>This ends up writing the files in the same folder as the .blend file
  128. in a sub folder called <strong>textures</strong>.</p>
  129. <div class="threejs_center"><img style="width: 758px;" src="../resources/images/windmill-exported-texture-files.png"></div>
  130. <p>I copied those textures into the same folder I exported the .OBJ
  131. file to.</p>
  132. <div class="threejs_center"><img style="width: 757px;" src="../resources/images/windmill-exported-files-with-textures.png"></div>
  133. <p>Now that we have the textures available we can load the .MTL file.</p>
  134. <p>First we need to include the <a href="/docs/#examples/loaders/MTLLoader"><code class="notranslate" translate="no">MTLLoader</code></a>;</p>
  135. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
  136. import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
  137. import {OBJLoader} from 'three/addons/loaders/OBJLoader.js';
  138. +import {MTLLoader} from 'three/addons/loaders/MTLLoader.js';
  139. </pre>
  140. <p>Then we first load the .MTL file. When it's finished loading we add
  141. the just loaded materials on to the <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a> itself via the <code class="notranslate" translate="no">setMaterials</code>
  142. and then load the .OBJ file.</p>
  143. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  144. + const mtlLoader = new MTLLoader();
  145. + mtlLoader.load('resources/models/windmill/windmill.mtl', (mtl) =&gt; {
  146. + mtl.preload();
  147. + objLoader.setMaterials(mtl);
  148. objLoader.load('resources/models/windmill/windmill.obj', (root) =&gt; {
  149. scene.add(root);
  150. });
  151. + });
  152. }
  153. </pre>
  154. <p>And if we try that...</p>
  155. <p></p><div translate="no" class="threejs_example_container notranslate">
  156. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-obj-materials.html"></iframe></div>
  157. <a class="threejs_center" href="/manual/examples/load-obj-materials.html" target="_blank">click here to open in a separate window</a>
  158. </div>
  159. <p></p>
  160. <p>Note that if we spin the model around you'll see the windmill cloth
  161. disappears</p>
  162. <div class="threejs_center"><img style="width: 528px;" src="../resources/images/windmill-missing-cloth.jpg"></div>
  163. <p>We need the material on the blades to be double sided, something
  164. we went over in <a href="materials.html">the article on materials</a>.
  165. There is no easy way to fix this in the .MTL file. Off the top of my
  166. head I can think of 3 ways to fix this.</p>
  167. <ol>
  168. <li><p>Loop over all the materials after loading them and set them all to double sided.</p>
  169. <pre class="prettyprint showlinemods notranslate notranslate" translate="no"> const mtlLoader = new MTLLoader();
  170. mtlLoader.load('resources/models/windmill/windmill.mtl', (mtl) =&gt; {
  171. mtl.preload();
  172. for (const material of Object.values(mtl.materials)) {
  173. material.side = THREE.DoubleSide;
  174. }
  175. ...
  176. </pre><p>This solution works but ideally we only want materials that need
  177. to be double sided to be double sided because drawing double sided
  178. is slower than single sided.</p>
  179. </li>
  180. <li><p>Manually set a specific material</p>
  181. <p>Looking in the .MTL file there are 2 materials. One called <code class="notranslate" translate="no">"windmill"</code>
  182. and the other called <code class="notranslate" translate="no">"Material"</code>. Through trial and error I figured
  183. out the blades use the material called <code class="notranslate" translate="no">"Material"</code>so we could set
  184. that one specifically </p>
  185. <pre class="prettyprint showlinemods notranslate notranslate" translate="no"> const mtlLoader = new MTLLoader();
  186. mtlLoader.load('resources/models/windmill/windmill.mtl', (mtl) =&gt; {
  187. mtl.preload();
  188. mtl.materials.Material.side = THREE.DoubleSide;
  189. ...
  190. </pre></li>
  191. <li><p>Realizing that the .MTL file is limited we could just not use it
  192. and instead create materials ourselves.</p>
  193. <p>In this case we'd need to look up the <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> object after
  194. loading the obj file.</p>
  195. <pre class="prettyprint showlinemods notranslate notranslate" translate="no"> objLoader.load('resources/models/windmill/windmill.obj', (root) =&gt; {
  196. const materials = {
  197. Material: new THREE.MeshPhongMaterial({...}),
  198. windmill: new THREE.MeshPhongMaterial({...}),
  199. };
  200. root.traverse(node =&gt; {
  201. const material = materials[node.material?.name];
  202. if (material) {
  203. node.material = material;
  204. }
  205. })
  206. scene.add(root);
  207. });
  208. </pre></li>
  209. </ol>
  210. <p>Which one you pick is up to you. 1 is easiest. 3 is most flexible.
  211. 2 somewhere in between. For now I'll pick 2.</p>
  212. <p>And with that change you should still see the cloth on the blades
  213. when looking from behind but there's one more issue. If we zoom in close
  214. we see things are turning blocky.</p>
  215. <div class="threejs_center"><img style="width: 700px;" src="../resources/images/windmill-blocky.jpg"></div>
  216. <p>What's going on?</p>
  217. <p>Looking at the textures there are 2 textures labelled NOR for NORmal map.
  218. And looking at them they look like normal maps. Normal maps are generally
  219. purple where as bump maps are black and white. Normal maps represent
  220. the direction of the surface where as bump maps represent the height of
  221. the surface.</p>
  222. <div class="threejs_center"><img style="width: 256px;" src="../examples/resources/models/windmill/windmill_001_base_NOR.jpg"></div>
  223. <p>Looking at <a href="https://github.com/mrdoob/three.js/blob/1a560a3426e24bbfc9ca1f5fb0dfb4c727d59046/examples/js/loaders/MTLLoader.js#L432">the source for the MTLLoader</a>
  224. it expects the keyword <code class="notranslate" translate="no">norm</code> for normal maps so let's edit the .MTL file</p>
  225. <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no"># Blender MTL File: 'windmill_001.blend'
  226. # Material Count: 2
  227. newmtl Material
  228. Ns 0.000000
  229. Ka 1.000000 1.000000 1.000000
  230. Kd 0.800000 0.800000 0.800000
  231. Ks 0.000000 0.000000 0.000000
  232. Ke 0.000000 0.000000 0.000000
  233. Ni 1.000000
  234. d 1.000000
  235. illum 1
  236. map_Kd windmill_001_lopatky_COL.jpg
  237. -map_Bump windmill_001_lopatky_NOR.jpg
  238. +norm windmill_001_lopatky_NOR.jpg
  239. newmtl windmill
  240. Ns 0.000000
  241. Ka 1.000000 1.000000 1.000000
  242. Kd 0.800000 0.800000 0.800000
  243. Ks 0.000000 0.000000 0.000000
  244. Ke 0.000000 0.000000 0.000000
  245. Ni 1.000000
  246. d 1.000000
  247. illum 1
  248. map_Kd windmill_001_base_COL.jpg
  249. -map_Bump windmill_001_base_NOR.jpg
  250. +norm windmill_001_base_NOR.jpg
  251. map_Ns windmill_001_base_SPEC.jpg
  252. </pre>
  253. <p>and now when we load it it will be using the normal maps as normal maps and
  254. we can see the back of the blades.</p>
  255. <p></p><div translate="no" class="threejs_example_container notranslate">
  256. <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>
  257. <a class="threejs_center" href="/manual/examples/load-obj-materials-fixed.html" target="_blank">click here to open in a separate window</a>
  258. </div>
  259. <p></p>
  260. <p>Let's load a different file.</p>
  261. <p>Searching the net I found this <a href="https://creativecommons.org/licenses/by-nc/4.0/">CC-BY-NC</a> windmill 3D model made by <a href="http://www.gerzi.ch/">Roger Gerzner / GERIZ.3D Art</a>.</p>
  262. <div class="threejs_center"><img src="../resources/images/windmill-obj-2.jpg"></div>
  263. <p>It had a .OBJ version already available. Let's load it up (note I removed the .MTL loader for now)</p>
  264. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">- objLoader.load('resources/models/windmill/windmill.obj', ...
  265. + objLoader.load('resources/models/windmill-2/windmill.obj', ...
  266. </pre>
  267. <p></p><div translate="no" class="threejs_example_container notranslate">
  268. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-obj-wat.html"></iframe></div>
  269. <a class="threejs_center" href="/manual/examples/load-obj-wat.html" target="_blank">click here to open in a separate window</a>
  270. </div>
  271. <p></p>
  272. <p>Hmmm, nothing appears. What's the problem? I wonder what size the model is?
  273. We can ask THREE.js what size the model is and try to set our
  274. camera automatically.</p>
  275. <p>First off we can ask THREE.js to compute a box that contains the scene
  276. we just loaded and ask for its size and center</p>
  277. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">objLoader.load('resources/models/windmill_2/windmill.obj', (root) =&gt; {
  278. scene.add(root);
  279. + const box = new THREE.Box3().setFromObject(root);
  280. + const boxSize = box.getSize(new THREE.Vector3()).length();
  281. + const boxCenter = box.getCenter(new THREE.Vector3());
  282. + console.log(boxSize);
  283. + console.log(boxCenter);
  284. </pre>
  285. <p>Looking in <a href="debugging-javascript.html">the JavaScript console</a> I see</p>
  286. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">size 2123.6499788469982
  287. center p {x: -0.00006103515625, y: 770.0909731090069, z: -3.313507080078125}
  288. </pre>
  289. <p>Our camera is currently only showing about 100 units with <code class="notranslate" translate="no">near</code> at 0.1 and <code class="notranslate" translate="no">far</code> at 100.
  290. Our ground plane is only 40 units across so basically this windmill model is so big, 2000 units,
  291. that it's surrounding our camera and all parts of it our outside our frustum.</p>
  292. <div class="threejs_center"><img style="width: 280px;" src="../resources/images/camera-inside-windmill.svg"></div>
  293. <p>We could manually fix that but we could also make the camera auto frame our scene.
  294. Let's try that. We can then use the box we just computed adjust the camera settings to
  295. view the entire scene. Note that there is no <em>right</em> answer
  296. on where to put the camera. We could be facing the scene from any direction at any
  297. altitude so we'll just have to pick something.</p>
  298. <p>As we went over in <a href="cameras.html">the article on cameras</a> the camera defines a frustum.
  299. That frustum is defined by the field of view (<code class="notranslate" translate="no">fov</code>) and the <code class="notranslate" translate="no">near</code> and <code class="notranslate" translate="no">far</code> settings. We
  300. want to know given whatever field of view the camera currently has, how far away does the camera
  301. need to be so the box containing the scene fits inside the frustum assuming the frustum
  302. extended forever. In other words let's assume <code class="notranslate" translate="no">near</code> is 0.00000001 and <code class="notranslate" translate="no">far</code> is infinity.</p>
  303. <p>Since we know the size of the box and we know the field of view we have this triangle</p>
  304. <div class="threejs_center"><img style="width: 600px;" src="../resources/images/camera-fit-scene.svg"></div>
  305. <p>You can see on the left is the camera and the blue frustum is projecting out in
  306. front of it. We just computed the box that contains the the windmill. We need to
  307. compute how far way the camera should be from the box so that the box appears
  308. inside the frustum.</p>
  309. <p>Using basic <em>right triangle</em> trigonometry and <a href="https://www.google.com/search?q=SOHCAHTOA">SOHCAHTOA</a>,
  310. given we know the field of view for the frustum and we know the size of the box we can compute the <em>distance</em>.</p>
  311. <div class="threejs_center"><img style="width: 600px;" src="../resources/images/field-of-view-camera.svg"></div>
  312. <p>Based on that diagram the formula for computing distance is</p>
  313. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">distance = halfSizeToFitOnScreen / tangent(halfFovY)
  314. </pre>
  315. <p>Let's translate that to code. First let's make a function that will compute <code class="notranslate" translate="no">distance</code> and then move the
  316. camera that <code class="notranslate" translate="no">distance</code> units from the center of the box. We'll then point the
  317. camera at the <code class="notranslate" translate="no">center</code> of the box.</p>
  318. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
  319. const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
  320. const halfFovY = THREE.MathUtils.degToRad(camera.fov * .5);
  321. const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
  322. // compute a unit vector that points in the direction the camera is now
  323. // from the center of the box
  324. const direction = (new THREE.Vector3()).subVectors(camera.position, boxCenter).normalize();
  325. // move the camera to a position distance units way from the center
  326. // in whatever direction the camera was from the center already
  327. camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
  328. // pick some near and far values for the frustum that
  329. // will contain the box.
  330. camera.near = boxSize / 100;
  331. camera.far = boxSize * 100;
  332. camera.updateProjectionMatrix();
  333. // point the camera to look at the center of the box
  334. camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
  335. }
  336. </pre>
  337. <p>We pass in 2 sizes. The <code class="notranslate" translate="no">boxSize</code> and the <code class="notranslate" translate="no">sizeToFitOnScreen</code>. If we just passed in <code class="notranslate" translate="no">boxSize</code>
  338. and used that as <code class="notranslate" translate="no">sizeToFitOnScreen</code> then the math would make the box fit perfectly inside
  339. the frustum. We want a little extra space above and below so we'll pass in a slightly
  340. larger size. </p>
  341. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  342. const objLoader = new OBJLoader();
  343. objLoader.load('resources/models/windmill_2/windmill.obj', (root) =&gt; {
  344. scene.add(root);
  345. + // compute the box that contains all the stuff
  346. + // from root and below
  347. + const box = new THREE.Box3().setFromObject(root);
  348. +
  349. + const boxSize = box.getSize(new THREE.Vector3()).length();
  350. + const boxCenter = box.getCenter(new THREE.Vector3());
  351. +
  352. + // set the camera to frame the box
  353. + frameArea(boxSize * 1.2, boxSize, boxCenter, camera);
  354. +
  355. + // update the Trackball controls to handle the new size
  356. + controls.maxDistance = boxSize * 10;
  357. + controls.target.copy(boxCenter);
  358. + controls.update();
  359. });
  360. }
  361. </pre>
  362. <p>You can see above we pass in <code class="notranslate" translate="no">boxSize * 1.2</code> to give us 20% more space above and below the box when trying
  363. to fit it inside the frustum. We also updated the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a> so the camera will orbit the center
  364. of the scene.</p>
  365. <p>Now if we try that we get...</p>
  366. <p></p><div translate="no" class="threejs_example_container notranslate">
  367. <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>
  368. <a class="threejs_center" href="/manual/examples/load-obj-auto-camera.html" target="_blank">click here to open in a separate window</a>
  369. </div>
  370. <p></p>
  371. <p>This almost works. Use the mouse to rotate the camera and you
  372. should see the windmill. The problem is the windmill is large and the box's center is at about (0, 770, 0). So, when we move the camera from where it
  373. starts (0, 10, 20) to <code class="notranslate" translate="no">distance</code> units way from the center in the direction the camera
  374. is relative to the center that's moving the camera almost straight down below
  375. the windmill.</p>
  376. <div class="threejs_center"><img style="width: 360px;" src="../resources/images/computed-camera-position.svg"></div>
  377. <p>Let's change it to move sideways from the center of the box to in whatever direction
  378. the camera is from the center. All we need to do to do that is zero out the <code class="notranslate" translate="no">y</code> component
  379. of the vector from the box to the camera. Then, when we normalize that vector it will
  380. become a vector parallel to the XZ plane. In other words parallel to the ground.</p>
  381. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-// compute a unit vector that points in the direction the camera is now
  382. -// from the center of the box
  383. -const direction = (new THREE.Vector3()).subVectors(camera.position, boxCenter).normalize();
  384. +// compute a unit vector that points in the direction the camera is now
  385. +// in the xz plane from the center of the box
  386. +const direction = (new THREE.Vector3())
  387. + .subVectors(camera.position, boxCenter)
  388. + .multiply(new THREE.Vector3(1, 0, 1))
  389. + .normalize();
  390. </pre>
  391. <p>If you look at the bottom of the windmill you'll see a small square. That is our ground
  392. plane. </p>
  393. <div class="threejs_center"><img style="width: 365px;" src="../resources/images/tiny-ground-plane.jpg"></div>
  394. <p>It's only 40x40 units and so is way too small relative to the windmill.
  395. Since the windmill is over 2000 units big let's change the size of the ground plane to
  396. something more fitting. We also need to adjust the repeat otherwise our checkerboard
  397. will be so fine we won't even be able to see it unless we zoom way way in.</p>
  398. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const planeSize = 40;
  399. +const planeSize = 4000;
  400. const loader = new THREE.TextureLoader();
  401. const texture = loader.load('resources/images/checker.png');
  402. texture.wrapS = THREE.RepeatWrapping;
  403. texture.wrapT = THREE.RepeatWrapping;
  404. texture.magFilter = THREE.NearestFilter;
  405. -const repeats = planeSize / 2;
  406. +const repeats = planeSize / 200;
  407. texture.repeat.set(repeats, repeats);
  408. </pre>
  409. <p>and now we can see this windmill</p>
  410. <p></p><div translate="no" class="threejs_example_container notranslate">
  411. <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>
  412. <a class="threejs_center" href="/manual/examples/load-obj-auto-camera-xz.html" target="_blank">click here to open in a separate window</a>
  413. </div>
  414. <p></p>
  415. <p>Let's add the materials back. Like before there is a .MTL file that references
  416. some textures but looking at the files I quickly see an issue.</p>
  417. <pre class="prettyprint showlinemods notranslate lang-shell" translate="no"> $ ls -l windmill
  418. -rw-r--r--@ 1 gregg staff 299 May 20 2009 windmill.mtl
  419. -rw-r--r--@ 1 gregg staff 142989 May 20 2009 windmill.obj
  420. -rw-r--r--@ 1 gregg staff 12582956 Apr 19 2009 windmill_diffuse.tga
  421. -rw-r--r--@ 1 gregg staff 12582956 Apr 20 2009 windmill_normal.tga
  422. -rw-r--r--@ 1 gregg staff 12582956 Apr 19 2009 windmill_spec.tga
  423. </pre>
  424. <p>There are TARGA (.tga) files and they are giant!</p>
  425. <p>THREE.js actually has a TGA loader but it's arguably wrong to use it for most use cases.
  426. If you're making a viewer where you want to allow users to view random 3D files they
  427. find on the net then maybe, just maybe, you might want to load TGA files. (<a href="#loading-scenes">*</a>)</p>
  428. <p>One problem with TGA files are they can't be compressed well at all. TGA only supports very
  429. simple compression and looking above we can see the files are not compressed at all
  430. as the odds of them being all exactly the same size are extremely low. Further they
  431. are 12 megabytes each!!! If we used those files the user would have to download 36meg
  432. to see the windmill.</p>
  433. <p>Another issue with TGA is the browser itself has no support for them so loading them
  434. is likely going to be slower than loading supported formats like .JPG and .PNG</p>
  435. <p>I'm pretty sure for our purposes converting them to .JPG will be the best option.
  436. Looking inside I see they are 3 channels each, RGB, there is no alpha channel. JPG
  437. only supports 3 channels so that's a good fit. JPG also supports lossy compression
  438. so we can make the files much smaller to download</p>
  439. <p>Loading the files up they were each 2048x2048. That seemed like a waste to me but of
  440. course it depends on your use case. I made them each 1024x1024 and saved them at a
  441. 50% quality setting in Photoshop. Getting a file listing</p>
  442. <pre class="prettyprint showlinemods notranslate lang-shell" translate="no"> $ ls -l ../threejs.org/manual/examples/resources/models/windmill
  443. -rw-r--r--@ 1 gregg staff 299 May 20 2009 windmill.mtl
  444. -rw-r--r--@ 1 gregg staff 142989 May 20 2009 windmill.obj
  445. -rw-r--r--@ 1 gregg staff 259927 Nov 7 18:37 windmill_diffuse.jpg
  446. -rw-r--r--@ 1 gregg staff 98013 Nov 7 18:38 windmill_normal.jpg
  447. -rw-r--r--@ 1 gregg staff 191864 Nov 7 18:39 windmill_spec.jpg
  448. </pre>
  449. <p>We went from 36meg to 0.55meg! Of course the artist might not be pleased
  450. with this compression so be sure to consult with them to discuss the tradeoffs.</p>
  451. <p>Now, to use the .MTL file we need to edit it to reference the .JPG files
  452. instead of the .TGA files. Fortunately it's a simple text file so it's easy to edit</p>
  453. <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">newmtl blinn1SG
  454. Ka 0.10 0.10 0.10
  455. Kd 0.00 0.00 0.00
  456. Ks 0.00 0.00 0.00
  457. Ke 0.00 0.00 0.00
  458. Ns 0.060000
  459. Ni 1.500000
  460. d 1.000000
  461. Tr 0.000000
  462. Tf 1.000000 1.000000 1.000000
  463. illum 2
  464. -map_Kd windmill_diffuse.tga
  465. +map_Kd windmill_diffuse.jpg
  466. -map_Ks windmill_spec.tga
  467. +map_Ks windmill_spec.jpg
  468. -map_bump windmill_normal.tga
  469. -bump windmill_normal.tga
  470. +map_bump windmill_normal.jpg
  471. +bump windmill_normal.jpg
  472. </pre>
  473. <p>Now that the .MTL file points to some reasonable size textures we need to load it so we'll just do like we did above, first load the materials
  474. and then set them on the <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a></p>
  475. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  476. + const mtlLoader = new MTLLoader();
  477. + mtlLoader.load('resources/models/windmill_2/windmill-fixed.mtl', (mtl) =&gt; {
  478. + mtl.preload();
  479. + const objLoader = new OBJLoader();
  480. + objLoader.setMaterials(mtl);
  481. objLoader.load('resources/models/windmill/windmill.obj', (root) =&gt; {
  482. root.updateMatrixWorld();
  483. scene.add(root);
  484. // compute the box that contains all the stuff
  485. // from root and below
  486. const box = new THREE.Box3().setFromObject(root);
  487. const boxSize = box.getSize(new THREE.Vector3()).length();
  488. const boxCenter = box.getCenter(new THREE.Vector3());
  489. // set the camera to frame the box
  490. frameArea(boxSize * 1.2, boxSize, boxCenter, camera);
  491. // update the Trackball controls to handle the new size
  492. controls.maxDistance = boxSize * 10;
  493. controls.target.copy(boxCenter);
  494. controls.update();
  495. });
  496. + });
  497. }
  498. </pre>
  499. <p>Before we actually try it out I ran into some issues that rather than show a failure I'm just going to go over them.</p>
  500. <p>Issue #1: The three <a href="/docs/#examples/loaders/MTLLoader"><code class="notranslate" translate="no">MTLLoader</code></a> creates materials that multiply the material's diffuse color by the diffuse texture map.</p>
  501. <p>That's a useful feature but looking a the .MTL file above the line</p>
  502. <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">Kd 0.00 0.00 0.00
  503. </pre>
  504. <p>sets the diffuse color to 0. Texture map * 0 = black! It's possible the modeling tool used to make the windmill
  505. did not multiply the diffuse texture map by the diffuse color. That's why it worked for the artists that made this windmill.</p>
  506. <p>To fix this we can change the line to</p>
  507. <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">Kd 1.00 1.00 1.00
  508. </pre>
  509. <p>since Texture Map * 1 = Texture Map.</p>
  510. <p>Issue #2: The specular color is also black</p>
  511. <p>The line that starts with <code class="notranslate" translate="no">Ks</code> specifies the specular color. It's likely the modeling software used to make the windmill
  512. did something similar as it did with diffuse maps in that it used the specular map's color for specular highlights.
  513. Three.js uses only the red channel of a specular map as input to how much of the specular color to reflect but three still
  514. needs a specular color set.</p>
  515. <p>Like above we can fix that by editing the .MTL file like this.</p>
  516. <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">-Ks 0.00 0.00 0.00
  517. +Ks 1.00 1.00 1.00
  518. </pre>
  519. <p>Issue #3: The <code class="notranslate" translate="no">windmill_normal.jpg</code> is a normal map not a bump map.</p>
  520. <p>Just like above we just need to edit the .MTL file</p>
  521. <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">-map_bump windmill_normal.jpg
  522. -bump windmill_normal.jpg
  523. +norm windmill_normal.jpg
  524. </pre>
  525. <p>Given all that if we now try it out it should load up with materials.</p>
  526. <p></p><div translate="no" class="threejs_example_container notranslate">
  527. <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>
  528. <a class="threejs_center" href="/manual/examples/load-obj-materials-windmill2.html" target="_blank">click here to open in a separate window</a>
  529. </div>
  530. <p></p>
  531. <p>Loading models often runs into these kinds of issues. Common issues include:</p>
  532. <ul>
  533. <li><p>Needing to know the size</p>
  534. <p>Above we made the camera try to frame the scene but that's not always the appropriate thing to do. Generally the most appropriate thing
  535. to do is to make your own models or download the models, load them up in some 3D software and look at their scale and adjust if need be.</p>
  536. </li>
  537. <li><p>Orientation Wrong</p>
  538. <p>THREE.js is generally Y = up. Some modeling packages default to Z = up, some Y = up. Some are settable.
  539. If you run into this case where you load a model and it's on its side. You can either hack your code to rotate the model after loading (not recommended),
  540. or you can load the model into your favorite modeling package or use some command line tools to rotate the object in the orientation you need it to be
  541. just like you'd edit an image for your website rather than download it and apply code to adjust it. Blender even has options when you export to
  542. change the orientation.</p>
  543. </li>
  544. <li><p>No .MTL file or wrong materials or incompatible parameters</p>
  545. <p>Above we used a .MTL file above which helped us load materials but there were issues. We manually edited the .MTL file to fix.
  546. It's also common to look inside the .OBJ file to see what materials there are, or to load the .OBJ file in THREE.js and walk the
  547. scene and print out all the materials. Then, go modify the code to make custom materials and assign them where appropriate either
  548. by making a name/material pair object to pass to the loader instead of loading the .MTL file, OR, after the scene has loaded, walking the
  549. scene and fixing things.</p>
  550. </li>
  551. <li><p>Textures too large</p>
  552. <p>Most 3D models are made for either architecture, movies and commercials, or
  553. games. For architecture and movies no one really cares about the size
  554. of the textures since. For games people care because games have limited
  555. memory but most games run locally. Webpages though you want to load
  556. as fast as possible and so you need to look at the textures and try
  557. to make them as small as possible and still look good. In fact the first windmill we should arguably done something about
  558. the textures. They are currently a total of 10meg!!!</p>
  559. <p>Also remember
  560. like we mentioned in the <a href="textures.html">article on textures</a> that
  561. textures take memory so a 50k JPG that expands to 4096x4096 will download
  562. fast but still take a ton of memory.</p>
  563. </li>
  564. </ul>
  565. <p>The last thing I wanted to show is spinning the windmills. Unfortunately, .OBJ files have no hierarchy. That means all parts of each
  566. windmill are basically considered 1 single mesh. You can't spin the blades of the mill as they aren't separated from the rest of the building.</p>
  567. <p>This is one of the main reasons why .OBJ is not really a good format. If I was to guess, the reason it's more common than other formats
  568. is because it's simple and doesn't support many features it works more often than not. Especially if you're making something still like
  569. an architectural image and there's no need to animate anything it's not a bad way to get static props into a scene.</p>
  570. <p>Next up we'll try <a href="load-gltf.html">loading a gLTF scene</a>. The gLTF format supports many more features.</p>
  571. </div>
  572. </div>
  573. </div>
  574. <script src="../resources/prettify.js"></script>
  575. <script src="../resources/lesson.js"></script>
  576. </body></html>