primitives.html 22 KB

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