Модификаторы кривых в Three.js

Мой первый серьезный пиар для Three.js

Недавно я сделал расслабляющую демонстрацию сад кои, в которой рыбки кои плавают в среде виртуальной реальности. Двумя наиболее примечательными частями сцены являются трехмерный звук, о котором я писал ранее и сотни рыб, которые, кажется, органично плавают вокруг деревьев.

Трехмерные модели рыб изгибаются при повороте в узкие углы. Этот эффект отлично подходит для органических моделей, путешествующих по фиксированным путям, например для птиц или рыб.

Для моей рыбной сцены я нашел действительно крутое демо, которое можно было адаптировать под мои нужды. Автор хотел сделать пример THREE.js, но не успел до него добраться. Когда я адаптировал его для себя, я сделал его универсальным модулем, который можно использовать для абстрагирования от множества сложностей. Затем я отправил это в THREE.js в качестве примера, который может использовать кто угодно.

Как это работает

Этот метод работает путем кодирования кривых в текстуру. Затем специальный шейдер искажает вершины при движении модели по кривой.

Этот метод основан на значениях с плавающей запятой в текстуре. В WebGL 1 это дополнительная функция для разработчиков, поэтому, если вы используете первый WebGL, он может не работать на многих устройствах, но на некоторых телефонах Android эта функция недоступна. Вы можете обойти это, используя WebGL2. WebGL2 теперь имеет неплохую поддержку браузерами.

Вы можете использовать WebGL2 в THREEjs, явно получив контекст WebGL2 и передав его рендереру:

const canvas = document.querySelector('canvas');
const context = canvas.getContext( 'webgl2', { antialias: true } );
const renderer = new WebGLRenderer({ canvas, context });

Если вы хотите оптимизировать поддержку, вы можете определить, поддерживается ли WebGL2, и использовать его, если он доступен.

Это дорогостоящий метод, поэтому, если вы хотите иметь много одинаковых объектов, вам нужно воспользоваться преимуществом создания экземпляров для многократного рендеринга одного и того же объекта вдоль кривой.

Пример кода, который я сделал для Three.js, предназначен для использования преимуществ создания экземпляров. Создание экземпляров позволяет визуализировать множество копий одного и того же объекта за один вызов отрисовки. Это позволяет вам определить несколько кривых в текстуре и каждом экземпляре модели, указать, на какой кривой он находится, а также установить ее положение на кривой. Это может позволить вам иметь очень большое количество объектов, которые визуализируются за один вызов отрисовки.

В демонстрации, которую я сделал с рыбой, вы можете контролировать количество рыбы, задав параметр запроса ?fish=300. На мобильных устройствах он может отображать почти 100 рыб при сохранении частоты кадров 60 кадров в секунду. На ноутбуках он может поймать тысячи рыб, а на мощных настольных компьютерах он может поймать более 10000 рыб!



Использование примера Three.js

В упрощенных демонстрациях, которые я сделал для Three.js, есть сетка со сложной геометрией, созданная с помощью Three.js TextGeometry, который перемещается по кривым.

В первом примере есть единственный объект, который не является экземпляром, перемещающийся по одной кривой.

Во втором примере один экземпляр объекта 8 раз перемещается по 2 отдельным кривым.

Неэкземплярный метод - это более простой способ создания одного объекта.

  • Выберите свою сетку
  • Определите кривую для использования
  • Создайте новый поток из сетки
  • Добавьте кривую к сетке с индексом 0
  • Добавьте объект кривых в сцену
import { Flow } from "three/examples/jsm/modifiers/CurveModifier.js";
const points = [
 new Vector3( 1, 0, z: -1 ),
 new Vector3( 1, 0, z: 1 ),
 new Vector3( -1, 0, z: 1 ),
 new Vector3( -1, 0, z: -1 ),
];
const curve = new THREE.CatmullRomCurve3(points);
curve.curveType = "centripetal";
curve.closed = true;
const mesh = // some mesh I made earlier;
// You may need to tweak the geometry beforehand to get it to
// Display with the orientation you expect.
mesh.geometry.rotateX( Math.PI );
const flow = new Flow( objectToCurve );
flow.updateCurve( 0, curve );
scene.add( flow.object3D );

Примечание: вам не нужно добавлять сетку в сцену. Объект потока клонирует один из меша.

Экземплярный метод - это эффективный способ рисования множества объектов за один вызов отрисовки.

import { InstancedFlow } from "three/examples/jsm/modifiers/CurveModifier.js";
const material = // some material
const geometry = // some geometry
const curve1 = // A curve
const curve2 = // A curve
const curve3 = // A curve
const curve4 = // A curve
geometry.rotateX( Math.PI );
const numberOfInstances = 8;
const numberOfCurves = 4;
const flow = new InstancedFlow( numberOfInstances, numberOfCurves, geometry, material );
// Add the flow object to the scene
scene.add( flow.object3D );
flow.updateCurve( 0, curve1 );
flow.updateCurve( 1, curve2 );
flow.updateCurve( 2, curve3 );
flow.updateCurve( 3, curve4 );
// Do each step below for each numberOfInstances
// Set the first instance to be on the first curve
flow.setCurve( 0, 0 );
// Move the first instance along the curve by a random amount
flow.moveIndividualAlongCurve( 0, Math.random() );
// Give the first instance a random Color
flow.object3D.setColorAt( 0, new THREE.Color( 0xffffff * Math.random() ) );

Вызов updateCurve() позже, вы можете изменить кривые на лету. Это дорогостоящая операция, особенно если у вас много кривых - старайтесь не менять ее каждый кадр, если этого можно избежать.

Если вам нужно больше кривых или экземпляров объектов, чем вы выделили, вам придется создать новый экземпляр кривой с пространством для новых активов.

Если вы очень продвинутый разработчик, вам не нужно использовать экземпляры Flow, они просто упрощают вашу жизнь. Вспомогательные функции, которые они используют, также экспортируются, поэтому вы можете использовать их для создания собственных шейдеров и материалов. Вот исходный код для тех, кто хочет погрузиться в воду и узнать больше: