primitives.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. Title: Three.js Primitives
  2. Description: A tour of three.js primitives.
  3. TOC: Primitives
  4. This article is one in a series of articles about three.js.
  5. The first article was [about fundamentals](threejs-fundamentals.html).
  6. If you haven't read that yet you might want to start there.
  7. Three.js has a large number of primitives. Primitives
  8. are generally 3D shapes that are generated at runtime
  9. with a bunch of parameters.
  10. It's common to use primitives for things like a sphere
  11. for a globe or a bunch of boxes to draw a 3D graph. It's
  12. especially common to use primitives to experiment
  13. and get started with 3D. For the majority of 3D apps
  14. it's more common to have an artist make 3D models
  15. in a 3D modeling program like [Blender](https://blender.org)
  16. or [Maya](https://www.autodesk.com/products/maya/) or [Cinema 4D](https://www.maxon.net/en-us/products/cinema-4d/). Later in this series we'll
  17. cover making and loading data from several 3D modeling
  18. programs. For now let's go over some of the available
  19. primitives.
  20. Many of the primitives below have defaults for some or all of their
  21. parameters so you can use more or less depending on your needs.
  22. <div id="Diagram-BoxGeometry" data-primitive="BoxGeometry">A Box</div>
  23. <div id="Diagram-CircleGeometry" data-primitive="CircleGeometry">A flat circle</div>
  24. <div id="Diagram-ConeGeometry" data-primitive="ConeGeometry">A Cone</div>
  25. <div id="Diagram-CylinderGeometry" data-primitive="CylinderGeometry">A Cylinder</div>
  26. <div id="Diagram-DodecahedronGeometry" data-primitive="DodecahedronGeometry">A dodecahedron (12 sides)</div>
  27. <div id="Diagram-ExtrudeGeometry" data-primitive="ExtrudeGeometry">An extruded 2d shape with optional bevelling.
  28. Here we are extruding a heart shape. Note this is the basis
  29. for <code>TextGeometry</code>.</div>
  30. <div id="Diagram-IcosahedronGeometry" data-primitive="IcosahedronGeometry">An icosahedron (20 sides)</div>
  31. <div id="Diagram-LatheGeometry" data-primitive="LatheGeometry">A shape generated by spinning a line. Examples would be: lamps, bowling pins, candles, candle holders, wine glasses, drinking glasses, etc... You provide the 2d silhouette as series of points and then tell three.js how many subdivisions to make as it spins the silhouette around an axis.</div>
  32. <div id="Diagram-OctahedronGeometry" data-primitive="OctahedronGeometry">An Octahedron (8 sides)</div>
  33. <div id="Diagram-ParametricGeometry" data-primitive="ParametricGeometry">A surface generated by providing a function that takes a 2D point from a grid and returns the corresponding 3d point.</div>
  34. <div id="Diagram-PlaneGeometry" data-primitive="PlaneGeometry">A 2D plane</div>
  35. <div id="Diagram-PolyhedronGeometry" data-primitive="PolyhedronGeometry">Takes a set of triangles centered around a point and projects them onto a sphere</div>
  36. <div id="Diagram-RingGeometry" data-primitive="RingGeometry">A 2D disc with a hole in the center</div>
  37. <div id="Diagram-ShapeGeometry" data-primitive="ShapeGeometry">A 2D outline that gets triangulated</div>
  38. <div id="Diagram-SphereGeometry" data-primitive="SphereGeometry">A sphere</div>
  39. <div id="Diagram-TetrahedronGeometry" data-primitive="TetrahedronGeometry">A tetrahedron (4 sides)</div>
  40. <div id="Diagram-TextGeometry" data-primitive="TextGeometry">3D text generated from a 3D font and a string</div>
  41. <div id="Diagram-TorusGeometry" data-primitive="TorusGeometry">A torus (donut)</div>
  42. <div id="Diagram-TorusKnotGeometry" data-primitive="TorusKnotGeometry">A torus knot</div>
  43. <div id="Diagram-TubeGeometry" data-primitive="TubeGeometry">A circle traced down a path</div>
  44. <div id="Diagram-EdgesGeometry" data-primitive="EdgesGeometry">A helper object that takes another geometry as input and generates edges only if the angle between faces is greater than some threshold. For example if you look at the box at the top it shows a line going through each face showing every triangle that makes the box. Using an <code>EdgesGeometry</code> instead the middle lines are removed. Adjust the thresholdAngle below and you'll see the edges below that threshold disappear.</div>
  45. <div id="Diagram-WireframeGeometry" data-primitive="WireframeGeometry">Generates geometry that contains one line segment (2 points) per edge in the given geometry. Without this you'd often be missing edges or get extra edges since WebGL generally requires 2 points per line segment. For example if all you had was a single triangle there would only be 3 points. If you tried to draw it using a material with <code>wireframe: true</code> you would only get a single line. Passing that triangle geometry to a <code>WireframeGeometry</code> will generate a new geometry that has 3 lines segments using 6 points..</div>
  46. We'll go over creating custom geometry in [another article](threejs-custom-buffergeometry.html). For now
  47. let's make an example creating each type of primitive. We'll start
  48. with the [examples from the previous article](threejs-responsive.html).
  49. Near the top let's set a background color
  50. ```js
  51. const scene = new THREE.Scene();
  52. +scene.background = new THREE.Color(0xAAAAAA);
  53. ```
  54. This tells three.js to clear to lightish gray.
  55. The camera needs to change position so that we can see all the
  56. objects.
  57. ```js
  58. -const fov = 75;
  59. +const fov = 40;
  60. const aspect = 2; // the canvas default
  61. const near = 0.1;
  62. -const far = 5;
  63. +const far = 1000;
  64. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  65. -camera.position.z = 2;
  66. +camera.position.z = 120;
  67. ```
  68. Let's add a function, `addObject`, that takes an x, y position and an `Object3D` and adds
  69. the object to the scene.
  70. ```js
  71. const objects = [];
  72. const spread = 15;
  73. function addObject(x, y, obj) {
  74. obj.position.x = x * spread;
  75. obj.position.y = y * spread;
  76. scene.add(obj);
  77. objects.push(obj);
  78. }
  79. ```
  80. Let's also make a function to create a random colored material.
  81. We'll use a feature of `Color` that lets you set a color
  82. based on hue, saturation, and luminance.
  83. `hue` goes from 0 to 1 around the color wheel with
  84. red at 0, green at .33 and blue at .66. `saturation`
  85. goes from 0 to 1 with 0 having no color and 1 being
  86. most saturated. `luminance` goes from 0 to 1
  87. with 0 being black, 1 being white and 0.5 being
  88. the maximum amount of color. In other words
  89. as `luminance` goes from 0.0 to 0.5 the color
  90. will go from black to `hue`. From 0.5 to 1.0
  91. the color will go from `hue` to white.
  92. ```js
  93. function createMaterial() {
  94. const material = new THREE.MeshPhongMaterial({
  95. side: THREE.DoubleSide,
  96. });
  97. const hue = Math.random();
  98. const saturation = 1;
  99. const luminance = .5;
  100. material.color.setHSL(hue, saturation, luminance);
  101. return material;
  102. }
  103. ```
  104. We also passed `side: THREE.DoubleSide` to the material.
  105. This tells three to draw both sides of the triangles
  106. that make up a shape. For a solid shape like a sphere
  107. or a cube there's usually no reason to draw the
  108. back sides of triangles as they all face inside the
  109. shape. In our case though we are drawing a few things
  110. like the `PlaneGeometry` and the `ShapeGeometry`
  111. which are 2 dimensional and so have no inside. Without
  112. setting `side: THREE.DoubleSide` they would disappear
  113. when looking at their back sides.
  114. I should note that it's faster to draw when **not** setting
  115. `side: THREE.DoubleSide` so ideally we'd set it only on
  116. the materials that really need it but in this case we
  117. are not drawing too much so there isn't much reason to
  118. worry about it.
  119. Let's make a function, `addSolidGeometry`, that
  120. we pass a geometry and it creates a random colored
  121. material via `createMaterial` and adds it to the scene
  122. via `addObject`.
  123. ```js
  124. function addSolidGeometry(x, y, geometry) {
  125. const mesh = new THREE.Mesh(geometry, createMaterial());
  126. addObject(x, y, mesh);
  127. }
  128. ```
  129. Now we can use this for the majority of the primitives we create.
  130. For example creating a box
  131. ```js
  132. {
  133. const width = 8;
  134. const height = 8;
  135. const depth = 8;
  136. addSolidGeometry(-2, -2, new THREE.BoxGeometry(width, height, depth));
  137. }
  138. ```
  139. If you look in the code below you'll see a similar section for each type of geometry.
  140. Here's the result:
  141. {{{example url="../threejs-primitives.html" }}}
  142. There are a couple of notable exceptions to the pattern above.
  143. The biggest is probably the `TextGeometry`. It needs to load
  144. 3D font data before it can generate a mesh for the text.
  145. That data loads asynchronously so we need to wait for it
  146. to load before trying to create the geometry. By promisifiying
  147. font loading we can make it mush easier.
  148. We create a `FontLoader` and then a function `loadFont` that returns
  149. a promise that on resolve will give us the font. We then create
  150. an `async` function called `doit` and load the font using `await`.
  151. And finally create the geometry and call `addObject` to add it the scene.
  152. ```js
  153. {
  154. const loader = new THREE.FontLoader();
  155. // promisify font loading
  156. function loadFont(url) {
  157. return new Promise((resolve, reject) => {
  158. loader.load(url, resolve, undefined, reject);
  159. });
  160. }
  161. async function doit() {
  162. const font = await loadFont('resources/threejs/fonts/helvetiker_regular.typeface.json'); /* threejsfundamentals: url */
  163. const geometry = new THREE.TextGeometry('three.js', {
  164. font: font,
  165. size: 3.0,
  166. height: .2,
  167. curveSegments: 12,
  168. bevelEnabled: true,
  169. bevelThickness: 0.15,
  170. bevelSize: .3,
  171. bevelSegments: 5,
  172. });
  173. const mesh = new THREE.Mesh(geometry, createMaterial());
  174. geometry.computeBoundingBox();
  175. geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);
  176. const parent = new THREE.Object3D();
  177. parent.add(mesh);
  178. addObject(-1, -1, parent);
  179. }
  180. doit();
  181. }
  182. ```
  183. There's one other difference. We want to spin the text around its
  184. center but by default three.js creates the text such that its center of rotation
  185. is on the left edge. To work around this we can ask three.js to compute the bounding
  186. box of the geometry. We can then call the `getCenter` method
  187. of the bounding box and pass it our mesh's position object.
  188. `getCenter` copies the center of the box into the position.
  189. It also returns the position object so we can call `multiplyScalar(-1)`
  190. to position the entire object such that its center of rotation
  191. is at the center of the object.
  192. If we then just called `addSolidGeometry` like with previous
  193. examples it would set the position again which is
  194. no good. So, in this case we create an `Object3D` which
  195. is the standard node for the three.js scene graph. `Mesh`
  196. is inherited from `Object3D` as well. We'll cover [how the scene graph
  197. works in another article](threejs-scenegraph.html).
  198. For now it's enough to know that
  199. like DOM nodes, children are drawn relative to their parent.
  200. By making an `Object3D` and making our mesh a child of that
  201. we can position the `Object3D` wherever we want and still
  202. keep the center offset we set earlier.
  203. If we didn't do this the text would spin off center.
  204. {{{example url="../threejs-primitives-text.html" }}}
  205. Notice the one on the left is not spinning around its center
  206. whereas the one on the right is.
  207. The other exceptions are the 2 line based examples for `EdgesGeometry`
  208. and `WireframeGeometry`. Instead of calling `addSolidGeometry` they call
  209. `addLineGeometry` which looks like this
  210. ```js
  211. function addLineGeometry(x, y, geometry) {
  212. const material = new THREE.LineBasicMaterial({color: 0x000000});
  213. const mesh = new THREE.LineSegments(geometry, material);
  214. addObject(x, y, mesh);
  215. }
  216. ```
  217. It creates a black `LineBasicMaterial` and then creates a `LineSegments`
  218. object which is a wrapper for `Mesh` that helps three know you're rendering
  219. line segments (2 points per segment).
  220. Each of the primitives has several parameters you can pass on creation
  221. and it's best to [look in the documentation](https://threejs.org/docs/) for all of them rather than
  222. repeat them here. You can also click the links above next to each shape
  223. to take you directly to the docs for that shape.
  224. There is one other pair of classes that doesn't really fit the patterns above. Those are
  225. the `PointsMaterial` and the `Points` class. `Points` is like `LineSegments` above in that it takes a
  226. a `BufferGeometry` but draws points at each vertex instead of lines.
  227. To use it you also need to pass it a `PointsMaterial` which
  228. take a [`size`](PointsMaterial.size) for how large to make the points.
  229. ```js
  230. const radius = 7;
  231. const widthSegments = 12;
  232. const heightSegments = 8;
  233. const geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
  234. const material = new THREE.PointsMaterial({
  235. color: 'red',
  236. size: 0.2, // in world units
  237. });
  238. const points = new THREE.Points(geometry, material);
  239. scene.add(points);
  240. ```
  241. <div class="spread">
  242. <div data-diagram="Points"></div>
  243. </div>
  244. You can turn off [`sizeAttenuation`](PointsMaterial.sizeAttenuation) by setting it to false if you want the points to
  245. be the same size regardless of their distance from the camera.
  246. ```js
  247. const material = new THREE.PointsMaterial({
  248. color: 'red',
  249. + sizeAttenuation: false,
  250. + size: 3, // in pixels
  251. - size: 0.2, // in world units
  252. });
  253. ...
  254. ```
  255. <div class="spread">
  256. <div data-diagram="PointsUniformSize"></div>
  257. </div>
  258. One other thing that's important to cover is that almost all shapes
  259. have various settings for how much to subdivide them. A good example
  260. might be the sphere geometries. Spheres take parameters for
  261. how many divisions to make around and how many top to bottom. For example
  262. <div class="spread">
  263. <div data-diagram="SphereGeometryLow"></div>
  264. <div data-diagram="SphereGeometryMedium"></div>
  265. <div data-diagram="SphereGeometryHigh"></div>
  266. </div>
  267. The first sphere has 5 segments around and 3 high which is 15 segments
  268. or 30 triangles. The second sphere has 24 segments by 10. That's 240 segments
  269. or 480 triangles. The last one has 50 by 50 which is 2500 segments or 5000 triangles.
  270. It's up to you to decide how many subdivisions you need. It might
  271. look like you need a high number of segments but remove the lines
  272. and the flat shading and we get this
  273. <div class="spread">
  274. <div data-diagram="SphereGeometryLowSmooth"></div>
  275. <div data-diagram="SphereGeometryMediumSmooth"></div>
  276. <div data-diagram="SphereGeometryHighSmooth"></div>
  277. </div>
  278. It's now not so clear that the one on the right with 5000 triangles
  279. is entirely better than the one in the middle with only 480.
  280. If you're only drawing a few spheres, like say a single globe for
  281. a map of the earth, then a single 10000 triangle sphere is not a bad
  282. choice. If on the other hand you're trying to draw 1000 spheres
  283. then 1000 spheres times 10000 triangles each is 10 million triangles.
  284. To animate smoothly you need the browser to draw at 60 frames per
  285. second so you'd be asking the browser to draw 600 million triangles
  286. per second. That's a lot of computing.
  287. Sometimes it's easy to choose. For example you can also choose
  288. to subdivide a plane.
  289. <div class="spread">
  290. <div data-diagram="PlaneGeometryLow"></div>
  291. <div data-diagram="PlaneGeometryHigh"></div>
  292. </div>
  293. The plane on the left is 2 triangles. The plane on the right
  294. is 200 triangles. Unlike the sphere there is really no trade off in quality for most
  295. use cases of a plane. You'd most likely only subdivide a plane
  296. if you expected to want to modify or warp it in some way. A box
  297. is similar.
  298. So, choose whatever is appropriate for your situation. The less
  299. subdivisions you choose the more likely things will run smoothly and the less
  300. memory they'll take. You'll have to decide for yourself what the correct
  301. tradeoff is for your particular situation.
  302. If none of the shapes above fit your use case you can load
  303. geometry for example from a [.obj file](threejs-load-obj.html)
  304. or a [.gltf file](threejs-load-gltf.html).
  305. You can also create your own [custom BufferGeometry](threejs-custom-buffergeometry.html).
  306. Next up let's go over [how three's scene graph works and how
  307. to use it](threejs-scenegraph.html).
  308. <link rel="stylesheet" href="resources/threejs-primitives.css">
  309. <script type="module" src="resources/threejs-primitives.js"></script>