Как рисовать генеративные грибы NFT с помощью Three.js 🍄

Как рисовать генеративные грибы NFT с помощью Three.js 🍄

17 февраля 2022 г.

Часть 1: Введение


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


Задний план


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


Вкратце, идея заключается в том, чтобы сделать некий генератор токенов, который выдает вам уникальный арт-объект каждый раз, когда вы его «чеканите» (на самом деле, вызываете метод в блокчейне, который тратит часть ваших денег на его выполнение, а также дает немного денег). к художнику). Определенно, есть какое-то волшебство в ощущении, что ваша транзакция генерирует уникальный объект, который навсегда останется в блокчейне, не так ли?


Есть несколько арт-платформ, которые используют эту идею, самая известная из них — artblocks.io. Но поскольку в нем много бюрократии, а также он построен на блокчейне Ethereum, который по-прежнему использует доказательство работы и имеет очень высокую цену на газ, я решил попробовать себя на более демократичном, дешевом и экологически чистая платформа - fxhash.xyz


Что такое генеративное изображение NFT?


Все генеративные NFT — это в основном веб-страницы, которые рисуют что-то на холсте, используя либо ванильный javascript, либо некоторые сторонние библиотеки. Пытаясь классифицировать, с моей точки зрения, я бы разделил все генеративные NFT на 3 категории: абстрактные математические произведения искусства, конкретные процедурные произведения искусства и вариативные рисованные произведения искусства.


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


Также есть некоторые экспериментальные и интерактивные работы, даже модульные синтезаторы и игры, но такие встречаются гораздо реже.


