Кажется, я сильно опоздал на вечеринку Вороного с тех пор, как я начал изучать Three.js около 2 лет назад… Сначала это выглядит сложно, но на самом деле алгоритм довольно прост. Со временем я стал большим поклонником новых шаблонов, которые я изучаю в сфере Three.js. На этот раз я кратко расскажу вам, как сделать Вороного на сфере стильно 😎.

Репозиторий кода и живая демонстрация

Как обычно, я начал проект со своего threejs-starter-template. Я должен признать, что посылка — не лучший упаковщик для three.js; его действительно легко настроить, но есть некоторые очевидные недостатки, например. не в состоянии распознать и, таким образом, связать файлы gltf/glb/hdr. Я обязательно найду другой упаковщик, который решит эти проблемы.

Полный код проекта: https://github.com/franky-adl/voronoi-sphere

Живая демонстрация:



Установка

Сцена three.js на самом деле представляет собой всего лишь один SphereGeometry, который использует ShaderMaterial, который использует пользовательские шейдеры, которыми я его кормлю. Никакого освещения, никаких текстур, просто сфера с шейдерами. Конечно, есть обычные функции, такие как статистика и OrbitControls.

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

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

Позвольте мне объяснить их в следующем.

Объяснение концепции

Итак, о Вороном…

Чтобы понять алгоритм Вороного, я настоятельно рекомендую следующие два ресурса, потому что я никогда не объясню его лучше, чем они:

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

Приведенные выше примеры демонстрируют только двумерный случай. Чтобы добиться эффекта в 3D, мы эффективно применяем ту же методологию, то есть вычисляем минимальные расстояния от пикселей до ближайших точек ячеек, но сетка ячеек теперь становится 3x3x3, а не просто 3x3, потому что мы находимся в 3D.

Таким образом, новая функция шейдера Вороного (с подробными комментариями кода) становится:

vec2 voronoi( in vec3 x, in float time )
{
    // current cell coordinates
    vec3 n = floor(x);
    // pixel coordinates in current cell
    vec3 f = fract(x);

    // initialize m with a large number
    // (which will be get replaced very soon with smaller distances below)
    vec4 m = vec4(8.0);

    // in 2D voronoi, we only have 2 dimensions to loop over
    // in 3D, we would naturally have one more dimension to loop over
    for( int k=-1; k<=1; k++ ) {
        for( int j=-1; j<=1; j++ ) {
            for( int i=-1; i<=1; i++ )
            {
                // coordinates for the relative cell  within the 3x3x3 3D grid
                vec3 g = vec3(float(i),float(j),float(k));
                // calculate a random point within the cell relative to 'n'(current cell coordinates)
                vec3 o = hash3d( n + g );
                // calculate the distance vector between the current pixel and the moving random point 'o'
                vec3 r = g + (0.5+0.5*sin(vec3(time)+6.2831*o)) - f;
                // calculate the scalar distance of r
                float d = dot(r,r);

                // find the minimum distance
                // it is most important to save the minimum distance into the result 'm'
                // saving other information into 'm' is optional and up to your liking
                // e.g. displaying different colors according to various cell coordinates
                if( d<m.x )
                {
                    m = vec4( d, o );
                }
            }
        }
    }

    return vec2(m.x, m.y+m.z+m.w);
}

Это все, что вам нужно знать для начала. Существуют также различные варианты шума Вороного, но в основном идеи схожи.

Далее, пользовательский код окраски.

Чтобы получить непосредственное визуальное представление о результате 3D-функции Вороного, можно попробовать сократить код основного фрагмента до такого:

void main() {
    vec2 res = voronoi(v_pos*3., u_time*0.3);
    gl_FragColor = vec4( vec3(res.x), 1.0 );
}

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

Уже красиво само по себе, правда?

Но на самом деле мы можем сделать гораздо больше отсюда:

// darken by pow
vec3 mycolor = vec3(pow(res.x, 1.5));

// emphasis on blue
float blue = mycolor.b * u_bFactor;

// cut off the blueness at the top end of the spectrum
mycolor.b = blue * (1. - smoothstep(0.9,1.0,res.x));

// adjust red+greeness using pcurve such that greyness/whiteness
// is only seen at a limited range within the spectrum
mycolor.r = pcurve(mycolor.r, 4.0, u_pcurveHandle);
mycolor.g = pcurve(mycolor.g, 4.0, u_pcurveHandle);

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

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

Как вам может быть интересно, работы только с синим каналом недостаточно для достижения синеватого эффекта, потому что в красном и зеленом каналах все еще есть значения! В конце моих экспериментов я больше всего доволен использованием pcurve. Я использую pcurve для настройки каналов r и g так, чтобы они имели более высокие значения примерно в диапазоне 0,6–0,8 и падали до 0, приближаясь к 1,0:

Вы можете поиграть с функцией и вводом pcurve на https://thebookofshaders.com/edit.php#05/pcurve.frag

Это объясняет, почему вы видите, что границы ячеек становятся более белыми, поскольку это области, где все цветовые каналы имеют высокие значения, но затем они внезапно становятся очень синими в следующих частях границ, когда r и g быстро снижаются до 0, но все еще высокое значение голубизны на короткое расстояние:

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

Больше безумно крутых шейдеров Вороного