123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- <!DOCTYPE html><html lang="en"><head>
- <meta charset="utf-8">
- <title>Loading a .OBJ File</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 – Loading a .OBJ File">
- <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="../resources/lesson.css">
- <link rel="stylesheet" href="../resources/lang.css">
- <script type="importmap">
- {
- "imports": {
- "three": "../../build/three.module.js"
- }
- }
- </script>
- </head>
- <body>
- <div class="container">
- <div class="lesson-title">
- <h1>Loading a .OBJ File</h1>
- </div>
- <div class="lesson">
- <div class="lesson-main">
- <p>One of the most common things people want to do with three.js
- is to load and display 3D models. A common format is the .OBJ
- 3D format so let's try loading one.</p>
- <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>
- <div class="threejs_center"><img src="../resources/images/windmill-obj.jpg"></div>
- <p>I downloaded the .blend file from that site, loaded it into <a href="https://blender.org">Blender</a>
- and exported it as an .OBJ file.</p>
- <div class="threejs_center"><img style="width: 827px;" src="../resources/images/windmill-export-as-obj.jpg"></div>
- <blockquote>
- <p>Note: If you've never used Blender you might be in for a surprise
- in that Blender does things differently than just about every
- other program you've ever used. Just be aware you might need to
- set aside some time to read some basic UI navigation for Blender.</p>
- <p>Let me also add that 3D programs in general are giant beasts with
- 1000s of features. They are some of the most complicated software there
- is. When I first learned 3D Studio Max in 1996 I read through 70% of the
- 600 page manual spending a few hours a day for around 3 weeks. That paid
- off in that when I learned Maya a few years later some of the lessons
- learned before were applicable to Maya. So, just be aware that if you
- really want to be able to use 3D software to either build 3D assets
- or to modify existing ones put it on your schedule and clear sometime
- to really go through some lessons.</p>
- </blockquote>
- <p>In any case I used these export options</p>
- <div class="threejs_center"><img style="width: 239px;" src="../resources/images/windmill-export-options.jpg"></div>
- <p>Let's try to display it!</p>
- <p>I started with the directional lighting example from
- <a href="lights.html">the lights article</a> and I combined it with
- the hemispherical lighting example so I ended up with one
- <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
- related to adjusting the lights. I also removed the cube and sphere
- that were being added to the scene.</p>
- <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>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import {OBJLoader} from 'three/addons/loaders/OBJLoader.js';
- </pre>
- <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>,
- pass it the URL of our .OBJ file, and pass in a callback that adds
- the loaded model to our scene.</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>If we run that what happens?</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">click here to open in a separate window</a>
- </div>
- <p></p>
- <p>Well it's close but we're getting errors about materials since we haven't
- given the scene any materials and .OBJ files don't have material
- parameters. </p>
- <p>The .OBJ loader can be passed an
- object of name / material pairs. When it loads the .OBJ file,
- any material name it finds it will look for the corresponding material
- in the map of materials set on the loader. If it finds a
- material that matches by name it will use that material. If
- not it will use the loader's default material.</p>
- <p>Sometimes .OBJ files come with a .MTL file that defines
- materials. In our case the exporter also created a .MTL file.
- .MTL format is plain ASCII so it's easy to look at. Looking at it here</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>We can see there are 2 materials referencing 5 jpg textures
- but where are the texture files?</p>
- <div class="threejs_center"><img style="width: 757px;" src="../resources/images/windmill-exported-files.png"></div>
- <p>All we got was an .OBJ file and an .MTL file.</p>
- <p>At least for this model it turns out the textures are embedded
- in the .blend file we downloaded. We can ask blender to
- export those files to by picking <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>and then choosing <strong>Write Files to Current Directory</strong></p>
- <div class="threejs_center"><img style="width: 828px;" src="../resources/images/windmill-overwrite.jpg"></div>
- <p>This ends up writing the files in the same folder as the .blend file
- in a sub folder called <strong>textures</strong>.</p>
- <div class="threejs_center"><img style="width: 758px;" src="../resources/images/windmill-exported-texture-files.png"></div>
- <p>I copied those textures into the same folder I exported the .OBJ
- file to.</p>
- <div class="threejs_center"><img style="width: 757px;" src="../resources/images/windmill-exported-files-with-textures.png"></div>
- <p>Now that we have the textures available we can load the .MTL file.</p>
- <p>First we need to include the <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>Then we first load the .MTL file. When it's finished loading we add
- 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>
- and then load the .OBJ file.</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>And if we try that...</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.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/load-obj-materials.html" target="_blank">click here to open in a separate window</a>
- </div>
- <p></p>
- <p>Note that if we spin the model around you'll see the windmill cloth
- disappears</p>
- <div class="threejs_center"><img style="width: 528px;" src="../resources/images/windmill-missing-cloth.jpg"></div>
- <p>We need the material on the blades to be double sided, something
- we went over in <a href="materials.html">the article on materials</a>.
- There is no easy way to fix this in the .MTL file. Off the top of my
- head I can think of 3 ways to fix this.</p>
- <ol>
- <li><p>Loop over all the materials after loading them and set them all to double sided.</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>This solution works but ideally we only want materials that need
- to be double sided to be double sided because drawing double sided
- is slower than single sided.</p>
- </li>
- <li><p>Manually set a specific material</p>
- <p>Looking in the .MTL file there are 2 materials. One called <code class="notranslate" translate="no">"windmill"</code>
- and the other called <code class="notranslate" translate="no">"Material"</code>. Through trial and error I figured
- out the blades use the material called <code class="notranslate" translate="no">"Material"</code>so we could set
- that one specifically </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>Realizing that the .MTL file is limited we could just not use it
- and instead create materials ourselves.</p>
- <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
- loading the obj file.</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>Which one you pick is up to you. 1 is easiest. 3 is most flexible.
- 2 somewhere in between. For now I'll pick 2.</p>
- <p>And with that change you should still see the cloth on the blades
- when looking from behind but there's one more issue. If we zoom in close
- we see things are turning blocky.</p>
- <div class="threejs_center"><img style="width: 700px;" src="../resources/images/windmill-blocky.jpg"></div>
- <p>What's going on?</p>
- <p>Looking at the textures there are 2 textures labelled NOR for NORmal map.
- And looking at them they look like normal maps. Normal maps are generally
- purple where as bump maps are black and white. Normal maps represent
- the direction of the surface where as bump maps represent the height of
- the surface.</p>
- <div class="threejs_center"><img style="width: 256px;" src="../examples/resources/models/windmill/windmill_001_base_NOR.jpg"></div>
- <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>
- it expects the keyword <code class="notranslate" translate="no">norm</code> for normal maps so let's edit the .MTL file</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>and now when we load it it will be using the normal maps as normal maps and
- we can see the back of the blades.</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">click here to open in a separate window</a>
- </div>
- <p></p>
- <p>Let's load a different file.</p>
- <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>
- <div class="threejs_center"><img src="../resources/images/windmill-obj-2.jpg"></div>
- <p>It had a .OBJ version already available. Let's load it up (note I removed the .MTL loader for now)</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">click here to open in a separate window</a>
- </div>
- <p></p>
- <p>Hmmm, nothing appears. What's the problem? I wonder what size the model is?
- We can ask THREE.js what size the model is and try to set our
- camera automatically.</p>
- <p>First off we can ask THREE.js to compute a box that contains the scene
- we just loaded and ask for its size and center</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>Looking in <a href="debugging-javascript.html">the JavaScript console</a> I see</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>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.
- Our ground plane is only 40 units across so basically this windmill model is so big, 2000 units,
- that it's surrounding our camera and all parts of it our outside our frustum.</p>
- <div class="threejs_center"><img style="width: 280px;" src="../resources/images/camera-inside-windmill.svg"></div>
- <p>We could manually fix that but we could also make the camera auto frame our scene.
- Let's try that. We can then use the box we just computed adjust the camera settings to
- view the entire scene. Note that there is no <em>right</em> answer
- on where to put the camera. We could be facing the scene from any direction at any
- altitude so we'll just have to pick something.</p>
- <p>As we went over in <a href="cameras.html">the article on cameras</a> the camera defines a frustum.
- 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
- want to know given whatever field of view the camera currently has, how far away does the camera
- need to be so the box containing the scene fits inside the frustum assuming the frustum
- 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>
- <p>Since we know the size of the box and we know the field of view we have this triangle</p>
- <div class="threejs_center"><img style="width: 600px;" src="../resources/images/camera-fit-scene.svg"></div>
- <p>You can see on the left is the camera and the blue frustum is projecting out in
- front of it. We just computed the box that contains the the windmill. We need to
- compute how far way the camera should be from the box so that the box appears
- inside the frustum.</p>
- <p>Using basic <em>right triangle</em> trigonometry and <a href="https://www.google.com/search?q=SOHCAHTOA">SOHCAHTOA</a>,
- 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>
- <div class="threejs_center"><img style="width: 600px;" src="../resources/images/field-of-view-camera.svg"></div>
- <p>Based on that diagram the formula for computing distance is</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">distance = halfSizeToFitOnScreen / tangent(halfFovY)
- </pre>
- <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
- camera that <code class="notranslate" translate="no">distance</code> units from the center of the box. We'll then point the
- camera at the <code class="notranslate" translate="no">center</code> of the box.</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);
- // compute a unit vector that points in the direction the camera is now
- // from the center of the box
- const direction = (new THREE.Vector3()).subVectors(camera.position, boxCenter).normalize();
- // move the camera to a position distance units way from the center
- // in whatever direction the camera was from the center already
- camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
- // pick some near and far values for the frustum that
- // will contain the box.
- camera.near = boxSize / 100;
- camera.far = boxSize * 100;
- camera.updateProjectionMatrix();
- // point the camera to look at the center of the box
- camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
- }
- </pre>
- <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>
- and used that as <code class="notranslate" translate="no">sizeToFitOnScreen</code> then the math would make the box fit perfectly inside
- the frustum. We want a little extra space above and below so we'll pass in a slightly
- larger size. </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);
- + // compute the box that contains all the stuff
- + // from root and below
- + const box = new THREE.Box3().setFromObject(root);
- +
- + const boxSize = box.getSize(new THREE.Vector3()).length();
- + const boxCenter = box.getCenter(new THREE.Vector3());
- +
- + // set the camera to frame the box
- + frameArea(boxSize * 1.2, boxSize, boxCenter, camera);
- +
- + // update the Trackball controls to handle the new size
- + controls.maxDistance = boxSize * 10;
- + controls.target.copy(boxCenter);
- + controls.update();
- });
- }
- </pre>
- <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
- 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
- of the scene.</p>
- <p>Now if we try that we get...</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">click here to open in a separate window</a>
- </div>
- <p></p>
- <p>This almost works. Use the mouse to rotate the camera and you
- 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
- starts (0, 10, 20) to <code class="notranslate" translate="no">distance</code> units way from the center in the direction the camera
- is relative to the center that's moving the camera almost straight down below
- the windmill.</p>
- <div class="threejs_center"><img style="width: 360px;" src="../resources/images/computed-camera-position.svg"></div>
- <p>Let's change it to move sideways from the center of the box to in whatever direction
- 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
- of the vector from the box to the camera. Then, when we normalize that vector it will
- become a vector parallel to the XZ plane. In other words parallel to the ground.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-// compute a unit vector that points in the direction the camera is now
- -// from the center of the box
- -const direction = (new THREE.Vector3()).subVectors(camera.position, boxCenter).normalize();
- +// compute a unit vector that points in the direction the camera is now
- +// in the xz plane from the center of the box
- +const direction = (new THREE.Vector3())
- + .subVectors(camera.position, boxCenter)
- + .multiply(new THREE.Vector3(1, 0, 1))
- + .normalize();
- </pre>
- <p>If you look at the bottom of the windmill you'll see a small square. That is our ground
- plane. </p>
- <div class="threejs_center"><img style="width: 365px;" src="../resources/images/tiny-ground-plane.jpg"></div>
- <p>It's only 40x40 units and so is way too small relative to the windmill.
- Since the windmill is over 2000 units big let's change the size of the ground plane to
- something more fitting. We also need to adjust the repeat otherwise our checkerboard
- will be so fine we won't even be able to see it unless we zoom way way in.</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>and now we can see this windmill</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-xz.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/load-obj-auto-camera-xz.html" target="_blank">click here to open in a separate window</a>
- </div>
- <p></p>
- <p>Let's add the materials back. Like before there is a .MTL file that references
- some textures but looking at the files I quickly see an issue.</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>There are TARGA (.tga) files and they are giant!</p>
- <p>THREE.js actually has a TGA loader but it's arguably wrong to use it for most use cases.
- If you're making a viewer where you want to allow users to view random 3D files they
- find on the net then maybe, just maybe, you might want to load TGA files. (<a href="#loading-scenes">*</a>)</p>
- <p>One problem with TGA files are they can't be compressed well at all. TGA only supports very
- simple compression and looking above we can see the files are not compressed at all
- as the odds of them being all exactly the same size are extremely low. Further they
- are 12 megabytes each!!! If we used those files the user would have to download 36meg
- to see the windmill.</p>
- <p>Another issue with TGA is the browser itself has no support for them so loading them
- is likely going to be slower than loading supported formats like .JPG and .PNG</p>
- <p>I'm pretty sure for our purposes converting them to .JPG will be the best option.
- Looking inside I see they are 3 channels each, RGB, there is no alpha channel. JPG
- only supports 3 channels so that's a good fit. JPG also supports lossy compression
- so we can make the files much smaller to download</p>
- <p>Loading the files up they were each 2048x2048. That seemed like a waste to me but of
- course it depends on your use case. I made them each 1024x1024 and saved them at a
- 50% quality setting in Photoshop. Getting a file listing</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>We went from 36meg to 0.55meg! Of course the artist might not be pleased
- with this compression so be sure to consult with them to discuss the tradeoffs.</p>
- <p>Now, to use the .MTL file we need to edit it to reference the .JPG files
- instead of the .TGA files. Fortunately it's a simple text file so it's easy to edit</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>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
- and then set them on the <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);
- // compute the box that contains all the stuff
- // from root and below
- const box = new THREE.Box3().setFromObject(root);
- const boxSize = box.getSize(new THREE.Vector3()).length();
- const boxCenter = box.getCenter(new THREE.Vector3());
- // set the camera to frame the box
- frameArea(boxSize * 1.2, boxSize, boxCenter, camera);
- // update the Trackball controls to handle the new size
- controls.maxDistance = boxSize * 10;
- controls.target.copy(boxCenter);
- controls.update();
- });
- + });
- }
- </pre>
- <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>
- <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>
- <p>That's a useful feature but looking a the .MTL file above the line</p>
- <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">Kd 0.00 0.00 0.00
- </pre>
- <p>sets the diffuse color to 0. Texture map * 0 = black! It's possible the modeling tool used to make the windmill
- did not multiply the diffuse texture map by the diffuse color. That's why it worked for the artists that made this windmill.</p>
- <p>To fix this we can change the line to</p>
- <pre class="prettyprint showlinemods notranslate lang-mtl" translate="no">Kd 1.00 1.00 1.00
- </pre>
- <p>since Texture Map * 1 = Texture Map.</p>
- <p>Issue #2: The specular color is also black</p>
- <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
- did something similar as it did with diffuse maps in that it used the specular map's color for specular highlights.
- 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
- needs a specular color set.</p>
- <p>Like above we can fix that by editing the .MTL file like this.</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>Issue #3: The <code class="notranslate" translate="no">windmill_normal.jpg</code> is a normal map not a bump map.</p>
- <p>Just like above we just need to edit the .MTL file</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>Given all that if we now try it out it should load up with materials.</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">click here to open in a separate window</a>
- </div>
- <p></p>
- <p>Loading models often runs into these kinds of issues. Common issues include:</p>
- <ul>
- <li><p>Needing to know the size</p>
- <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
- 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>
- </li>
- <li><p>Orientation Wrong</p>
- <p>THREE.js is generally Y = up. Some modeling packages default to Z = up, some Y = up. Some are settable.
- 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),
- 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
- 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
- change the orientation.</p>
- </li>
- <li><p>No .MTL file or wrong materials or incompatible parameters</p>
- <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.
- 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
- scene and print out all the materials. Then, go modify the code to make custom materials and assign them where appropriate either
- 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
- scene and fixing things.</p>
- </li>
- <li><p>Textures too large</p>
- <p>Most 3D models are made for either architecture, movies and commercials, or
- games. For architecture and movies no one really cares about the size
- of the textures since. For games people care because games have limited
- memory but most games run locally. Webpages though you want to load
- as fast as possible and so you need to look at the textures and try
- to make them as small as possible and still look good. In fact the first windmill we should arguably done something about
- the textures. They are currently a total of 10meg!!!</p>
- <p>Also remember
- like we mentioned in the <a href="textures.html">article on textures</a> that
- textures take memory so a 50k JPG that expands to 4096x4096 will download
- fast but still take a ton of memory.</p>
- </li>
- </ul>
- <p>The last thing I wanted to show is spinning the windmills. Unfortunately, .OBJ files have no hierarchy. That means all parts of each
- 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>
- <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
- 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
- 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>
- <p>Next up we'll try <a href="load-gltf.html">loading a gLTF scene</a>. The gLTF format supports many more features.</p>
- </div>
- </div>
- </div>
- <script src="../resources/prettify.js"></script>
- <script src="../resources/lesson.js"></script>
- </body></html>
|