You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

440 lines
32 KiB

2 years ago
<!DOCTYPE html><html lang="ru"><head>
<meta charset="utf-8">
<title>Пользовательская BufferGeometry</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 – Пользовательская BufferGeometry">
<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="/manual/resources/lesson.css">
<link rel="stylesheet" href="/manual/resources/lang.css">
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../../build/three.module.js"
}
}
</script>
</head>
<body>
<div class="container">
<div class="lesson-title">
<h1>Пользовательская BufferGeometry</h1>
</div>
<div class="lesson">
<div class="lesson-main">
<p></p>
<p>В <a href="custom-geometry.html">предыдущей статье</a> рассказывалось, как использовать <code class="notranslate" translate="no">Geometry</code>. Эта статья о <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>. <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> обычно быстрее запускается и использует меньше памяти, но может быть сложнее в настройке. </p>
<p>В <a href="custom-geometry.html">статье о Geometry</a> мы рассказали, что для использования <code class="notranslate" translate="no">Geometry</code> вы предоставляете массив вершин <a href="/docs/#api/en/math/Vector3"><code class="notranslate" translate="no">Vector3</code></a> (позиций).
Затем вы создаете объекты <a href="/docs/#api/en/core/Face3"><code class="notranslate" translate="no">Face3</code></a>, определяя по индексу 3 вершины, которые составляют каждый треугольник фигуры, которую вы делаете.
Для каждого <a href="/docs/#api/en/core/Face3"><code class="notranslate" translate="no">Face3</code></a> вы можете указать либо нормаль грани, либо нормали для каждой отдельной вершины грани.
Вы также можете указать цвет грани или отдельные цвета вершин. Наконец, вы можете создать параллельный массив массивов текстурных координат (UV),
один массив для каждой грани, содержащий массив UV, один для каждой вершины грани. </p>
<div class="threejs_center"><img src="../resources/threejs-geometry.svg" style="width: 700px"></div>
<p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> с другой стороны использует названный <code class="notranslate" translate="no">BufferAttributes</code>.
Каждый атрибут <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> представляет собой массив данных одного типа: позиции, нормали, цвета и ультрафиолетовые лучи.
Вместе добавленные атрибуты <code class="notranslate" translate="no">BufferAttributes</code> представляют параллельные массивы всех данных для каждой вершины. </p>
<div class="threejs_center"><img src="../resources/threejs-attributes.svg" style="width: 700px"></div>
<p>Вы можете видеть, что у нас есть 4 атрибута: <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, <code class="notranslate" translate="no">color</code>, <code class="notranslate" translate="no">uv</code>.
Они представляют параллельные массивы, что означает, что N-й набор данных в каждом атрибуте принадлежит одной и той же вершине.
Вершина с индексом = 4 подсвечивается, чтобы показать, что параллельные данные по всем атрибутам определяют одну вершину. </p>
<p>Это поднимает точку, вот схема куба с одним выделенным углом. </p>
<div class="threejs_center"><img src="../resources/cube-faces-vertex.svg" style="width: 500px"></div>
<p>Думая об этом, один угол нуждается в разной нормали для каждой грани куба.
Для каждой стороны тоже нужны разные ультрафиолеты. Это указывает на самую большую разницу между <code class="notranslate" translate="no">Geometry</code> и <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>. Ничего общего с <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.
Одна вершина - это комбинация всех ее частей. Если вершина нуждается в какой-либо части, то она должна быть другой. </p>
<p>Правда в том, что когда вы используете <code class="notranslate" translate="no">Geometry</code> three.js преобразует его в этот формат.
Вот откуда появляется дополнительная память и время при использовании <code class="notranslate" translate="no">Geometry</code>.
Дополнительная память для всех объектов <code class="notranslate" translate="no">Vector3s</code>, <code class="notranslate" translate="no">Vector2s</code>, <code class="notranslate" translate="no">Face3s</code> и массива, а затем дополнительное время для преобразования всех этих данных
в параллельные массивы в форме атрибутов <code class="notranslate" translate="no">BufferAttributes</code>, как указано выше.
Иногда это облегчает использование Geometry. С <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> мы можем предоставить данные, уже преобразованные в этот формат. </p>
<p>В качестве простого примера давайте сделаем куб, используя <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.
Куб интересен тем, что кажется, что он разделяет вершины в углах, но на самом деле это не так. В нашем примере мы перечислим все вершины со всеми их данными,
а затем преобразуем эти данные в параллельные массивы и, наконец, используем их для создания атрибутов <code class="notranslate" translate="no">Buffer</code> и добавления их в <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>. </p>
<p>Начиная с примера координат текстуры из предыдущей статьи, мы удалили весь код, связанный с настройкой <code class="notranslate" translate="no">Geometry</code>.
Затем мы перечисляем все данные, необходимые для куба. Помните еще раз, что если вершина имеет какие-либо уникальные части, она должна быть отдельной вершиной.
Для создания куба необходимо 36 вершин. 2 треугольника на грань, 3 вершины на треугольник, 6 граней = 36 вершин. </p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
// front
{ pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
{ pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
{ pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], },
// right
{ pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], },
{ pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
{ pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
{ pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
{ pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
{ pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], },
// back
{ pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], },
{ pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
{ pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
{ pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
{ pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
{ pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], },
// left
{ pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], },
{ pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
{ pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
{ pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
{ pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
{ pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], },
// top
{ pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], },
{ pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
{ pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
{ pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
{ pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
{ pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], },
// bottom
{ pos: [ 1, -1, 1], norm: [ 0, -1, 0], uv: [0, 0], },
{ pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
{ pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
{ pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
{ pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
{ pos: [-1, -1, -1], norm: [ 0, -1, 0], uv: [1, 1], },
];
</pre>
<p>Затем мы можем перевести все это в 3 параллельных массива </p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const positions = [];
const normals = [];
const uvs = [];
for (const vertex of vertices) {
positions.push(...vertex.pos);
normals.push(...vertex.norm);
uvs.push(...vertex.uv);
}
</pre>
<p>Наконец, мы можем создать <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>, а затем <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> для каждого массива и добавить его в <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no"> const geometry = new THREE.BufferGeometry();
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
geometry.setAttribute(
'position',
new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
geometry.setAttribute(
'normal',
new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
geometry.setAttribute(
'uv',
new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
</pre>
<p>Обратите внимание, что имена являются значительными. Вы должны назвать свои атрибуты именами, которые соответствуют ожиданиям three.js
(если вы не создаете пользовательский шейдер). В этом случае <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code> и <code class="notranslate" translate="no">uv</code>. Если вы хотите цвета вершин, назовите свой атрибут <code class="notranslate" translate="no">color</code>. </p>
<p>Выше мы создали 3 собственных массива JavaScript, <code class="notranslate" translate="no">positions</code>, <code class="notranslate" translate="no">normals</code> и <code class="notranslate" translate="no">uvs</code> . Затем мы конвертируем их в
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a>
типа Float32Array. Атрибут <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> требует TypedArray, а не собственного массива. Атрибут <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> также требует, чтобы вы указали,
сколько компонентов в каждой вершине. Для позиций и нормалей у нас есть 3 компонента на вершину, x, y и z. Для UV у нас есть 2, u и v. </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/custom-buffergeometry-cube.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
</div>
<p></p>
<p>Это много данных. Небольшая вещь, которую мы можем сделать, это использовать индексы для ссылки на вершины.
Оглядываясь назад на данные нашего куба, каждая грань состоит из 2 треугольников с 3 вершинами в каждом, всего 6 вершин, но 2 из этих вершин абсолютно одинаковы;
Та же самая position, та же самая normal, и та же самая uv.
Таким образом, мы можем удалить совпадающие вершины и затем ссылаться на них по индексу. Сначала мы удаляем совпадающие вершины. </p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
// front
{ pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], }, // 0
{ pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], }, // 1
{ pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], }, // 2
-
- { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
- { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
{ pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], }, // 3
// right
{ pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], }, // 4
{ pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], }, // 5
-
- { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
- { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
{ pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], }, // 6
{ pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], }, // 7
// back
{ pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], }, // 8
{ pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], }, // 9
-
- { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
- { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
{ pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], }, // 10
{ pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], }, // 11
// left
{ pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], }, // 12
{ pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], }, // 13
-
- { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
- { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
{ pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], }, // 14
{ pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], }, // 15
// top
{ pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], }, // 16
{ pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], }, // 17
-
- { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
- { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
{ pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], }, // 18
{ pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], }, // 19
// bottom
{ pos: [ 1, -1, 1], norm: [ 0, -1, 0], uv: [0, 0], }, // 20
{ pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], }, // 21
-
- { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
- { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
{ pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], }, // 22
{ pos: [-1, -1, -1], norm: [ 0, -1, 0], uv: [1, 1], }, // 23
];
</pre>
<p>Итак, теперь у нас есть 24 уникальные вершины.
Затем мы указываем 36 индексов для 36 вершин, которые нам нужно нарисовать, чтобы сделать 12 треугольников, вызывая <a href="/docs/#api/en/core/BufferGeometry.setIndex"><code class="notranslate" translate="no">BufferGeometry.setIndex</code></a> с массивом индексов. </p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.setAttribute(
'position',
new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute(
'normal',
new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute(
'uv',
new THREE.BufferAttribute(uvs, uvNumComponents));
+geometry.setIndex([
+ 0, 1, 2, 2, 1, 3, // front
+ 4, 5, 6, 6, 5, 7, // right
+ 8, 9, 10, 10, 9, 11, // back
+ 12, 13, 14, 14, 13, 15, // left
+ 16, 17, 18, 18, 17, 19, // top
+ 20, 21, 22, 22, 21, 23, // bottom
+]);
</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/custom-buffergeometry-cube-indexed.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-indexed.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
</div>
<p></p>
<p>Как и в <code class="notranslate" translate="no">Geometry</code>, в <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> есть метод <a href="/docs/#api/en/core/BufferGeometry#computeVertexNormals"><code class="notranslate" translate="no">computeVertexNormals</code></a> для вычисления нормалей,
если вы их не предоставляете. В отличие от версии <code class="notranslate" translate="no">Geometry</code> той же функции,
поскольку позиции не могут быть общими, если любая другая часть вершины отличается, результаты вызова <code class="notranslate" translate="no">computeVertexNormals</code> будут другими. </p>
<div class="spread">
<div>
<div data-diagram="bufferGeometryCylinder"></div>
<div class="code">BufferGeometry</div>
</div>
<div>
<div data-diagram="geometryCylinder"></div>
<div class="code">Geometry</div>
</div>
</div>
<p>Вот 2 цилиндра, где нормали были созданы с использованием <code class="notranslate" translate="no">computeVertexNormals</code>.
Если вы посмотрите внимательно, на левом цилиндре есть шов. Это связано с тем,
что нет возможности совместно использовать вершины в начале и конце цилиндра, так как они требуют разных UV.
Просто небольшая вещь, чтобы быть в курсе.
Решение состоит в том, чтобы предоставить свои собственные normals. </p>
<p>Мы также можем использовать <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a>
с самого начала вместо собственных массивов JavaScript. Недостатком TypedArrays является то, что вы должны указать их размер заранее.
Конечно, это не так уж сложно, но с помощью собственных массивов мы можем просто <code class="notranslate" translate="no">push</code> значения в них и посмотреть,
какого размера они заканчиваются, проверив их <code class="notranslate" translate="no">length</code> в конце.
В TypedArrays нет функции push, поэтому нам нужно вести собственную бухгалтерию при добавлении значений к ним. </p>
<p>В этом примере узнать длину заранее довольно просто, так как для начала мы используем большой блок статических данных. </p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const positions = [];
-const normals = [];
-const uvs = [];
+const numVertices = vertices.length;
+const positionNumComponents = 3;
+const normalNumComponents = 3;
+const uvNumComponents = 2;
+const positions = new Float32Array(numVertices * positionNumComponents);
+const normals = new Float32Array(numVertices * normalNumComponents);
+const uvs = new Float32Array(numVertices * uvNumComponents);
+let posNdx = 0;
+let nrmNdx = 0;
+let uvNdx = 0;
for (const vertex of vertices) {
- positions.push(...vertex.pos);
- normals.push(...vertex.norm);
- uvs.push(...vertex.uv);
+ positions.set(vertex.pos, posNdx);
+ normals.set(vertex.norm, nrmNdx);
+ uvs.set(vertex.uv, uvNdx);
+ posNdx += positionNumComponents;
+ nrmNdx += normalNumComponents;
+ uvNdx += uvNumComponents;
}
geometry.setAttribute(
'position',
- new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
+ new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute(
'normal',
- new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
+ new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute(
'uv',
- new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
+ new THREE.BufferAttribute(uvs, uvNumComponents));
geometry.setIndex([
0, 1, 2, 2, 1, 3, // front
4, 5, 6, 6, 5, 7, // right
8, 9, 10, 10, 9, 11, // back
12, 13, 14, 14, 13, 15, // left
16, 17, 18, 18, 17, 19, // top
20, 21, 22, 22, 21, 23, // bottom
]);
</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/custom-buffergeometry-cube-typedarrays.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-typedarrays.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
</div>
<p></p>
<p>Хорошая причина использовать typedarrays - если вы хотите динамически обновлять любую часть вершин. </p>
<p>Я не мог придумать действительно хороший пример динамического обновления вершин,
поэтому я решил создать сферу и переместить каждый четырехугольник внутрь и наружу от центра. Надеюсь, это полезный пример. </p>
<p>Вот код для генерации позиций и индексов для сферы. Код разделяет вершины внутри четырехугольника,
но не разделяет вершины между четырьмя, потому что мы хотим иметь возможность перемещать каждый четырёхугольник по отдельности. </p>
<p>Поскольку я ленивый, я использовал небольшую иерархию из 3 объектов Object3D для вычисления точек сферы. Как это работает, объясняется в
<a href="optimize-lots-of-objects.html">статье об оптимизации множества объектов. </a>.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeSpherePositions(segmentsAround, segmentsDown) {
const numVertices = segmentsAround * segmentsDown * 6;
const numComponents = 3;
const positions = new Float32Array(numVertices * numComponents);
const indices = [];
const longHelper = new THREE.Object3D();
const latHelper = new THREE.Object3D();
const pointHelper = new THREE.Object3D();
longHelper.add(latHelper);
latHelper.add(pointHelper);
pointHelper.position.z = 1;
const temp = new THREE.Vector3();
function getPoint(lat, long) {
latHelper.rotation.x = lat;
longHelper.rotation.y = long;
longHelper.updateMatrixWorld(true);
return pointHelper.getWorldPosition(temp).toArray();
}
let posNdx = 0;
let ndx = 0;
for (let down = 0; down &lt; segmentsDown; ++down) {
const v0 = down / segmentsDown;
const v1 = (down + 1) / segmentsDown;
const lat0 = (v0 - 0.5) * Math.PI;
const lat1 = (v1 - 0.5) * Math.PI;
for (let across = 0; across &lt; segmentsAround; ++across) {
const u0 = across / segmentsAround;
const u1 = (across + 1) / segmentsAround;
const long0 = u0 * Math.PI * 2;
const long1 = u1 * Math.PI * 2;
positions.set(getPoint(lat0, long0), posNdx); posNdx += numComponents;
positions.set(getPoint(lat1, long0), posNdx); posNdx += numComponents;
positions.set(getPoint(lat0, long1), posNdx); posNdx += numComponents;
positions.set(getPoint(lat1, long1), posNdx); posNdx += numComponents;
indices.push(
ndx, ndx + 1, ndx + 2,
ndx + 2, ndx + 1, ndx + 3,
);
ndx += 4;
}
}
return {positions, indices};
}
</pre>
<p>Затем мы можем вызвать это так</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const segmentsAround = 24;
const segmentsDown = 16;
const {positions, indices} = makeSpherePositions(segmentsAround, segmentsDown);
</pre>
<p>Поскольку возвращаемые позиции являются позициями единичных сфер,
они являются точно такими же значениями, которые нам нужны для нормалей, поэтому мы можем просто дублировать их для нормалей. </p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const normals = positions.slice();
</pre>
<p>И тогда мы устанавливаем атрибуты, как раньше</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.BufferGeometry();
const positionNumComponents = 3;
const normalNumComponents = 3;
+const positionAttribute = new THREE.BufferAttribute(positions, positionNumComponents);
+positionAttribute.setUsage(THREE.DynamicDrawUsage);
geometry.setAttribute(
'position',
+ positionAttribute);
geometry.setAttribute(
'normal',
new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setIndex(indices);
</pre>
<p>Я выделил несколько различий. Мы сохраняем ссылку на атрибут позиции.
Мы также отмечаем его как динамический. Это намек на THREE.js, что мы будем часто менять содержимое атрибута. </p>
<p>В нашем цикле рендеринга мы обновляем позиции на основе их нормалей каждый кадр.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const temp = new THREE.Vector3();
...
for (let i = 0; i &lt; positions.length; i += 3) {
const quad = (i / 12 | 0);
const ringId = quad / segmentsAround | 0;
const ringQuadId = quad % segmentsAround;
const ringU = ringQuadId / segmentsAround;
const angle = ringU * Math.PI * 2;
temp.fromArray(normals, i);
temp.multiplyScalar(THREE.MathUtils.lerp(1, 1.4, Math.sin(time + ringId + angle) * .5 + .5));
temp.toArray(positions, i);
}
positionAttribute.needsUpdate = true;
</pre>
<p>И мы устанавливаем <code class="notranslate" translate="no">positionAttribute.needsUpdate</code>, чтобы THREE.js указывал использовать наши изменения.</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/custom-buffergeometry-dynamic.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/custom-buffergeometry-dynamic.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
</div>
<p></p>
<p>Я надеюсь, что это были полезные примеры того, как использовать
<a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> напрямую для создания собственной геометрии и как динамически
обновлять содержимое <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>. То, что вы используете, <code class="notranslate" translate="no">Geometry</code> или <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>,
действительно зависит от ваших потребностей. </p>
<p><canvas id="c"></canvas></p>
<script type="module" src="../resources/threejs-custom-buffergeometry.js"></script>
</div>
</div>
</div>
<script src="/manual/resources/prettify.js"></script>
<script src="/manual/resources/lesson.js"></script>
</body></html>