Создайте интерактивный шар из жидкого металла с помощью WebGL

Автор: John Stephens
Дата создания: 27 Январь 2021
Дата обновления: 19 Май 2024
Anonim
Создайте интерактивный шар из жидкого металла с помощью WebGL - Творческий
Создайте интерактивный шар из жидкого металла с помощью WebGL - Творческий

Содержание

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

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

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


Настройка

Для начала нам нужно создать сцену, камеру и средство визуализации:

/ * * * Создает 3D-сцену, камеру и средство визуализации WebGL. * Затем продолжает и добавляет средство визуализации в тело * / function init () {var width = window.innerWidth, height = window.innerHeight, ratio = width / height; // настраиваем сцену и камеру scene = new THREE.Scene (); camera = new THREE.PerspectiveCamera (75, // угол камеры, соотношение, // соотношение окна просмотра 1, // ближняя плоскость и дальняя плоскость ниже 10000); // создаем средство визуализации со сглаживанием на renderer = new THREE.WebGLRenderer ({antialias: true}); renderer.setSize (ширина, высота); // теперь добавляем камеру в сцену // и контекст WebGL в DOM scene.add (camera); document.body.appendChild (renderer.domElement); // делаем все остальное createObjects (); createSprings (); bindCallbacks (); displaceRandomFace (); requestAnimationFrame (анимировать);}

Если вам нужно более подробное руководство по началу работы с Three.js, вы можете найти его на моем сайте.


Я предполагаю, что ваша сцена запущена и работает. Теперь, если бы вы посмотрели на это, вы бы не увидели абсолютно ничего, что немного удручает, так что давайте что-нибудь туда добавим!

Создание сферы и пола

В нашей сцене у нас есть два объекта: шар (сфера) и тень на полу. Сфера - это стандартный примитив Three.js (который я немного изменил, но об этом позже!), А пол - это плоскость, текстурированная с градиентом, чтобы выглядеть как тень. Тени в реальном времени - это действительно здорово, но в этом уроке наше изображение даст нам хороший обзор.

Создание сферы включает в себя создание геометрии (вершины и грани), а затем создание материала так, чтобы он выглядел как металлический.

Для создания геометрии мы используем следующий код:

// создаем геометрию сферы sphereGeometry = new THREE.SphereGeometry (200, // радиус 60, // разрешение x 30); // разрешение y

Надеюсь, довольно прямолинейно! У нас есть вершины, а как насчет нашего материала? Ну, это немного сложнее. Теперь нам нужно загрузить текстурный куб (или кубическую карту, как ее иногда называют), чтобы дать нам отражения, и нам нужно создать материал, который использует эту кубическую карту:


// сначала создаем карту окруженияvar urls = ['envmap / posx.webp', 'envmap / negx.webp', 'envmap / posy.webp', 'envmap / negy.webp', 'envmap / posz.webp', 'envmap / negz.webp'], // оборачиваем его в объект, который нам нуженtextureCube = THREE.ImageUtils.loadTextureCube (urls); // создаем материал сферы sphereMaterial = new THREE.MeshLambertMaterial ({color: 0xEEEEEE, envMap: textureCube , shininess: 200, shading: THREE.SmoothShading}); // теперь создаем сферу, а затем // объявляем ее геометрию динамической // чтобы мы могли обновить ее позже sphere = new THREE.Mesh (phereGeometry ,phereMaterial); .geometry.dynamic = true;

Итак, вы можете видеть, что мы начинаем с создания карты окружения или TextureCube. В моем случае я использую одну из карт окружения, которая поставляется с Three.js, но вы можете использовать и другую. Затем мы создаем куб текстуры и применяем его к envMap материала или карте среды. Это означает, что сфера будет отражать текстуру куба. Вы также увидите, что мы придаем сфере цвет. Этот цвет умножается на текстуру куба, и это последний цвет, который мы видим. Так, например, черный не даст нам отражения, а белый даст нам полное отражение. Здесь я выбрал светло-серый. Последнее, что нужно упомянуть о сфере, это то, что мы объявили геометрию динамической:

Sphere.geometry.dynamic = true;

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

Переходим к полу. На самом деле это довольно просто, поэтому давайте сразу перейдем к этому коду:

// создаем floorplaneGeometry = new THREE.PlaneGeometry (400, 400, 1); planeMaterial = new THREE.MeshBasicMaterial ({color: 0xFFFFFF, map: THREE.ImageUtils.loadTexture ("floor.png"), transparent: true}) ; plane = новый THREE.Mesh (planeGeometry, planeMaterial);

На этот раз мы создаем PlaneGeometry с шириной и высотой 400 и текстурой градиента, которая выглядит примерно так:

Наконец, нам нужно повернуть пол и расположить его под сферой:

// расположите пол немного вниз // и поверните его перпендикулярно // центру сферы plane.rotation.x = Math.PI * -0.5; plane.position.y = -150;

Большой! Итак, теперь, если вы откроете сцену, она должна выглядеть примерно так:

Пересечение лучей

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

Чтобы прийти к нашему решению, нам сначала нужно понять, что такое преобразование лучей, что может показаться немного пугающим, но я уверяю вас, что это не так. Всякий раз, когда мы рисуем 3D-фигуру на экране, мы фактически рисуем ее в 2D. В конце концов, ваш экран - это двухмерный блок. Этот процесс называется проектирование и это очень похоже на реальную жизнь, когда вы можете проецировать изображение на стену. Это также создает проблему, потому что, когда вы нажимаете на сцену, мы знаем координаты x и y того места, где вы щелкнули, но мы не знаем координату z; мы не знаем, как далеко в экран, по которому вы хотели щелкнуть.

Чтобы исправить это, мы берем координаты x и y того места, где вы щелкнули, и создаем бесконечно длинную линию или луч внутри нашей 3D-сцены, который при повторном сглаживании (как и остальная часть нашей сцены) даст нам x и координата y. Фактически, тогда наш луч представляет все возможные трехмерные местоположения, которые могут представлять местоположение, по которому щелкнули мышью. Этот процесс называется непроектирующий. Что мы можем сделать с этим, так это создать луч, начинающийся в позиции нашей камеры с нашей непроецированной линией, и посмотреть, пересекает ли он нашу сферу в какой-либо точке. Если он действительно пересекается, мы точно узнаем, где и исказим грани сферы прямо в этом месте.

Как это обычно бывает с Three.js, есть удобные функции, которые мы можем использовать для этого:

/ * * * Проверяет, подразумевает ли щелчок мышью * пересечение луча со сферой, и, если * это так, перемещает лицо, на которое он попал * * @param {Event} evt Мышь event * / function checkIntersection (evt) {// получаем положение мыши и создаем // проектор для луча var mouseX = evt.offsetX || evt.clientX, mouseY = evt.offsetY || evt.clientY, проектор = новый THREE.Projector (); // устанавливаем новый вектор в правильной // системе координат var vector = new THREE.Vector3 ((mouseX / window.innerWidth) * 2-1, - (mouseY / window.innerHeight) * 2 + 1, 0.5 ); // теперь "спроецируем" точку на экране // обратно в саму сцену. Это дает // нам направление лучей: project.unprojectVector (vector, camera); // создаем луч из нашей текущей позиции камеры // с этим направлением луча и смотрим, попадает ли он в сферу var ray = new THREE.Ray (camera.position, vector.subSelf (camera.position) .normalize ()), пересекает = ray.intersectObject (сфера); // если луч пересекается с // поверхностью, определить, где и исказить лицо if (intersects.length) {displaceFace (corrects [0] .face, DISPLACEMENT); }}

Фактический процесс создания луча в Three.js осуществляется путем определения положения камеры и направления нашего виртуального непроецированного луча. Направление - это нормализованный вектор (вектор длиной 1) трехмерной линии между координатами непроецированного щелчка мыши и положением камеры.

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

Весенняя физика

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

Мы будем создавать пружину между каждой из вершин, составляющих сферу. На картинке выше я упростил модель сферы, чтобы было понятнее. Я выделил одну из вершин оранжевым, виртуальные пружины - великолепным ярко-розовым, а вершины, с которыми наша оранжевая соединена, - синим. Теперь, когда мы искажаем сферу, мы закодируем ее так, чтобы эти пружины попытались вернуться в исходное положение, и все это благодаря чуду закона Гука.

Первое, что мы сделаем, это пройдемся по каждой грани и для каждой создадим пружины между составляющими ее вершинами:

/ * * * Создает отдельную пружину * * @param {Number} start Индекс вершины для начала пружины * @param {Number} end Индекс вершины для начала пружины * / функция createSpring (начало, конец) {var SphereVertices = Sphere.geometry.vertices; var startVertex = SphereVertices [начало]; var endVertex = SphereVertices [конец]; // создаем пружину startVertex.springs.push ({start: startVertex, end: endVertex, length: startVertex.position.length (endVertex.position)});}

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

По умолчанию примитивы в Three.js разделяют вершины между гранями. Это здорово для нас, потому что мы хотим, чтобы наши лица были связаны через эти общие вершины. К сожалению, по умолчанию не все вершины являются общими, особенно начальная и конечная в каждой полосе. В результате мы получим шов, спускающийся сверху вниз по нашей сфере, где нет никаких пружин! Итак, я создал модифицированную версию SphereGeometry в комплекте с Three.js и обновил ее, чтобы разделить первую и последнюю вершины.

Что ж, это хорошо, сейчас мы приближаемся к нашей цели. У нас есть сфера, пол, мы отслеживаем щелчки и искажаем грани сфер. У нас есть пружины на сфере, которые мы можем использовать для обработки вершин. Последнее, что связывает все это воедино, - это физика пружины.

Давайте посмотрим на физику и разберемся. Мы сосредоточимся на интересной части функции:

// теперь перебираем каждую пружину в отдельности (var v = 0; v vertexSprings.length; v ++) {// вычисляем длину пружины по сравнению // с ее базовой длиной vertexSpring = vertexSprings [v]; длина = vertexSpring.start.position. длина (vertexSpring.end.position); // теперь вычисляем, насколько далеко растянулась пружина // и используем это для создания // силы, которая будет тянуть за вершину extension = vertexSpring.length - length; // извлекаем начальную вершину acceleration.copy (vertexSpring.start.normal) .multiplyScalar (extension * SPRING_STRENGTH); vertexSpring.start.velocity.addSelf (ускорение); // вытаскиваем конечную вершину acceleration.copy (vertexSpring.end.normal) .multiplyScalar (extension * SPRING_STRENGTH); vertexSpring.end.velocity.addSelf (ускорение); // добавляем скорость к положению с помощью // базового интегрирования Эйлера vertexSpring.start.position.addSelf (vertexSpring.start.velocity); vertexSpring.end.position.addSelf (vertexSpring.end.velocity); // уменьшаем скорость пружины, чтобы она не // вечно качалась вперед и назад vertexSpring.start.velocity.multiplyScalar (DAMPEN); vertexSpring.end.velocity.multiplyScalar (DAMPEN);}

Как вы можете видеть из кода, мы проходим каждую пружину и вычисляем ее длину, и мы вычитаем исходную длину, давая нам удлинение пружины. Согласно закону Гука к вершине необходимо приложить силу, а именно:

F = -k * extension, где k - «жесткость пружины».

Все это означает, что сила пропорциональна растяжению и отрицательна, потому что действует в направлении, противоположном растяжению. Чем больше он вытянут, тем сильнее тянет в противоположном направлении. Все, что уходит, - это k, который показывает, насколько сильна пружина, и обычно находится между 0 (не пружинит) и 1 (полностью пружинит).

Отсюда мы делаем простое интегрирование Эйлера. Мы можем использовать классическое уравнение F = ma, чтобы вычислить ускорение (я предполагаю в коде массу 1 для простоты), которое мы добавляем к скорости пружины. Наконец, мы добавляем скорость пружины к ее положению.

Наконец, вы заметите, что я уменьшаю скорость при каждом проходе. Причина этого в том, что в реальной жизни источники успокаиваются. Без этого наша пружина будет постоянно звенеть взад и вперед, и она очень быстро выйдет из-под контроля!

Последние кусочки и кусочки

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

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

// пытаемся вернуть вершину // в исходное положение, чтобы она // не вышла из-под контроляvertex.position.addSelf (vertex.originalPosition.clone (). subSelf (vertex.position) .multiplyScalar (0.03)) ;

Просматривая код, вы увидите, что при отпускании мыши устанавливается таймер, который при срабатывании случайным образом выбирает лицо и перемещает его. Это дает нам немного жуткое органическое ощущение мяча, как будто что-то пытается ускользнуть.

/ * * * Случайно выбирает грань и смещает ее * затем устанавливает тайм-аут для следующего смещения * / function displaceRandomFace () {var SphereFaces = Sphere.geometry.faces, randomFaceIndex = Math.floor (Math. random () * SphereFaces.length), randomFace = SphereFaces [randomFaceIndex]; displaceFace (randomFace, СМЕЩЕНИЕ); autoDistortTimer = setTimeout (displaceRandomFace, 100);}

И последнее, но не менее важное, мы должны поговорить об обновлении геометрии. В Three.js вам нужно сообщить ему, когда вы обновили положение вершин. Без этого вы вообще не увидите никаких изменений, и это очень сбивает с толку!

// помечаем, что геометрия сферы // изменилась, и пересчитываем нормаль sphere.geometry .__ dirtyVertices = true ;phere.geometry .__ dirtyNormals = true ;phere.geometry.computeFaceNormals (); Sphere.geometry.computeVertexNormals ()

Заключение

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

Я надеюсь, что вы почувствуете вдохновение и поиграете с Three.js и WebGL самостоятельно. Если да, дайте нам знать; мы будем рады видеть то, что вы создаете!

Выбор читателей
Учебное пособие по Adobe Fresco: создание портрета в приложении для рисования
Узнать

Учебное пособие по Adobe Fresco: создание портрета в приложении для рисования

В этом уроке Adobe Fre co я создам яркий и эмоциональный портрет, показывающий, как в приложении можно использовать различные техники и текстуры, чтобы оживить и реалистичнее ваше искусство. От грубог...
10 советов по моделированию твердых поверхностей
Узнать

10 советов по моделированию твердых поверхностей

Это изображение Великого Восточного парохода Брунеля, созданное в 1858 году, находится в постоянной экспозиции нового музея стоимостью 7 миллионов фунтов стерлингов в Бристоле, который распахнул свои ...
Что делать, если вы начинаете ненавидеть рисование
Узнать

Что делать, если вы начинаете ненавидеть рисование

Несмотря на впечатляющую художественную карьеру, длившуюся почти 15 лет, Том Фаулер возненавидел рисование. Так что же делать иллюстратору, когда его страсть к рисованию остывает? В случае с Томом он ...