2
0

fundamentals.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <!DOCTYPE html><html lang="ru"><head>
  2. <meta charset="utf-8">
  3. <title>Основы </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 – Основы ">
  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="/manual/resources/lesson.css">
  12. <link rel="stylesheet" href="/manual/resources/lang.css">
  13. <!-- Import maps polyfill -->
  14. <!-- Remove this when import maps will be widely supported -->
  15. <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
  16. <script type="importmap">
  17. {
  18. "imports": {
  19. "three": "../../build/three.module.js"
  20. }
  21. }
  22. </script>
  23. </head>
  24. <body>
  25. <div class="container">
  26. <div class="lesson-title">
  27. <h1>Основы </h1>
  28. </div>
  29. <div class="lesson">
  30. <div class="lesson-main">
  31. <p></p>
  32. <p>Это первая статья в серии статей о three.js.
  33. <a href="http://threejs.org">Three.js</a> это 3D-библиотека, которая максимально
  34. упрощает создание 3D-контента на веб-странице.</p>
  35. <p>Three.js часто путают с WebGL, поскольку чаще всего,
  36. но не всегда, three.js использует WebGL для рисования 3D.
  37. <a href="https://webglfundamentals.org">WebGL - это очень низкоуровневое api, рисующее только точки, линии и треугольники</a>.
  38. Чтобы сделать что-нибудь полезное с WebGL, как правило, требуется немало кода,
  39. и именно здесь приходит Three.js. Он обрабатывает такие вещи, как сцены,
  40. источники света, тени, материалы, текстуры, 3D-математику, все,
  41. что вам нужно было бы написать самостоятельно, если бы вы использовали WebGL напрямую.</p>
  42. <p>В этих руководствах предполагается, что вы уже знаете JavaScript,
  43. и по большей части они будут использовать стандарт ES6+. <a href="prerequisites.html">Смотрите здесь
  44. краткий список вещей, которые вы, как ожидается, уже знаете</a>.
  45. Большинство браузеров, которые поддерживают three.js,
  46. обновляются автоматически, поэтому большинство пользователей
  47. должны иметь возможность запускать этот код. Если вы хотите,
  48. чтобы этот код запускался в действительно старых браузерах,
  49. посмотрите на транспайлер, такой как <a href="http://babeljs.io">Babel</a>.
  50. Конечно, пользователи, использующие действительно старые браузеры,
  51. вероятно, имеют машины, которые не могут запускать three.js.</p>
  52. <p>При изучении большинства языков программирования первое, что делают люди,
  53. это заставляют компьютер напечатать <code class="notranslate" translate="no">"Hello World!"</code>. Для 3D одна из самых
  54. распространенных задач - создать 3D-куб, так что давайте начнем с <code class="notranslate" translate="no">"Hello Cube!"</code></p>
  55. <p>Первое, что нам нужно, это тэг <code class="notranslate" translate="no">&lt;canvas&gt;</code>:</p>
  56. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">&lt;body&gt;
  57. &lt;canvas id="c"&gt;&lt;/canvas&gt;
  58. &lt;/body&gt;
  59. </pre><p>Three.js будет рисовать на этом холсте, так что нам нужно найти
  60. его и передать three.js.</p>
  61. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">&lt;script type="module"&gt;
  62. import * as THREE from 'three';
  63. function main() {
  64. const canvas = document.querySelector('#c');
  65. const renderer = new THREE.WebGLRenderer({canvas});
  66. ...
  67. &lt;/script&gt;
  68. </pre><p>Обратите внимание, что здесь есть некоторые не явные детали.
  69. Если вы не передадите холст в three.js, библиотека создаст его за вас,
  70. но затем нужно будет добавить его в DOM. Место добавления
  71. может меняться в зависимости от вашего варианта использования,
  72. и вам придется изменить свой код, поэтому я считаю, что передача canvas
  73. в three.js выглядит немного более гибкой. Я могу поместить холст где угодно,
  74. и код найдет его там, как если бы у меня был код для вставки холста в документ,
  75. и мне, вероятно, пришлось бы изменить этот код, если бы изменился мой вариант
  76. использования.</p>
  77. <p>Когда канвас найден, мы создаем <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>. Renderer - это то, что отвечает
  78. за фактическое получение всех предоставленных вами данных и их отрисовку
  79. на холст. В прошлом были другие рендеры, такие как <code class="notranslate" translate="no">CSSRenderer</code>,
  80. <code class="notranslate" translate="no">CanvasRenderer</code>, а в будущем могут быть
  81. <code class="notranslate" translate="no">WebGL2Renderer</code> или <code class="notranslate" translate="no">WebGPURenderer</code>. На данный момент есть <a href="/docs/#api/en/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>,
  82. который использует WebGL для рисования 3D на холсте.</p>
  83. <p>Далее нам нужна камера.</p>
  84. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">const fov = 75;
  85. const aspect = 2; // значение для canvas по умолчанию
  86. const near = 0.1;
  87. const far = 5;
  88. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  89. </pre><p><code class="notranslate" translate="no">fov</code> сокращение от <code class="notranslate" translate="no">field of view</code>, <a href="https://en.wikipedia.org/wiki/Field_of_view">поле зрения</a>. В этом случае 75 градусов в
  90. вертикальном измерении. Обратите внимание, что большинство углов в Three.js
  91. указаны в радианах, но по какой-то причине перспективная камера принимает градусы.</p>
  92. <p><code class="notranslate" translate="no">aspect</code> это <a href="https://ru.wikipedia.org/wiki/Соотношение_сторон_экрана">соотношение сторон холста</a> (англ. aspect ratio). Мы рассмотрим детали
  93. в другой статье, но по умолчанию холст имеет размер 300x150 пикселей,
  94. значит соотношение сторон 300/150 или 2.</p>
  95. <p><code class="notranslate" translate="no">near</code> и <code class="notranslate" translate="no">far</code> представляют пространство перед камерой, которое будет отображаться.
  96. Все, что находится до или после этого диапазона, будет обрезано (не нарисовано).</p>
  97. <p>Эти 4 параметра определяют <a href="https://ru.wikipedia.org/wiki/Усечённая_пирамида">усеченную пирамиду</a> <em>"frustum"</em>. <em>Frustum</em> это
  98. название 3D фигуры, напоминающей пирамиду с отсеченной верхушкой. Другими словами,
  99. думайте о слове "frustum" как о трехмерной фигуре,
  100. такой как сфера, куб и призма.</p>
  101. <p><img src="../resources/frustum-3d.svg" width="500" class="threejs_center"></p>
  102. <p>Высота ближней и дальней плоскостей определяется полем зрения (field of view).
  103. Ширина обеих плоскостей определяется полем зрения и соотношением сторон (aspect).</p>
  104. <p>Все, что находится внутри определенного усеченного контура, будет нарисовано.
  105. Снаружи ничего не будет.</p>
  106. <p>По умолчанию камера смотрит вниз по оси -Z и вверх по оси +Y. Мы поместим наш куб
  107. в начало координат (origin), поэтому нам нужно немного отодвинуть камеру назад,
  108. чтобы что-то увидеть.</p>
  109. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">camera.position.z = 2;
  110. </pre><p>Вот как мы её направили.</p>
  111. <p><img src="../resources/scene-down.svg" width="500" class="threejs_center"></p>
  112. <p>На диаграмме выше мы видим, что наша камера находится в <code class="notranslate" translate="no">z = 2</code>. И смотрит вниз по оси -Z.
  113. Усеченная пирамида начинается с 0.1 единицы спереди камеры и до 5 единиц перед камерой.
  114. Поскольку на этой диаграмме мы смотрим вниз, поле зрения (fov) зависит от отношения
  115. сторон (aspect). Так как ширина холста в 2 раза больше высоты, при просмотре поле обзора
  116. будет намного шире, чем указанные нами 75 градусов, которые являются вертикальным
  117. полем зрения.</p>
  118. <p>Далее создадим <a href="/docs/#api/en/scenes/Scene"><code class="notranslate" translate="no">Scene</code></a>. <a href="/docs/#api/en/scenes/Scene"><code class="notranslate" translate="no">Scene</code></a> в three.js корень формы графа сцены.
  119. Все, что вы хотите нарисовать необходимо добавить на сцену. Мы рассмотрим подробнее,
  120. <a href="scenegraph.html">как работают сцены, в следующей статье</a>.</p>
  121. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">const scene = new THREE.Scene();
  122. </pre><p>Далее мы создаем <a href="/docs/#api/en/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a> который содержит данные для <a href="https://ru.wikipedia.org/wiki/Прямоугольный_параллелепипед">прямоугольного параллелепипеда</a>.
  123. Почти все, что мы хотим отобразить в Three.js, нуждается в геометрии,
  124. которая определяет вершины нашего трехмерного объекта.</p>
  125. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">const boxWidth = 1;
  126. const boxHeight = 1;
  127. const boxDepth = 1;
  128. const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
  129. </pre><p>Затем мы создаем основной материал и устанавливаем его цвет.
  130. Цвета могут быть определены с использованием 6-значных шестнадцатеричных
  131. значений цвета, как в CSS.</p>
  132. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">const material = new THREE.MeshBasicMaterial({color: 0x44aa88});
  133. </pre><p>Затем мы создаем <a href="https://ru.wikipedia.org/wiki/Полигональная_сетка">полигональную сетку</a>
  134. <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a>. <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> в three.js представляет комбинацию
  135. формы объекта <code class="notranslate" translate="no">Geometry</code> и <a href="/docs/#api/en/materials/Material"><code class="notranslate" translate="no">Material</code></a> (как нарисовать объект,
  136. блестящий или плоский, какой цвет, какую текстуру(ры) применить и т.д.)
  137. а также положение, ориентацию, и масштаб этого объекта в сцене.</p>
  138. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">const cube = new THREE.Mesh(geometry, material);
  139. </pre><p>И, наконец, мы добавляем <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> на сцену</p>
  140. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">scene.add(cube);
  141. </pre><p>Затем мы можем отрендерить сцену, вызвав функцию <code class="notranslate" translate="no">render</code> рендерера
  142. передав ей сцену и камеру.</p>
  143. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">renderer.render(scene, camera);
  144. </pre><p>Вот рабочий пример</p>
  145. <p></p><div translate="no" class="threejs_example_container notranslate">
  146. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals.html"></iframe></div>
  147. <a class="threejs_center" href="/manual/examples/fundamentals.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
  148. </div>
  149. <p></p>
  150. <p>Трудно сказать, что это 3D-куб, так как мы видим его непосредственно по оси
  151. -Z, а сам куб выровнен по этой оси, поэтому мы видим только одну грань.</p>
  152. <p>Давайте оживим его, и, надеюсь, это прояснит, что он рисуется в 3D. Для его
  153. анимации мы будем отрисовывать внутри цикла отрисовки, используя
  154. <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame"><code class="notranslate" translate="no">requestAnimationFrame</code></a>.</p>
  155. <p>Вот наш цикл</p>
  156. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">function render(time) {
  157. time *= 0.001; // конвертировать время в секунды
  158. cube.rotation.x = time;
  159. cube.rotation.y = time;
  160. renderer.render(scene, camera);
  161. requestAnimationFrame(render);
  162. }
  163. requestAnimationFrame(render);
  164. </pre><p><code class="notranslate" translate="no">requestAnimationFrame</code> это запрос к браузеру, что вы хотите что-то анимировать.
  165. Вы передаете ему функцию для вызова. В нашем случае эта функция <code class="notranslate" translate="no">render</code>.
  166. Браузер вызовет вашу функцию, и если вы обновите что-либо, связанное с
  167. отображением страницы, браузер выполнит перерисовку страницы.
  168. В нашем случае мы вызываем <code class="notranslate" translate="no">renderer.render</code>, которая нарисует нашу сцену.</p>
  169. <p><code class="notranslate" translate="no">requestAnimationFrame</code> передает время с момента загрузки страницы в нашу функцию.
  170. Это время приходит в миллисекундах. Я считаю, что работать с секундами намного проще,
  171. поэтому здесь мы конвертируем время в секунды.</p>
  172. <p>Затем мы устанавливаем вращение куба по X и Y на текущее время. Эти повороты в
  173. <a href="https://ru.wikipedia.org/wiki/Радиан">радианах</a>. В круге 2 пи радиана,
  174. поэтому наш куб должен повернуться вокруг каждой оси примерно за 6.28
  175. секунд.</p>
  176. <p>Затем мы отрисовываем сцену и запрашиваем еще один кадр анимации,
  177. чтобы продолжить наш цикл.</p>
  178. <p>Вне цикла мы вызываем <code class="notranslate" translate="no">requestAnimationFrame</code> один раз, чтобы запустить цикл.</p>
  179. <p></p><div translate="no" class="threejs_example_container notranslate">
  180. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals-with-animation.html"></iframe></div>
  181. <a class="threejs_center" href="/manual/examples/fundamentals-with-animation.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
  182. </div>
  183. <p></p>
  184. <p>Это немного лучше, но все еще трудно увидеть 3d. Что может помочь, так это
  185. добавить немного освещения, поэтому давайте добавим источник света.
  186. В Three.js есть много разных источников света, о которых мы поговорим в
  187. следующей статье. А пока давайте создадим направленный свет.</p>
  188. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">{
  189. const color = 0xFFFFFF;
  190. const intensity = 1;
  191. const light = new THREE.DirectionalLight(color, intensity);
  192. light.position.set(-1, 2, 4);
  193. scene.add(light);
  194. }
  195. </pre><p>Направленные источники имеет положение и цель. Оба по умолчанию равны 0, 0, 0. В нашем
  196. случае мы устанавливаем положение источника света на -1, 2, 4 чтобы оно было немного слева,
  197. сверху и позади нашей камеры. Цель по-прежнему 0, 0, 0, поэтому они будут светить
  198. в направлении начала координат.</p>
  199. <p>Нам также нужно изменить материал. <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a> не воспреимчив к свету.
  200. Давайте изменим его на <a href="/docs/#api/en/materials/MeshPhongMaterial"><code class="notranslate" translate="no">MeshPhongMaterial</code></a>, который отражает свет.</p>
  201. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">-const material = new THREE.MeshBasicMaterial({color: 0x44aa88}); // greenish blue
  202. +const material = new THREE.MeshPhongMaterial({color: 0x44aa88}); // greenish blue
  203. </pre><p>И вот оно работает.</p>
  204. <p></p><div translate="no" class="threejs_example_container notranslate">
  205. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals-with-light.html"></iframe></div>
  206. <a class="threejs_center" href="/manual/examples/fundamentals-with-light.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
  207. </div>
  208. <p></p>
  209. <p>Теперь должно быть довольно четко видно 3D.</p>
  210. <p>Просто для удовольствия добавим еще 2 кубика.</p>
  211. <p>Мы будем использовать одну и ту же геометрию для каждого куба, но
  212. создадим другой материал, чтобы каждый куб мог иметь свой цвет.</p>
  213. <p>Сначала мы сделаем функцию, которая создает новый материал с указанным цветом.
  214. Затем создает mesh, используя указанную геометрию, добавляет ее к сцене и
  215. устанавливает ей позицию X.</p>
  216. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">function makeInstance(geometry, color, x) {
  217. const material = new THREE.MeshPhongMaterial({color});
  218. const cube = new THREE.Mesh(geometry, material);
  219. scene.add(cube);
  220. cube.position.x = x;
  221. return cube;
  222. }
  223. </pre><p>Затем мы будем вызывать его 3 раза с 3 разными цветами и позициями X,
  224. сохраняя экземпляры <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> в массив.</p>
  225. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">const cubes = [
  226. makeInstance(geometry, 0x44aa88, 0),
  227. makeInstance(geometry, 0x8844aa, -2),
  228. makeInstance(geometry, 0xaa8844, 2),
  229. ];
  230. </pre><p>Наконец, мы закрутим все 3 куба в нашей функции отрисовки.
  231. Мы рассчитываем немного разные коэффициенты вращения для каждого.</p>
  232. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">function render(time) {
  233. time *= 0.001; // конвертировать время в секунды
  234. cubes.forEach((cube, ndx) =&gt; {
  235. const speed = 1 + ndx * .1;
  236. const rot = time * speed;
  237. cube.rotation.x = rot;
  238. cube.rotation.y = rot;
  239. });
  240. ...
  241. </pre><p>и вот оно.</p>
  242. <p></p><div translate="no" class="threejs_example_container notranslate">
  243. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fundamentals-3-cubes.html"></iframe></div>
  244. <a class="threejs_center" href="/manual/examples/fundamentals-3-cubes.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
  245. </div>
  246. <p></p>
  247. <p>Если вы сравните его с диаграммой сверху вниз, вы увидите, что она соответствует
  248. нашим ожиданиям. С кубами в X = -2 и X = +2 они частично находятся вне нашей
  249. усеченной пирамиды. Они также несколько искривлены, так как
  250. поле зрения на холсте очень велико.</p>
  251. <p>Я надеюсь, что это короткое вступление поможет вам начать изучение.
  252. <a href="responsive.html">Далее мы рассмотрим, как сделать отзывчивый дизайн, чтобы код можно было применять
  253. к различным ситуациям</a>.</p>
  254. </div>
  255. </div>
  256. </div>
  257. <script src="/manual/resources/prettify.js"></script>
  258. <script src="/manual/resources/lesson.js"></script>
  259. </body></html>