![Слева направо примеры математических, процедурных и вариативных работ. Кредиты: ciphrd, zancan, littlesilver] (https://cdn.hackernoon.com/images/ckzp-8-qjr-400-dt-0-as-68-oy-7-gde-3.jpg)


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


Часть 2: Рисуем гриб 🍄


Otinium caseubbacula — один из генеративных образцов грибов


Хорошо, давайте закончим со всей этой философией и перейдем к технической части. Этот проект был полностью создан с использованием библиотеки Three.js, которая имеет достаточно простой и хорошо документированный API.


Стайп


По сути, ножка может быть параметризована как выдавливание замкнутого контура вдоль некоторого сплайна (назовем его базовым сплайном). Для создания базового сплайна я использовал класс CatmullRomCurve3 из Three js. Затем я создал геометрию вершина за вершиной, переместив другую замкнутую фигуру вдоль базового сплайна и, наконец, соединив эти вершины с гранями. Для этой цели я использовал BufferGeometry.


```javascript


stipe_vSegments = 30; // вертикальное разрешение


stipe_rSegments = 20; // угловое разрешение


острые_точки = []; // вершины


stipe_indices = []; // индексы граней


stipe_shape = new THREE.CatmullRomCurve3(...,closed=false);


функция stipe_radius(a, t) { ... }


for (var t = 0; t < 1; t += 1 / stipe_vSegments) {


// кривая профиля ножки


переменная кривая = новый THREE.CatmullRomCurve3([


новый THREE.Vector3 (0, 0, stipe_radius (0, t)),


новый THREE.Vector3 (stipe_radius (Math.PI/2, t), 0, 0),


новый THREE.Vector3 (0, 0, -stipe_radius (Math.PI, t)),


новый THREE.Vector3 (-stipe_radius (Math.PI * 1.5, t), 0, 0),


], закрытый = истина, curveType = 'catmullrom', натяжение = 0,75);


var profile_points = curve.getPoints(stipe_rSegments);


for (var i = 0; i < profile_points.length; i++) {


stipe_points.push(profile_points[i].x, profile_points[i].y, profile_points[i].z);


// <- здесь нужно вычислить индексы граней


// и затем создаем BufferGeometry


var stipe = new THREE.BufferGeometry();


stipe.setAttribute('position', new THREE.BufferAttribute(new Float32Array(stipe_points), 3));


stipe.setIndex(stipe_indices);


stipe.computeVertexNormals();


Этапы генерации ножки: сплайн, вершины, грани


Ступенчатый шум


Для большей естественности поверхность ножки может как-то меняться вместе с ее высотой. Я определил радиус ножки как функцию угла и относительной высоты точки на базовом сплайне. Затем к значению радиуса добавляется небольшое количество шума в зависимости от этих параметров.


```javascript


базовый_радиус = 1; // средний радиус


шум_с = 2; // выше это - выше деформации


// радиус ножки как функция угла и относительного положения


функция stipe_radius(a, t) {


вернуть base_radius + (1 - t)(1 + Math.random())noise_c;


Вариации шума


Шапка


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


```javascript


cap_rSegments = 30; // радиальное разрешение


cap_cSegments = 20; // угловое разрешение


cap_points = [];


cap_indices = [];


// поверхность шапки как функция полярных координат


функция cap_surface(a0, t0) {


// 1. вычислить (a,t) из (a0,t0), например применить шум


// 2. вычислить значение сплайна в t


// 3. повернуть его на угол a вокруг конца ножки


// 4. применить некоторые другие шумы/преобразования


вернуть точку_поверхности;


// создаем вершины поверхности с разрешением


// cap_rSegments * cap_cSegments


for (var i = 1; i <= cap_rSegments; i++) {


var t0 = i / cap_rSegments;


for (var j = 0; j < cap_cSegments; j++) {


var a0 = Math.PI * 2 / cap_cSegments * j;


вар surface_point = cap_surface(a0, t0);


cap_points.push(surface_point.x, Surface_point.y, Surface_point.z);


// <- здесь нужно вычислить индексы граней


// и затем создаем BufferGeometry


var cap = new THREE.BufferGeometry();


cap.setAttribute('position', new THREE.BufferAttribute(new Float32Array(cap_points), 3));


cap.setIndex(cap_indices);


cap.computeVertexNormals();


Этапы генерации кепки: сплайн, вершины, грани


Шум кепки


Чтобы быть более реалистичным, кепке также нужен шум. Я разделил колпачковый шум на 3 компонента: радиальный, угловой и нормальный шум. Радиальный шум влияет на относительное положение вершины на базовом сплайне. Угловой шум изменяет угол вращения базового сплайна вокруг вершины ножки.


И, наконец, нормальный шум изменяет положение вершины вдоль базовой поверхности нормально в этой точке. При определении поверхности колпачка в полярной системе координат полезно применить 2d шум Перлина для этих искажений. Для этого я использовал библиотеку noisejs.


```javascript


функция radnoise(a, t) {


return -Math.abs(NOISE.perlin2(t * Math.cos(a), t * Math.sin(a)) * 0,5);


function angnoise(a, t) {


return NOISE.perlin2(t * Math.cos(a), t * Math.sin(a)) * 0,2;


функция nornoise(a, t) {


return NOISE.perlin2(t * Math.cos(a), t * Math.sin(a)) * t;


функция cap_surface(a0, t0) {


// t0 -> t добавлением радиального шума


var t = t0 * (1 + радшум(a, t0));


// вычисляем вектор нормали в t


вар shape_point = cap_shape.getPointAt(t);


вар тангенс = cap_shape.getTangentAt(t);


переменная норма = новый THREE.Vector3 (0,0,0);


const z1 = новый ТРИ.Вектор3(0,0,1);


norm.crossVectors(z1, касательная);


// a0 -> a добавлением углового шума


var a = раздражать (a0, t);


var surface_point = новый THREE.Vector3(


Math.cos(a) * shape_point.x,


shape_point.y,


Math.sin(a) * shape_point.x


// коэффициент нормального шума


вар шум_прибоя = нормальный шум (а, т);


// наконец, точка поверхности


Surface_point.x += norm.x * Math.cos(a) * surfnoise_val;


поверхность_точка.у += норма.у * шум_прибоя;


surface_point.z += norm.x * Math.sin(a) * surfnoise_val;


вернуть точку_поверхности;


Компоненты шума слева направо: радиальный, угловой, нормальный


Остальная часть гриба: чешуя, жабры, кольцо


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


```javascript


буфгеомс = [];


масштабы_число = 20;


n_вершин = 10;


масштаб_радиус = 2;


for (var i = 0; i <scales_num; i++) {


вар масштаб_точки = [];


// выбираем случайным образом центр шкалы на крышке


var a = Math.random() * Math.PI * 2;


вар т = Math.random();


var scale_center = cap_surface(a, t);


// создаем случайное облако точек вокруг scale_center


for (var j = 0; j < n_vertices; j++) {


scale_points.push (новый THREE.Vector3 (


scale_center.x + (1 - Math.random() * 2) * scale_radius,


scale_center.y + (1 - Math.random() * 2) * scale_radius,


scale_center.z + (1 - Math.random() * 2) * scale_radius


// создаем выпуклую геометрию, используя эти точки


var scale_geometry = new THREE.ConvexGeometry(scale_points);


bufgeoms.push(scale_geometry);


// объединяем все эти геометрии в одну BufferGeometry


весы var = THREE.BufferGeometryUtils.mergeBufferGeometries(bufgeoms);


Чешуя, жабры, кольцо и полная геометрия гриба


Проверка на коллизии


Чтобы предотвратить нереальные пересечения при появлении нескольких грибов в сцене, нужно проверять коллизии между ними. [Здесь я нашел фрагмент кода] (https://stackoverflow.com/questions/11473755/how-to-detect-collision-in-three-js), который проверяет столкновения с использованием raycasting из каждой точки сетки.


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


```javascript


for (var vertexIndex = 0; vertexIndex < Player.geometry.attributes.position.array.length; vertexIndex++)


var localVertex = new THREE.Vector3().fromBufferAttribute(Player.geometry.attributes.position, vertexIndex).clone();


var globalVertex = localVertex.applyMatrix4(Player.matrix);


var directionVector = globalVertex.sub( Player.position );


var ray = new THREE.Raycaster( Player.position, directionVector.clone().normalize() );


var столкновенияРезультаты = ray.intersectObjects (collidableMeshList);


если ( CollisionResults.length > 0 && CollisionResults[0].distance < directionVector.length() )


// Произошло столкновение... делаем что-нибудь...


Упрощенные модели для более быстрой проверки коллизий


Рендеринг и стилизация


Изначально я хотел добиться эффекта 2d-рисунка, несмотря на то, что вся генерация делается в 3d. Первое, что приходит на ум в контексте стилизации, это эффект контура. Я не профессионал в шейдерах, поэтому просто взял эффект контура из этого примера. С его помощью я получил красивый карандашный набросок контура гриба.


Эффект контура трех js


Следующее, что нужно сделать на пути к 2D, — правильная раскраска. Текстура должна быть немного шумной и иметь мягкие тени. Есть ленивый лайфхак для тех, кто, как и я, не хочет разбираться с UV-картами. Вместо того, чтобы генерировать настоящую текстуру и оборачивать ее с помощью UV, можно определить цвета вершин объекта с помощью BufferGeometry API. Более того, используя этот подход, цвет вершины также можно параметризовать как функцию угла и положения, поэтому генерация зашумленной процедурной текстуры становится немного проще.


Добавление цвета вершин


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


```javascript


var renderer = new THREE.WebGLRenderer({antialias: true});


контур = новый THREE.OutlineEffect( renderer , {толщина: 0,01, альфа: 1, defaultColor: [0,1, 0,1, 0,1]});


var composer = новый THREE.EffectComposer(контур);


// <- создаем сцену и камеру


var renderPass = новый THREE.RenderPass(сцена, камера);


composer.addPass(рендерингпасс);


var filmPass = новый THREE.FilmPass(


0,20, // интенсивность шума


0,025, // интенсивность строки сканирования


648, // количество строк сканирования


ложь, // оттенки серого


композитор.addPass(filmPass);


композитор.рендер();


Почти готовые, цветные и шумные шрумы


Генерация имени


Для генерации имен я использовал простую цепь Маркова, которая была обучена на 1 тыс. названий грибов отсюда. Для предварительной обработки и токенизации этих имен я использовал библиотеку Python YouTokenToMe. С его помощью я разбил все имена на 200 уникальных токенов и записал их вероятности перехода в словарь javascript. Сторона JS кода только считывает эти вероятности и складывает токены, пока не сгенерирует пару слов.


Вот несколько примеров названий грибов, созданных с использованием этого подхода:


Stricosphaete cinus


Fusarium sium confsisomyc


Этиформансум поник


Hellatatum bataticola


'Armillanata gossypina mortic'


Хоспориум аннифракт


Фла по спортрина


Часть 3: Завершение


Первые 15 грибов отчеканены на fxhash


Подготовка к сбросу


Для подготовки проекта к релизу на fxhash нужно просто заменить все случайные вызовы в коде на метод fxrand() как описано здесь. Основная идея заключается в том, что ваш код должен генерировать уникальные выходные данные для каждого хэша, но точно такие же выходные данные для одного и того же хеша. Затем протестируйте токен в песочнице и, наконец, отчеканьте его, когда чеканка будет открыта. Вот и все!


Это подводит нас к Грибному Атласу (так я назвал эту коллекцию). Вы можете проверить его и увидеть его варианты здесь. Хотя он не был распродан, как некоторые из моих предыдущих работ, я думаю, что это самая продвинутая и сложная вещь, которую я когда-либо делал в генеративном искусстве. Надеюсь, что те, кто отчеканил этот токен, также наслаждались своими грибами в невзаимозаменяемом мире!


Ссылки






Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE