fundamentals.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. Title: Three.js Fundamentals
  2. Description: Your first Three.js lesson starting with the fundamentals
  3. TOC: Fundamentals
  4. This is the first article in a series of articles about three.js.
  5. [Three.js](https://threejs.org) is a 3D library that tries to make
  6. it as easy as possible to get 3D content on a webpage.
  7. Three.js is often confused with WebGL since more often than
  8. not, but not always, three.js uses WebGL to draw 3D.
  9. [WebGL is a very low-level system that only draws points, lines, and triangles](https://webglfundamentals.org).
  10. To do anything useful with WebGL generally requires quite a bit of
  11. code and that is where three.js comes in. It handles stuff
  12. like scenes, lights, shadows, materials, textures, 3d math, all things that you'd
  13. have to write yourself if you were to use WebGL directly.
  14. These tutorials assume you already know JavaScript and, for the
  15. most part they will use ES6 style. [See here for a
  16. terse list of things you're expected to already know](threejs-prerequisites.html).
  17. Most browsers that support three.js are auto-updated so most users should
  18. be able to run this code. If you'd like to make this code run
  19. on really old browsers look into a transpiler like [Babel](https://babeljs.io).
  20. Of course users running really old browsers probably have machines
  21. that can't run three.js.
  22. When learning most programming languages the first thing people
  23. do is make the computer print `"Hello World!"`. For 3D one
  24. of the most common first things to do is to make a 3D cube.
  25. So let's start with "Hello Cube!"
  26. Before we get started let's try to give you an idea of the structure
  27. of a three.js app. A three.js app requires you to create a bunch of
  28. objects and connect them together. Here's a diagram that represents
  29. a small three.js app
  30. <div class="threejs_center"><img src="resources/images/threejs-structure.svg" style="width: 768px;"></div>
  31. Things to notice about the diagram above.
  32. * There is a `Renderer`. This is arguably the main object of three.js. You pass a
  33. `Scene` and a `Camera` to a `Renderer` and it renders (draws) the portion of
  34. the 3D scene that is inside the *frustum* of the camera as a 2D image to a
  35. canvas.
  36. * There is a [scenegraph](threejs-scenegraph.html) which is a tree like
  37. structure, consisting of various objects like a `Scene` object, multiple
  38. `Mesh` objects, `Light` objects, `Group`, `Object3D`, and `Camera` objects. A
  39. `Scene` object defines the root of the scenegraph and contains properties like
  40. the background color and fog. These objects define a hierarchical parent/child
  41. tree like structure and represent where objects appear and how they are
  42. oriented. Children are positioned and oriented relative to their parent. For
  43. example the wheels on a car might be children of the car so that moving and
  44. orienting the car's object automatically moves the wheels. You can read more
  45. about this in [the article on scenegraphs](threejs-scenegraph.html).
  46. Note in the diagram `Camera` is half in half out of the scenegraph. This is to
  47. represent that in three.js, unlike the other objects, a `Camera` does not have
  48. to be in the scenegraph to function. Just like other objects, a `Camera`, as a
  49. child of some other object, will move and orient relative to its parent object.
  50. There is an example of putting multiple `Camera` objects in a scenegraph at
  51. the end of [the article on scenegraphs](threejs-scenegraph.html).
  52. * `Mesh` objects represent drawing a specific `Geometry` with a specific
  53. `Material`. Both `Material` objects and `Geometry` objects can be used by
  54. multiple `Mesh` objects. For example to draw two blue cubes in different
  55. locations we could need two `Mesh` objects to represent the position and
  56. orientation of each cube. We would only need one `Geometry` to hold the vertex
  57. data for a cube and we would only need one `Material` to specify the color
  58. blue. Both `Mesh` objects could reference the same `Geometry` object and the
  59. same `Material` object.
  60. * `Geometry` objects represent the vertex data of some piece of geometry like
  61. a sphere, cube, plane, dog, cat, human, tree, building, etc...
  62. Three.js provides many kinds of built in
  63. [geometry primitives](threejs-primitives.html). You can also
  64. [create custom geometry](threejs-custom-buffergeometry.html) as well as
  65. [load geometry from files](threejs-load-obj.html).
  66. * `Material` objects represent
  67. [the surface properties used to draw geometry](threejs-materials.html)
  68. including things like the color to use and how shiny it is. A `Material` can also
  69. reference one or more `Texture` objects which can be used, for example,
  70. to wrap an image onto the surface of a geometry.
  71. * `Texture` objects generally represent images either [loaded from image files](threejs-textures.html),
  72. [generated from a canvas](threejs-canvas-textures.html) or [rendered from another scene](threejs-rendertargets.html).
  73. * `Light` objects represent [different kinds of lights](threejs-lights.html).
  74. Given all of that we're going to make the smallest *"Hello Cube"* setup
  75. that looks like this
  76. <div class="threejs_center"><img src="resources/images/threejs-1cube-no-light-scene.svg" style="width: 500px;"></div>
  77. First let's load three.js
  78. ```html
  79. <script type="module">
  80. import * as THREE from './resources/threejs/r132/build/three.module.js';
  81. </script>
  82. ```
  83. It's important you put `type="module"` in the script tag. This enables
  84. us to use the `import` keyword to load three.js. There are other ways
  85. to load three.js but as of r106 using modules is the recommended way.
  86. Modules have the advantage that they can easily import other modules
  87. they need. That saves us from having to manually load extra scripts
  88. they are dependent on.
  89. Next we need is a `<canvas>` tag so...
  90. ```html
  91. <body>
  92. <canvas id="c"></canvas>
  93. </body>
  94. ```
  95. We will ask three.js to draw into that canvas so we need to look it up.
  96. ```html
  97. <script type="module">
  98. import * as THREE from './resources/threejs/r132/build/three.module.js';
  99. +function main() {
  100. + const canvas = document.querySelector('#c');
  101. + const renderer = new THREE.WebGLRenderer({canvas});
  102. + ...
  103. </script>
  104. ```
  105. After we look up the canvas we create a `WebGLRenderer`. The renderer
  106. is the thing responsible for actually taking all the data you provide
  107. and rendering it to the canvas. In the past there have been other renderers
  108. like `CSSRenderer`, a `CanvasRenderer` and in the future there may be a
  109. `WebGL2Renderer` or `WebGPURenderer`. For now there's the `WebGLRenderer`
  110. that uses WebGL to render 3D to the canvas.
  111. Note there are some esoteric details here. If you don't pass a canvas
  112. into three.js it will create one for you but then you have to add it
  113. to your document. Where to add it may change depending on your use case
  114. and you'll have to change your code so I find that passing a canvas
  115. to three.js feels a little more flexible. I can put the canvas anywhere
  116. and the code will find it whereas if I had code to insert the canvas
  117. into to the document I'd likely have to change that code if my use case
  118. changed.
  119. Next up we need a camera. We'll create a `PerspectiveCamera`.
  120. ```js
  121. const fov = 75;
  122. const aspect = 2; // the canvas default
  123. const near = 0.1;
  124. const far = 5;
  125. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  126. ```
  127. `fov` is short for `field of view`. In this case 75 degrees in the vertical
  128. dimension. Note that most angles in three.js are in radians but for some
  129. reason the perspective camera takes degrees.
  130. `aspect` is the display aspect of the canvas. We'll go over the details
  131. [in another article](threejs-responsive.html) but by default a canvas is
  132. 300x150 pixels which makes the aspect 300/150 or 2.
  133. `near` and `far` represent the space in front of the camera
  134. that will be rendered. Anything before that range or after that range
  135. will be clipped (not drawn).
  136. Those four settings define a *"frustum"*. A *frustum* is the name of
  137. a 3d shape that is like a pyramid with the tip sliced off. In other
  138. words think of the word "frustum" as another 3D shape like sphere,
  139. cube, prism, frustum.
  140. <img src="resources/frustum-3d.svg" width="500" class="threejs_center"/>
  141. The height of the near and far planes are determined by the field of view.
  142. The width of both planes is determined by the field of view and the aspect.
  143. Anything inside the defined frustum will be be drawn. Anything outside
  144. will not.
  145. The camera defaults to looking down the -Z axis with +Y up. We'll put our cube
  146. at the origin so we need to move the camera back a little from the origin
  147. in order to see anything.
  148. ```js
  149. camera.position.z = 2;
  150. ```
  151. Here's what we're aiming for.
  152. <img src="resources/scene-down.svg" width="500" class="threejs_center"/>
  153. In the diagram above we can see our camera is at `z = 2`. It's looking
  154. down the -Z axis. Our frustum starts 0.1 units from the front of the camera
  155. and goes to 5 units in front of the camera. Because in this diagram we are looking down,
  156. the field of view is affected by the aspect. Our canvas is twice as wide
  157. as it is tall so across the canvas the field of view will be much wider than
  158. our specified 75 degrees which is the vertical field of view.
  159. Next we make a `Scene`. A `Scene` in three.js is the root of a form of scene graph.
  160. Anything you want three.js to draw needs to be added to the scene. We'll
  161. cover more details of [how scenes work in a future article](threejs-scenegraph.html).
  162. ```js
  163. const scene = new THREE.Scene();
  164. ```
  165. Next up we create a `BoxGeometry` which contains the data for a box.
  166. Almost anything we want to display in Three.js needs geometry which defines
  167. the vertices that make up our 3D object.
  168. ```js
  169. const boxWidth = 1;
  170. const boxHeight = 1;
  171. const boxDepth = 1;
  172. const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
  173. ```
  174. We then create a basic material and set its color. Colors can
  175. be specified using standard CSS style 6 digit hex color values.
  176. ```js
  177. const material = new THREE.MeshBasicMaterial({color: 0x44aa88});
  178. ```
  179. We then create a `Mesh`. A `Mesh` in three represents the combination
  180. of a three things
  181. 1. A `Geometry` (the shape of the object)
  182. 2. A `Material` (how to draw the object, shiny or flat, what color, what texture(s) to apply. Etc.)
  183. 3. The position, orientation, and scale of that object in the scene relative to its parent. In the code below that parent is the scene.
  184. ```js
  185. const cube = new THREE.Mesh(geometry, material);
  186. ```
  187. And finally we add that mesh to the scene
  188. ```js
  189. scene.add(cube);
  190. ```
  191. We can then render the scene by calling the renderer's render function
  192. and passing it the scene and the camera
  193. ```js
  194. renderer.render(scene, camera);
  195. ```
  196. Here's a working example
  197. {{{example url="../threejs-fundamentals.html" }}}
  198. It's kind of hard to tell that is a 3D cube since we're viewing
  199. it directly down the -Z axis and the cube itself is axis aligned
  200. so we're only seeing a single face.
  201. Let's animate it spinning and hopefully that will make
  202. it clear it's being drawn in 3D. To animate it we'll render inside a render loop using
  203. [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
  204. Here's our loop
  205. ```js
  206. function render(time) {
  207. time *= 0.001; // convert time to seconds
  208. cube.rotation.x = time;
  209. cube.rotation.y = time;
  210. renderer.render(scene, camera);
  211. requestAnimationFrame(render);
  212. }
  213. requestAnimationFrame(render);
  214. ```
  215. `requestAnimationFrame` is a request to the browser that you want to animate something.
  216. You pass it a function to be called. In our case that function is `render`. The browser
  217. will call your function and if you update anything related to the display of the
  218. page the browser will re-render the page. In our case we are calling three's
  219. `renderer.render` function which will draw our scene.
  220. `requestAnimationFrame` passes the time since the page loaded to
  221. our function. That time is passed in milliseconds. I find it's much
  222. easier to work with seconds so here we're converting that to seconds.
  223. We then set the cube's X and Y rotation to the current time. These rotations
  224. are in [radians](https://en.wikipedia.org/wiki/Radian). There are 2 pi radians
  225. in a circle so our cube should turn around once on each axis in about 6.28
  226. seconds.
  227. We then render the scene and request another animation frame to continue
  228. our loop.
  229. Outside the loop we call `requestAnimationFrame` one time to start the loop.
  230. {{{example url="../threejs-fundamentals-with-animation.html" }}}
  231. It's a little better but it's still hard to see the 3d. What would help is to
  232. add some lighting so let's add a light. There are many kinds of lights in
  233. three.js which we'll go over in [a future article](threejs-lights.html). For now let's create a directional light.
  234. ```js
  235. {
  236. const color = 0xFFFFFF;
  237. const intensity = 1;
  238. const light = new THREE.DirectionalLight(color, intensity);
  239. light.position.set(-1, 2, 4);
  240. scene.add(light);
  241. }
  242. ```
  243. Directional lights have a position and a target. Both default to 0, 0, 0. In our
  244. case we're setting the light's position to -1, 2, 4 so it's slightly on the left,
  245. above, and behind our camera. The target is still 0, 0, 0 so it will shine
  246. toward the origin.
  247. We also need to change the material. The `MeshBasicMaterial` is not affected by
  248. lights. Let's change it to a `MeshPhongMaterial` which is affected by lights.
  249. ```js
  250. -const material = new THREE.MeshBasicMaterial({color: 0x44aa88}); // greenish blue
  251. +const material = new THREE.MeshPhongMaterial({color: 0x44aa88}); // greenish blue
  252. ```
  253. Here is our new program structure
  254. <div class="threejs_center"><img src="resources/images/threejs-1cube-with-directionallight.svg" style="width: 500px;"></div>
  255. And here it is working.
  256. {{{example url="../threejs-fundamentals-with-light.html" }}}
  257. It should now be pretty clearly 3D.
  258. Just for the fun of it let's add 2 more cubes.
  259. We'll use the same geometry for each cube but make a different
  260. material so each cube can be a different color.
  261. First we'll make a function that creates a new material
  262. with the specified color. Then it creates a mesh using
  263. the specified geometry and adds it to the scene and
  264. sets its X position.
  265. ```js
  266. function makeInstance(geometry, color, x) {
  267. const material = new THREE.MeshPhongMaterial({color});
  268. const cube = new THREE.Mesh(geometry, material);
  269. scene.add(cube);
  270. cube.position.x = x;
  271. return cube;
  272. }
  273. ```
  274. Then we'll call it 3 times with 3 different colors and X positions
  275. saving the `Mesh` instances in an array.
  276. ```js
  277. const cubes = [
  278. makeInstance(geometry, 0x44aa88, 0),
  279. makeInstance(geometry, 0x8844aa, -2),
  280. makeInstance(geometry, 0xaa8844, 2),
  281. ];
  282. ```
  283. Finally we'll spin all 3 cubes in our render function. We
  284. compute a slightly different rotation for each one.
  285. ```js
  286. function render(time) {
  287. time *= 0.001; // convert time to seconds
  288. cubes.forEach((cube, ndx) => {
  289. const speed = 1 + ndx * .1;
  290. const rot = time * speed;
  291. cube.rotation.x = rot;
  292. cube.rotation.y = rot;
  293. });
  294. ...
  295. ```
  296. and here's that.
  297. {{{example url="../threejs-fundamentals-3-cubes.html" }}}
  298. If you compare it to the top down diagram above you can see
  299. it matches our expectations. With cubes at X = -2 and X = +2
  300. they are partially outside our frustum. They are also
  301. somewhat exaggeratedly warped since the field of view
  302. across the canvas is so extreme.
  303. Our program now has this structure
  304. <div class="threejs_center"><img src="resources/images/threejs-3cubes-scene.svg" style="width: 610px;"></div>
  305. As you can see we have 3 `Mesh` objects each referencing the same `BoxGeometry`.
  306. Each `Mesh` references a unique `MeshPhongMaterial` so that each cube can have
  307. a different color.
  308. I hope this short intro helps to get things started. [Next up we'll cover
  309. making our code responsive so it is adaptable to multiple situations](threejs-responsive.html).
  310. <div id="es6" class="threejs_bottombar">
  311. <h3>es6 modules, three.js, and folder structure</h3>
  312. <p>As of version r106 the preferred way to use three.js is via <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import">es6 modules</a>.</p>
  313. <p>
  314. es6 modules can be loaded via the <code>import</code> keyword in a script
  315. or inline via a <code>&lt;script type="module"&gt;</code> tag. Here's an example of
  316. both
  317. </p>
  318. <pre class=prettyprint>
  319. &lt;script type="module"&gt;
  320. import * as THREE from './resources/threejs/r132/build/three.module.js';
  321. ...
  322. &lt;/script&gt;
  323. </pre>
  324. <p>
  325. Paths must be absolute or relative. Relative paths always start with <code>./</code> or <code>../</code>
  326. which is different than other tags like <code>&lt;img&gt;</code> and <code>&lt;a&gt;</code>.
  327. </p>
  328. <p>
  329. References to the same script will only be loaded once as long as their absolute paths
  330. are exactly the same. For three.js this means it's required that you put all the examples
  331. libraries in the correct folder structure
  332. </p>
  333. <pre class="dos">
  334. someFolder
  335. |
  336. ├-build
  337. | |
  338. | +-three.module.js
  339. |
  340. +-examples
  341. |
  342. +-jsm
  343. |
  344. +-controls
  345. | |
  346. | +-OrbitControls.js
  347. | +-TrackballControls.js
  348. | +-...
  349. |
  350. +-loaders
  351. | |
  352. | +-GLTFLoader.js
  353. | +-...
  354. |
  355. ...
  356. </pre>
  357. <p>
  358. The reason this folder structure is required is because the scripts in the
  359. examples like <a href="https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/OrbitControls.js"><code>OrbitControls.js</code></a>
  360. have hard coded relative paths like
  361. </p>
  362. <pre class="prettyprint">
  363. import * as THREE from '../../../build/three.module.js';
  364. </pre>
  365. <p>
  366. Using the same structure assures then when you import both three and one of the example
  367. libraries they'll both reference the same <code>three.module.js</code> file.
  368. </p>
  369. <pre class="prettyprint">
  370. import * as THREE from './someFolder/build/three.module.js';
  371. import {OrbitControls} from './someFolder/examples/jsm/controls/OrbitControls.js';
  372. </pre>
  373. <p>This includes when using a CDN. Be sure your path to <code>three.module.js</code> ends with
  374. <code>/build/three.modules.js</code>. For example</p>
  375. <pre class="prettyprint">
  376. import * as THREE from 'https://unpkg.com/[email protected]<b>/build/three.module.js</b>';
  377. import {OrbitControls} from 'https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js';
  378. </pre>
  379. <p>If you'd prefer the old <code>&lt;script src="path/to/three.js"&gt;&lt;/script&gt;</code> style
  380. you can check out <a href="https://r105.threejsfundamentals.org">an older version of this site</a>.
  381. Three.js has a policy of not worrying about backward compatibility. They expect you to use a specific
  382. version, as in you're expected to download the code and put it in your project. When upgrading to a newer version
  383. you can read the <a href="https://github.com/mrdoob/three.js/wiki/Migration-Guide">migration guide</a> to
  384. see what you need to change. It would be too much work to maintain both an es6 module and a class script
  385. version of this site so going forward this site will only show es6 module style. As stated elsewhere,
  386. to support legacy browsers look into a <a href="https://babeljs.io">transpiler</a>.</p>
  387. </div>
  388. <!-- needed in English only to prevent warning from outdated translations -->
  389. <a href="threejs-geometry.html"></a>
  390. <a href="Geometry"></a>