load-gltf.html 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>Loading a .GLTF 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 .GLTF 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. <!-- Import maps polyfill -->
  14. <!-- Remove this when import maps will be widely supported -->
  15. <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
  16. <script type="importmap">
  17. {
  18. "imports": {
  19. "three": "../../build/three.module.js"
  20. }
  21. }
  22. </script>
  23. </head>
  24. <body>
  25. <div class="container">
  26. <div class="lesson-title">
  27. <h1>Loading a .GLTF File</h1>
  28. </div>
  29. <div class="lesson">
  30. <div class="lesson-main">
  31. <p>In a previous lesson we <a href="load-obj.html">loaded an .OBJ file</a>. If
  32. you haven't read it you might want to check it out first.</p>
  33. <p>As pointed out over there the .OBJ file format is very old and fairly
  34. simple. It provides no scene graph so everything loaded is one large
  35. mesh. It was designed mostly as a simple way to pass data between
  36. 3D editors.</p>
  37. <p><a href="https://github.com/KhronosGroup/glTF">The gLTF format</a> is actually
  38. a format designed from the ground up for be used for displaying
  39. graphics. 3D formats can be divided into 3 or 4 basic types.</p>
  40. <ul>
  41. <li><p>3D Editor Formats</p>
  42. <p>This are formats specific to a single app. .blend (Blender), .max (3d Studio Max),
  43. .mb and .ma (Maya), etc...</p>
  44. </li>
  45. <li><p>Exchange formats</p>
  46. <p>These are formats like .OBJ, .DAE (Collada), .FBX. They are designed to help exchange
  47. information between 3D editors. As such they are usually much larger than needed with
  48. extra info used only inside 3d editors</p>
  49. </li>
  50. <li><p>App formats</p>
  51. <p>These are usually specific to certain apps, usually games.</p>
  52. </li>
  53. <li><p>Transmission formats</p>
  54. <p>gLTF might be the first true transmission format. I suppose VRML might be considered
  55. one but VRML was actually a pretty poor format.</p>
  56. <p>gLTF is designed to do some things well that all those other formats don't do</p>
  57. <ol>
  58. <li><p>Be small for transmission</p>
  59. <p>For example this means much of their large data, like vertices, is stored in
  60. binary. When you download a .gLTF file that data can be uploaded to the GPU
  61. with zero processing. It's ready as is. This is in contrast to say VRML, .OBJ,
  62. or .DAE where vertices are stored as text and have to be parsed. Text vertex
  63. positions can easily be 3x to 5x larger than binary.</p>
  64. </li>
  65. <li><p>Be ready to render</p>
  66. <p>This again is different from other formats except maybe App formats. The data
  67. in a glTF file is mean to be rendered, not edited. Data that's not important to
  68. rendering has generally been removed. Polygons have been converted to triangles.
  69. Materials have known values that are supposed to work everywhere.</p>
  70. </li>
  71. </ol>
  72. </li>
  73. </ul>
  74. <p>gLTF was specifically designed so you should be able to download a glTF file and
  75. display it with a minimum of trouble. Let's cross our fingers that's truly the case
  76. as none of the other formats have been able to do this.</p>
  77. <p>I wasn't really sure what I should show. At some level loading and displaying a gLTF file
  78. is simpler than an .OBJ file. Unlike a .OBJ file materials are directly part of the format.
  79. That said I thought I should at least load one up and I think going over the issues I ran
  80. into might provide some good info.</p>
  81. <p>Searching the net I found <a href="https://sketchfab.com/models/edd1c604e1e045a0a2a552ddd9a293e6">this low-poly city</a>
  82. by <a href="https://sketchfab.com/antonmoek">antonmoek</a> which seemed like if we're lucky
  83. might make a good example.</p>
  84. <div class="threejs_center"><img src="../resources/images/cartoon_lowpoly_small_city_free_pack.jpg"></div>
  85. <p>Starting with <a href="load-obj.html">an example from the .OBJ article</a> I removed the code
  86. for loading .OBJ and replaced it with code for loading .GLTF</p>
  87. <p>The old .OBJ code was</p>
  88. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mtlLoader = new MTLLoader();
  89. mtlLoader.loadMtl('resources/models/windmill/windmill-fixed.mtl', (mtl) =&gt; {
  90. mtl.preload();
  91. mtl.materials.Material.side = THREE.DoubleSide;
  92. objLoader.setMaterials(mtl);
  93. objLoader.load('resources/models/windmill/windmill.obj', (event) =&gt; {
  94. const root = event.detail.loaderRootNode;
  95. scene.add(root);
  96. ...
  97. });
  98. });
  99. </pre>
  100. <p>The new .GLTF code is</p>
  101. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  102. const gltfLoader = new GLTFLoader();
  103. const url = 'resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf';
  104. gltfLoader.load(url, (gltf) =&gt; {
  105. const root = gltf.scene;
  106. scene.add(root);
  107. ...
  108. });
  109. </pre>
  110. <p>I kept the auto framing code as before</p>
  111. <p>We also need to include the <a href="/docs/#examples/loaders/GLTFLoader"><code class="notranslate" translate="no">GLTFLoader</code></a> and we can get rid of the <a href="/docs/#examples/loaders/OBJLoader"><code class="notranslate" translate="no">OBJLoader</code></a>.</p>
  112. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">-import {LoaderSupport} from 'three/addons/loaders/LoaderSupport.js';
  113. -import {OBJLoader} from 'three/addons/loaders/OBJLoader.js';
  114. -import {MTLLoader} from 'three/addons/loaders/MTLLoader.js';
  115. +import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
  116. </pre>
  117. <p>And running that we get</p>
  118. <p></p><div translate="no" class="threejs_example_container notranslate">
  119. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf.html"></iframe></div>
  120. <a class="threejs_center" href="/manual/examples/load-gltf.html" target="_blank">click here to open in a separate window</a>
  121. </div>
  122. <p></p>
  123. <p>Magic! It just works, textures and all.</p>
  124. <p>Next I wanted to see if I could animate the cars driving around so
  125. I needed to check if the scene had the cars as separate entities
  126. and if they were setup in a way I could use them.</p>
  127. <p>I wrote some code to dump put the scenegraph to the <a href="debugging-javascript.html">JavaScript
  128. console</a>.</p>
  129. <p>Here's the code to print out the scenegraph.</p>
  130. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function dumpObject(obj, lines = [], isLast = true, prefix = '') {
  131. const localPrefix = isLast ? '└─' : '├─';
  132. lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
  133. const newPrefix = prefix + (isLast ? ' ' : '│ ');
  134. const lastNdx = obj.children.length - 1;
  135. obj.children.forEach((child, ndx) =&gt; {
  136. const isLast = ndx === lastNdx;
  137. dumpObject(child, lines, isLast, newPrefix);
  138. });
  139. return lines;
  140. }
  141. </pre>
  142. <p>And I just called it right after loading the scene.</p>
  143. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gltfLoader = new GLTFLoader();
  144. gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
  145. const root = gltf.scene;
  146. scene.add(root);
  147. console.log(dumpObject(root).join('\n'));
  148. </pre>
  149. <p><a href="../examples/load-gltf-dump-scenegraph.html">Running that</a> I got this listing</p>
  150. <pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
  151. └─RootNode_(gltf_orientation_matrix) [Object3D]
  152. └─RootNode_(model_correction_matrix) [Object3D]
  153. └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
  154. └─RootNode [Object3D]
  155. │ ...
  156. ├─Cars [Object3D]
  157. │ ├─CAR_03_1 [Object3D]
  158. │ │ └─CAR_03_1_World_ap_0 [Mesh]
  159. │ ├─CAR_03 [Object3D]
  160. │ │ └─CAR_03_World_ap_0 [Mesh]
  161. │ ├─Car_04 [Object3D]
  162. │ │ └─Car_04_World_ap_0 [Mesh]
  163. │ ├─CAR_03_2 [Object3D]
  164. │ │ └─CAR_03_2_World_ap_0 [Mesh]
  165. │ ├─Car_04_1 [Object3D]
  166. │ │ └─Car_04_1_World_ap_0 [Mesh]
  167. │ ├─Car_04_2 [Object3D]
  168. │ │ └─Car_04_2_World_ap_0 [Mesh]
  169. │ ├─Car_04_3 [Object3D]
  170. │ │ └─Car_04_3_World_ap_0 [Mesh]
  171. │ ├─Car_04_4 [Object3D]
  172. │ │ └─Car_04_4_World_ap_0 [Mesh]
  173. │ ├─Car_08_4 [Object3D]
  174. │ │ └─Car_08_4_World_ap8_0 [Mesh]
  175. │ ├─Car_08_3 [Object3D]
  176. │ │ └─Car_08_3_World_ap9_0 [Mesh]
  177. │ ├─Car_04_1_2 [Object3D]
  178. │ │ └─Car_04_1_2_World_ap_0 [Mesh]
  179. │ ├─Car_08_2 [Object3D]
  180. │ │ └─Car_08_2_World_ap11_0 [Mesh]
  181. │ ├─CAR_03_1_2 [Object3D]
  182. │ │ └─CAR_03_1_2_World_ap_0 [Mesh]
  183. │ ├─CAR_03_2_2 [Object3D]
  184. │ │ └─CAR_03_2_2_World_ap_0 [Mesh]
  185. │ ├─Car_04_2_2 [Object3D]
  186. │ │ └─Car_04_2_2_World_ap_0 [Mesh]
  187. ...
  188. </pre>
  189. <p>From that we can see all the cars happen to be under a parent
  190. called <code class="notranslate" translate="no">"Cars"</code></p>
  191. <pre class="prettyprint showlinemods notranslate lang-text" translate="no">* ├─Cars [Object3D]
  192. │ ├─CAR_03_1 [Object3D]
  193. │ │ └─CAR_03_1_World_ap_0 [Mesh]
  194. │ ├─CAR_03 [Object3D]
  195. │ │ └─CAR_03_World_ap_0 [Mesh]
  196. │ ├─Car_04 [Object3D]
  197. │ │ └─Car_04_World_ap_0 [Mesh]
  198. </pre>
  199. <p>So as a simple test I thought I would just try rotating
  200. all the children of the "Cars" node around their Y axis.</p>
  201. <p>I looked up the "Cars" node after loading the scene
  202. and saved the result.</p>
  203. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+let cars;
  204. {
  205. const gltfLoader = new GLTFLoader();
  206. gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
  207. const root = gltf.scene;
  208. scene.add(root);
  209. + cars = root.getObjectByName('Cars');
  210. </pre>
  211. <p>Then in the <code class="notranslate" translate="no">render</code> function we can just set the rotation
  212. of each child of <code class="notranslate" translate="no">cars</code>.</p>
  213. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function render(time) {
  214. + time *= 0.001; // convert to seconds
  215. if (resizeRendererToDisplaySize(renderer)) {
  216. const canvas = renderer.domElement;
  217. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  218. camera.updateProjectionMatrix();
  219. }
  220. + if (cars) {
  221. + for (const car of cars.children) {
  222. + car.rotation.y = time;
  223. + }
  224. + }
  225. renderer.render(scene, camera);
  226. requestAnimationFrame(render);
  227. }
  228. </pre>
  229. <p>And we get</p>
  230. <p></p><div translate="no" class="threejs_example_container notranslate">
  231. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-rotate-cars.html"></iframe></div>
  232. <a class="threejs_center" href="/manual/examples/load-gltf-rotate-cars.html" target="_blank">click here to open in a separate window</a>
  233. </div>
  234. <p></p>
  235. <p>Hmmm, it looks like unfortunately this scene wasn't designed to
  236. animate the cars as their origins are not setup for that purpose.
  237. The trucks are rotating in the wrong direction.</p>
  238. <p>This brings up an important point which is if you're going to
  239. do something in 3D you need to plan ahead and design your assets
  240. so they have their origins in the correct places, so they are
  241. the correct scale, etc.</p>
  242. <p>Since I'm not an artist and I don't know blender that well I
  243. will hack this example. We'll take each car and parent it to
  244. another <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>. We will then move those <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> objects
  245. to move the cars but separately we can set the car's original
  246. <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> to re-orient it so it's about where we really need it.</p>
  247. <p>Looking back at the scene graph listing it looks like there
  248. are really only 3 types of cars, "Car_08", "CAR_03", and "Car_04".
  249. Hopefully each type of car will work with the same adjustments.</p>
  250. <p>I wrote this code to go through each car, parent it to a new
  251. <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>, parent that new <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> to the scene, and apply
  252. some per car <em>type</em> settings to fix its orientation, and add
  253. the new <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> a <code class="notranslate" translate="no">cars</code> array.</p>
  254. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-let cars;
  255. +const cars = [];
  256. {
  257. const gltfLoader = new GLTFLoader();
  258. gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
  259. const root = gltf.scene;
  260. scene.add(root);
  261. - cars = root.getObjectByName('Cars');
  262. + const loadedCars = root.getObjectByName('Cars');
  263. + const fixes = [
  264. + { prefix: 'Car_08', rot: [Math.PI * .5, 0, Math.PI * .5], },
  265. + { prefix: 'CAR_03', rot: [0, Math.PI, 0], },
  266. + { prefix: 'Car_04', rot: [0, Math.PI, 0], },
  267. + ];
  268. +
  269. + root.updateMatrixWorld();
  270. + for (const car of loadedCars.children.slice()) {
  271. + const fix = fixes.find(fix =&gt; car.name.startsWith(fix.prefix));
  272. + const obj = new THREE.Object3D();
  273. + car.getWorldPosition(obj.position);
  274. + car.position.set(0, 0, 0);
  275. + car.rotation.set(...fix.rot);
  276. + obj.add(car);
  277. + scene.add(obj);
  278. + cars.push(obj);
  279. + }
  280. ...
  281. </pre>
  282. <p>This fixes the orientation of the cars. </p>
  283. <p></p><div translate="no" class="threejs_example_container notranslate">
  284. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-rotate-cars-fixed.html"></iframe></div>
  285. <a class="threejs_center" href="/manual/examples/load-gltf-rotate-cars-fixed.html" target="_blank">click here to open in a separate window</a>
  286. </div>
  287. <p></p>
  288. <p>Now let's drive them around.</p>
  289. <p>Making even a simple driving system is too much for this post but
  290. it seems instead we could just make one convoluted path that
  291. drives down all the roads and then put the cars on the path.
  292. Here's a picture from Blender about half way through building
  293. the path.</p>
  294. <div class="threejs_center"><img src="../resources/images/making-path-for-cars.jpg" style="width: 1094px"></div>
  295. <p>I needed a way to get the data for that path out of Blender.
  296. Fortunately I was able to select just my path and export .OBJ checking "write nurbs".</p>
  297. <div class="threejs_center"><img src="../resources/images/blender-export-obj-write-nurbs.jpg" style="width: 498px"></div>
  298. <p>Opening the .OBJ file I was able to get a list of points
  299. which I formatted into this</p>
  300. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const controlPoints = [
  301. [1.118281, 5.115846, -3.681386],
  302. [3.948875, 5.115846, -3.641834],
  303. [3.960072, 5.115846, -0.240352],
  304. [3.985447, 5.115846, 4.585005],
  305. [-3.793631, 5.115846, 4.585006],
  306. [-3.826839, 5.115846, -14.736200],
  307. [-14.542292, 5.115846, -14.765865],
  308. [-14.520929, 5.115846, -3.627002],
  309. [-5.452815, 5.115846, -3.634418],
  310. [-5.467251, 5.115846, 4.549161],
  311. [-13.266233, 5.115846, 4.567083],
  312. [-13.250067, 5.115846, -13.499271],
  313. [4.081842, 5.115846, -13.435463],
  314. [4.125436, 5.115846, -5.334928],
  315. [-14.521364, 5.115846, -5.239871],
  316. [-14.510466, 5.115846, 5.486727],
  317. [5.745666, 5.115846, 5.510492],
  318. [5.787942, 5.115846, -14.728308],
  319. [-5.423720, 5.115846, -14.761919],
  320. [-5.373599, 5.115846, -3.704133],
  321. [1.004861, 5.115846, -3.641834],
  322. ];
  323. </pre>
  324. <p>THREE.js has some curve classes. The <a href="/docs/#api/en/extras/curves/CatmullRomCurve3"><code class="notranslate" translate="no">CatmullRomCurve3</code></a> seemed
  325. like it might work. The thing about that kind of curve is
  326. it tries to make a smooth curve going through the points.</p>
  327. <p>In fact putting those points in directly will generate
  328. a curve like this</p>
  329. <div class="threejs_center"><img src="../resources/images/car-curves-before.png" style="width: 400px"></div>
  330. <p>but we want a sharper corners. It seemed like if we computed
  331. some extra points we could get what we want. For each pair
  332. of points we'll compute a point 10% of the way between
  333. the 2 points and another 90% of the way between the 2 points
  334. and pass the result to <a href="/docs/#api/en/extras/curves/CatmullRomCurve3"><code class="notranslate" translate="no">CatmullRomCurve3</code></a>.</p>
  335. <p>This will give us a curve like this</p>
  336. <div class="threejs_center"><img src="../resources/images/car-curves-after.png" style="width: 400px"></div>
  337. <p>Here's the code to make the curve </p>
  338. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">let curve;
  339. let curveObject;
  340. {
  341. const controlPoints = [
  342. [1.118281, 5.115846, -3.681386],
  343. [3.948875, 5.115846, -3.641834],
  344. [3.960072, 5.115846, -0.240352],
  345. [3.985447, 5.115846, 4.585005],
  346. [-3.793631, 5.115846, 4.585006],
  347. [-3.826839, 5.115846, -14.736200],
  348. [-14.542292, 5.115846, -14.765865],
  349. [-14.520929, 5.115846, -3.627002],
  350. [-5.452815, 5.115846, -3.634418],
  351. [-5.467251, 5.115846, 4.549161],
  352. [-13.266233, 5.115846, 4.567083],
  353. [-13.250067, 5.115846, -13.499271],
  354. [4.081842, 5.115846, -13.435463],
  355. [4.125436, 5.115846, -5.334928],
  356. [-14.521364, 5.115846, -5.239871],
  357. [-14.510466, 5.115846, 5.486727],
  358. [5.745666, 5.115846, 5.510492],
  359. [5.787942, 5.115846, -14.728308],
  360. [-5.423720, 5.115846, -14.761919],
  361. [-5.373599, 5.115846, -3.704133],
  362. [1.004861, 5.115846, -3.641834],
  363. ];
  364. const p0 = new THREE.Vector3();
  365. const p1 = new THREE.Vector3();
  366. curve = new THREE.CatmullRomCurve3(
  367. controlPoints.map((p, ndx) =&gt; {
  368. p0.set(...p);
  369. p1.set(...controlPoints[(ndx + 1) % controlPoints.length]);
  370. return [
  371. (new THREE.Vector3()).copy(p0),
  372. (new THREE.Vector3()).lerpVectors(p0, p1, 0.1),
  373. (new THREE.Vector3()).lerpVectors(p0, p1, 0.9),
  374. ];
  375. }).flat(),
  376. true,
  377. );
  378. {
  379. const points = curve.getPoints(250);
  380. const geometry = new THREE.BufferGeometry().setFromPoints(points);
  381. const material = new THREE.LineBasicMaterial({color: 0xff0000});
  382. curveObject = new THREE.Line(geometry, material);
  383. scene.add(curveObject);
  384. }
  385. }
  386. </pre>
  387. <p>The first part of that code makes a curve.
  388. The second part of that code generates 250 points
  389. from the curve and then creates an object to display
  390. the lines made by connecting those 250 points.</p>
  391. <p>Running <a href="../examples/load-gltf-car-path.html">the example</a> I didn't see
  392. the curve. To make it visible I made it ignore the depth test and
  393. render last</p>
  394. <pre class="prettyprint showlinemods notranslate lang-js" translate="no"> curveObject = new THREE.Line(geometry, material);
  395. + material.depthTest = false;
  396. + curveObject.renderOrder = 1;
  397. </pre>
  398. <p>And that's when I discovered it was way too small.</p>
  399. <div class="threejs_center"><img src="../resources/images/car-curves-too-small.png" style="width: 498px"></div>
  400. <p>Checking the hierarchy in Blender I found out that the artist had
  401. scaled the node all the cars are parented to.</p>
  402. <div class="threejs_center"><img src="../resources/images/cars-scale-0.01.png" style="width: 342px;"></div>
  403. <p>Scaling is bad for real time 3D apps. It causes all kinds of
  404. issues and ends up being no end of frustration when doing
  405. real time 3D. Artists often don't know this because it's so
  406. easy to scale an entire scene in a 3D editing program but
  407. if you decide to make a real time 3D app I suggest you request your
  408. artists to never scale anything. If they change the scale
  409. they should find a way to apply that scale to the vertices
  410. so that when it ends up making it to your app you can ignore
  411. scale.</p>
  412. <p>And, not just scale, in this case the cars are rotated and offset
  413. by their parent, the <code class="notranslate" translate="no">Cars</code> node. This will make it hard at runtime
  414. to move the cars around in world space. To be clear, in this case
  415. we want cars to drive around in world space which is why these
  416. issues are coming up. If something that is meant to be manipulated
  417. in a local space, like the moon revolving around the earth this
  418. is less of an issue.</p>
  419. <p>Going back to the function we wrote above to dump the scene graph,
  420. let's dump the position, rotation, and scale of each node.</p>
  421. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function dumpVec3(v3, precision = 3) {
  422. + return `${v3.x.toFixed(precision)}, ${v3.y.toFixed(precision)}, ${v3.z.toFixed(precision)}`;
  423. +}
  424. function dumpObject(obj, lines, isLast = true, prefix = '') {
  425. const localPrefix = isLast ? '└─' : '├─';
  426. lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
  427. + const dataPrefix = obj.children.length
  428. + ? (isLast ? ' │ ' : '│ │ ')
  429. + : (isLast ? ' ' : '│ ');
  430. + lines.push(`${prefix}${dataPrefix} pos: ${dumpVec3(obj.position)}`);
  431. + lines.push(`${prefix}${dataPrefix} rot: ${dumpVec3(obj.rotation)}`);
  432. + lines.push(`${prefix}${dataPrefix} scl: ${dumpVec3(obj.scale)}`);
  433. const newPrefix = prefix + (isLast ? ' ' : '│ ');
  434. const lastNdx = obj.children.length - 1;
  435. obj.children.forEach((child, ndx) =&gt; {
  436. const isLast = ndx === lastNdx;
  437. dumpObject(child, lines, isLast, newPrefix);
  438. });
  439. return lines;
  440. }
  441. </pre>
  442. <p>And the result from <a href="../examples/load-gltf-dump-scenegraph-extra.html">running it</a></p>
  443. <pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
  444. │ pos: 0.000, 0.000, 0.000
  445. │ rot: 0.000, 0.000, 0.000
  446. │ scl: 1.000, 1.000, 1.000
  447. └─RootNode_(gltf_orientation_matrix) [Object3D]
  448. │ pos: 0.000, 0.000, 0.000
  449. │ rot: -1.571, 0.000, 0.000
  450. │ scl: 1.000, 1.000, 1.000
  451. └─RootNode_(model_correction_matrix) [Object3D]
  452. │ pos: 0.000, 0.000, 0.000
  453. │ rot: 0.000, 0.000, 0.000
  454. │ scl: 1.000, 1.000, 1.000
  455. └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
  456. │ pos: 0.000, 0.000, 0.000
  457. │ rot: 1.571, 0.000, 0.000
  458. │ scl: 1.000, 1.000, 1.000
  459. └─RootNode [Object3D]
  460. │ pos: 0.000, 0.000, 0.000
  461. │ rot: 0.000, 0.000, 0.000
  462. │ scl: 1.000, 1.000, 1.000
  463. ├─Cars [Object3D]
  464. * │ │ pos: -369.069, -90.704, -920.159
  465. * │ │ rot: 0.000, 0.000, 0.000
  466. * │ │ scl: 1.000, 1.000, 1.000
  467. │ ├─CAR_03_1 [Object3D]
  468. │ │ │ pos: 22.131, 14.663, -475.071
  469. │ │ │ rot: -3.142, 0.732, 3.142
  470. │ │ │ scl: 1.500, 1.500, 1.500
  471. │ │ └─CAR_03_1_World_ap_0 [Mesh]
  472. │ │ pos: 0.000, 0.000, 0.000
  473. │ │ rot: 0.000, 0.000, 0.000
  474. │ │ scl: 1.000, 1.000, 1.000
  475. </pre>
  476. <p>This shows us that <code class="notranslate" translate="no">Cars</code> in the original scene has had its rotation and scale
  477. removed and applied to its children. That suggests either whatever exporter was
  478. used to create the .GLTF file did some special work here or more likely the
  479. artist exported a different version of the file than the corresponding .blend
  480. file, which is why things don't match.</p>
  481. <p>The moral of that is I should have probably downloaded the .blend
  482. file and exported myself. Before exporting I should have inspected
  483. all the major nodes and removed any transformations.</p>
  484. <p>All these nodes at the top</p>
  485. <pre class="prettyprint showlinemods notranslate lang-text" translate="no">OSG_Scene [Scene]
  486. │ pos: 0.000, 0.000, 0.000
  487. │ rot: 0.000, 0.000, 0.000
  488. │ scl: 1.000, 1.000, 1.000
  489. └─RootNode_(gltf_orientation_matrix) [Object3D]
  490. │ pos: 0.000, 0.000, 0.000
  491. │ rot: -1.571, 0.000, 0.000
  492. │ scl: 1.000, 1.000, 1.000
  493. └─RootNode_(model_correction_matrix) [Object3D]
  494. │ pos: 0.000, 0.000, 0.000
  495. │ rot: 0.000, 0.000, 0.000
  496. │ scl: 1.000, 1.000, 1.000
  497. └─4d4100bcb1c640e69699a87140df79d7fbx [Object3D]
  498. │ pos: 0.000, 0.000, 0.000
  499. │ rot: 1.571, 0.000, 0.000
  500. │ scl: 1.000, 1.000, 1.000
  501. </pre>
  502. <p>are also a waste.</p>
  503. <p>Ideally the scene would consist of a single "root" node with no position,
  504. rotation, or scale. At runtime I could then pull all the children out of that
  505. root and parent them to the scene itself. There might be children of the root
  506. like "Cars" which would help me find all the cars but ideally it would also have
  507. no translation, rotation, or scale so I could re-parent the cars to the scene
  508. with the minimal amount of work.</p>
  509. <p>In any case the quickest though maybe not the best fix is to just
  510. adjust the object we're using to view the curve.</p>
  511. <p>Here's what I ended up with.</p>
  512. <p>First I adjusted the position of the curve and found values
  513. that seemed to work. I then hid it.</p>
  514. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  515. const points = curve.getPoints(250);
  516. const geometry = new THREE.BufferGeometry().setFromPoints(points);
  517. const material = new THREE.LineBasicMaterial({color: 0xff0000});
  518. curveObject = new THREE.Line(geometry, material);
  519. + curveObject.scale.set(100, 100, 100);
  520. + curveObject.position.y = -621;
  521. + curveObject.visible = false;
  522. material.depthTest = false;
  523. curveObject.renderOrder = 1;
  524. scene.add(curveObject);
  525. }
  526. </pre>
  527. <p>Then I wrote code to move the cars along the curve. For each car we pick a
  528. position from 0 to 1 along the curve and compute a point in world space using
  529. the <code class="notranslate" translate="no">curveObject</code> to transform the point. We then pick another point slightly
  530. further down the curve. We set the car's orientation using <code class="notranslate" translate="no">lookAt</code> and put the
  531. car at the mid point between the 2 points.</p>
  532. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">// create 2 Vector3s we can use for path calculations
  533. const carPosition = new THREE.Vector3();
  534. const carTarget = new THREE.Vector3();
  535. function render(time) {
  536. ...
  537. - for (const car of cars) {
  538. - car.rotation.y = time;
  539. - }
  540. + {
  541. + const pathTime = time * .01;
  542. + const targetOffset = 0.01;
  543. + cars.forEach((car, ndx) =&gt; {
  544. + // a number between 0 and 1 to evenly space the cars
  545. + const u = pathTime + ndx / cars.length;
  546. +
  547. + // get the first point
  548. + curve.getPointAt(u % 1, carPosition);
  549. + carPosition.applyMatrix4(curveObject.matrixWorld);
  550. +
  551. + // get a second point slightly further down the curve
  552. + curve.getPointAt((u + targetOffset) % 1, carTarget);
  553. + carTarget.applyMatrix4(curveObject.matrixWorld);
  554. +
  555. + // put the car at the first point (temporarily)
  556. + car.position.copy(carPosition);
  557. + // point the car the second point
  558. + car.lookAt(carTarget);
  559. +
  560. + // put the car between the 2 points
  561. + car.position.lerpVectors(carPosition, carTarget, 0.5);
  562. + });
  563. + }
  564. </pre>
  565. <p>and when I ran it I found out for each type of car, their height above their origins
  566. are not consistently set and so I needed to offset each one
  567. a little.</p>
  568. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loadedCars = root.getObjectByName('Cars');
  569. const fixes = [
  570. - { prefix: 'Car_08', rot: [Math.PI * .5, 0, Math.PI * .5], },
  571. - { prefix: 'CAR_03', rot: [0, Math.PI, 0], },
  572. - { prefix: 'Car_04', rot: [0, Math.PI, 0], },
  573. + { prefix: 'Car_08', y: 0, rot: [Math.PI * .5, 0, Math.PI * .5], },
  574. + { prefix: 'CAR_03', y: 33, rot: [0, Math.PI, 0], },
  575. + { prefix: 'Car_04', y: 40, rot: [0, Math.PI, 0], },
  576. ];
  577. root.updateMatrixWorld();
  578. for (const car of loadedCars.children.slice()) {
  579. const fix = fixes.find(fix =&gt; car.name.startsWith(fix.prefix));
  580. const obj = new THREE.Object3D();
  581. car.getWorldPosition(obj.position);
  582. - car.position.set(0, 0, 0);
  583. + car.position.set(0, fix.y, 0);
  584. car.rotation.set(...fix.rot);
  585. obj.add(car);
  586. scene.add(obj);
  587. cars.push(obj);
  588. }
  589. </pre>
  590. <p>And the result.</p>
  591. <p></p><div translate="no" class="threejs_example_container notranslate">
  592. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-animated-cars.html"></iframe></div>
  593. <a class="threejs_center" href="/manual/examples/load-gltf-animated-cars.html" target="_blank">click here to open in a separate window</a>
  594. </div>
  595. <p></p>
  596. <p>Not bad for a few minutes work.</p>
  597. <p>The last thing I wanted to do is turn on shadows.</p>
  598. <p>To do this I grabbed all the GUI code from the <a href="/docs/#api/en/lights/DirectionalLight"><code class="notranslate" translate="no">DirectionalLight</code></a> shadows
  599. example in <a href="shadows.html">the article on shadows</a> and pasted it
  600. into our latest code.</p>
  601. <p>Then, after loading, we need to turn on shadows on all the objects.</p>
  602. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  603. const gltfLoader = new GLTFLoader();
  604. gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) =&gt; {
  605. const root = gltf.scene;
  606. scene.add(root);
  607. + root.traverse((obj) =&gt; {
  608. + if (obj.castShadow !== undefined) {
  609. + obj.castShadow = true;
  610. + obj.receiveShadow = true;
  611. + }
  612. + });
  613. </pre>
  614. <p>I then spent nearly 4 hours trying to figure out why the shadow helpers
  615. were not working. It was because I forgot to enable shadows with</p>
  616. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">renderer.shadowMap.enabled = true;
  617. </pre>
  618. <p>😭</p>
  619. <p>I then adjusted the values until our <code class="notranslate" translate="no">DirectionLight</code>'s shadow camera
  620. had a frustum that covered the entire scene. These are the settings
  621. I ended up with.</p>
  622. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  623. const color = 0xFFFFFF;
  624. const intensity = 1;
  625. const light = new THREE.DirectionalLight(color, intensity);
  626. + light.castShadow = true;
  627. * light.position.set(-250, 800, -850);
  628. * light.target.position.set(-550, 40, -450);
  629. + light.shadow.bias = -0.004;
  630. + light.shadow.mapSize.width = 2048;
  631. + light.shadow.mapSize.height = 2048;
  632. scene.add(light);
  633. scene.add(light.target);
  634. + const cam = light.shadow.camera;
  635. + cam.near = 1;
  636. + cam.far = 2000;
  637. + cam.left = -1500;
  638. + cam.right = 1500;
  639. + cam.top = 1500;
  640. + cam.bottom = -1500;
  641. ...
  642. </pre>
  643. <p>and I set the background color to light blue.</p>
  644. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
  645. -scene.background = new THREE.Color('black');
  646. +scene.background = new THREE.Color('#DEFEFF');
  647. </pre>
  648. <p>And ... shadows</p>
  649. <p></p><div translate="no" class="threejs_example_container notranslate">
  650. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/load-gltf-shadows.html"></iframe></div>
  651. <a class="threejs_center" href="/manual/examples/load-gltf-shadows.html" target="_blank">click here to open in a separate window</a>
  652. </div>
  653. <p></p>
  654. <p>I hope walking through this project was useful and showed some
  655. good examples of working though some of the issues of loading
  656. a file with a scenegraph.</p>
  657. <p>One interesting thing is that comparing the .blend file to the .gltf
  658. file, the .blend file has several lights but they are not lights
  659. after being loaded into the scene. A .GLTF file is just a JSON
  660. file so you can easily look inside. It consists of several
  661. arrays of things and each item in an array is referenced by index
  662. else where. While there are extensions in the works they point
  663. to a problem with almost all 3d formats. <strong>They can never cover every
  664. case</strong>.</p>
  665. <p>There is always a need for more data. For example we manually exported
  666. a path for the cars to follow. Ideally that info could have been in
  667. the .GLTF file but to do that we'd need to write our own exporter
  668. and some how mark nodes for how we want them exported or use a
  669. naming scheme or something along those lines to get data from
  670. whatever tool we're using to create the data into our app.</p>
  671. <p>All of that is left as an exercise to the reader.</p>
  672. </div>
  673. </div>
  674. </div>
  675. <script src="../resources/prettify.js"></script>
  676. <script src="../resources/lesson.js"></script>
  677. </body></html>