Это руководство научит вас рисовать объекты из функции шума на языке Processing. Вы изучите простой способ создания зацикленных GIF-анимаций на основе функции шума.
Функция шума
В языке Processing есть функция noise()
, которая при некоторых входных данных выдаёт значения от 0 до 1 (с центром в точке 0,5). Эти значения являются случайными, но они всегда повторяются при повторении входных данных и соли.
Другое свойство этой функции заключается в том, что шум непрерывен: для близких входных данных вы получаете почти одинаковые значения. Вычисление значений шума выполняется быстро, не нужно вычислять предыдущие значения. Эта функция в Processing иногда называется шумом Перлина.
С помощью небольшого фрагмента кода:
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
}
void draw(){
float scale = 0.03;
beginShape();
for(int x = 0; x<width;x++){
float y = height*noise(scale*x);
vertex(x,y);
}
endShape();
}
Можно получить такой результат:
Параметр scale
используется потому, что без него значения изменяются слишком быстро.
Также следует помнить, что функция шума симметрична: noise(x) = noise(-x)
.
При каждом запуске эскиза кривая выглядит по-разному. Но можно всегда получать один и тот же результат, используя функцию noiseSeed()
. Ещё обратите внимание на функцию noiseDetail()
, которая изменяет параметры шума (например, его плавность).
Есть одна хитрость: чтобы нарисовать ещё одну независимую кривую, можно просто взять значения шума, далёкие от предыдущих. Например, сначала используете noise(scale*x)
, а затем noise(scale*x + 1000)
.
Эскиз выше демонстрирует пример одномерного шума, двумерный принимает 2 значения с плавающей запятой и возвращает значения от 0 до 1. Это можно представить как уровень яркости на 2D-изображении.
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
}
void draw(){
float scale = 0.01;
for(int x = 0; x<width;x++){
for(int y = 0; y<height;y++){
stroke(255*noise(scale*x,scale*y));
point(x,y);
}
}
}
Результат:
Единственным способом визуализации трёхмерного шума является использование времени в качестве третьего измерения.
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
noiseDetail(5);
}
void draw(){
background(0);
float scale = 0.01;
loadPixels();
for(int x = 0; x<width;x++){
for(int y = 0; y<height;y++){
float col = 255*noise(scale*x,scale*y,10*scale*frameCount);
pixels[x + width*y] = color(col);
}
}
updatePixels();
if(frameCount<=50){
saveFrame("tuto###.png");
}
}
Результат:
Чтобы анимация зацикливалась, вам понадобится четырёхмерный шум. Функция шума в языке Processing ограничена 3D, поэтому знакомьтесь с openSimplex noise. Чтобы использовать её, вставьте этот код в подобную вкладку вашего Processing-эскиза.
OpenSimplexNoise noise;
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
noise = new OpenSimplexNoise();
}
void draw(){
float scale = 0.03;
beginShape();
for(int x = 0; x<width;x++){
float ns = (float)noise.eval(scale*x,0);
float y = map(ns,-1,1,0,height);
vertex(x,y);
}
endShape();
}
Результат:
Фактически здесь используется 2D шум с нулём в качестве второго входного параметра, потому что реализация openSimplex noise имеет только 2D, 3D и 4D шум.
Прежде чем использовать эту функцию для создания зацикленных анимаций, давайте нарисуем что-нибудь более интересное, отличное от шума. Используем пороговое значение для шума, чтобы нарисовать пиксели чёрным или белым:
OpenSimplexNoise noise;
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
noise = new OpenSimplexNoise();
}
void draw(){
background(0);
float scale = 0.02;
loadPixels();
for(int x = 0; x<width;x++){
for(int y = 0; y<height;y++){
boolean b = (float)noise.eval(scale*x,scale*y) > 0;
float col = b?255:0;
pixels[x + width*y] = color(col);
}
}
updatePixels();
saveFrame("tuto3.jpg");
}
Результат:
Оживление шума
Это хороший способ сделать идеальное зацикливание. Он будет работать везде, где используется 1D или 2D шум. Замените noise.eval(scale*x,scale*y)
на noise.eval(scale*x,scale*y,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t))
.
Чтобы зацикливание анимации было идеальным, сделаем круг в двух новых измерениях шумового пространства. Немного проблематично реализовать графическую интерпретацию шума в 2D, но с 1D шумом это означало бы, что нужно брать значения на линии для каждого кадра и перемещать эту линию вдоль цилиндра с увеличением времени.
Давайте применим это к рисунку с пороговым значением:
OpenSimplexNoise noise;
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
noise = new OpenSimplexNoise();
}
int numFrames = 75;
float radius = 1.0;
void draw(){
float t = 1.0*frameCount/numFrames;
background(0);
float scale = 0.02;
loadPixels();
for(int x = 0; x<width;x++){
for(int y = 0; y<height;y++){
boolean b = (float)noise.eval(scale*x,scale*y,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t)) > 0;
float col = b?255:0;
pixels[x + width*y] = color(col);
}
}
updatePixels();
println(t);
if(frameCount<=numFrames){
saveFrame("tuto2###.gif");
}
if(frameCount == numFrames){
println("finished");
stop();
}
}
Результат:
radius
— это параметр, который будет контролировать, насколько сильно изменится анимация.
Можно анимировать пример кривой с помощью той же техники:
OpenSimplexNoise noise;
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
noise = new OpenSimplexNoise();
}
int numFrames = 150;
float radius = 1.5;
void draw(){
float t = 1.0*frameCount/numFrames;
background(0);
float scale = 0.02;
beginShape();
for(int x = 0; x<width;x++){
float ns = (float)noise.eval(scale*x,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t));
float y = map(ns,-1,1,0,height);
vertex(x,y);
}
endShape();
println(t);
if(frameCount<=numFrames){
saveFrame("tuto2###.jpg");
}
if(frameCount == numFrames){
println("finished");
stop();
}
}
Результат:
Теперь давайте применим технику к предыдущему примеру с яркостью. Гифка будет выглядеть размытой, потому что в используемой здесь реализации openSimplex noise не так много деталей (но эта плавность отлично выглядит).
OpenSimplexNoise noise;
void setup(){
size(500,500);
background(0);
stroke(255);
noFill();
noise = new OpenSimplexNoise();
}
int numFrames = 75;
float radius = 1.5;
void draw(){
float t = 1.0*frameCount/numFrames;
background(0);
float scale = 0.02;
loadPixels();
for(int x = 0; x<width;x++){
for(int y = 0; y<height;y++){
float ns = (float)noise.eval(scale*x,scale*y,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t));
float col = map(ns,-1,1,0,255);
pixels[x + width*y] = color(col);
}
}
updatePixels();
println(t);
if(frameCount<=numFrames){
saveFrame("tuto3###.jpg");
}
if(frameCount == numFrames){
println("finished");
stop();
}
}
Результат:
Теперь вы знаете, что каждый Processing-эскиз, использующий 1D или 2D шум, можно легко превратить в идеально зацикленную анимацию.
Давайте рассмотрим, как можно сделать несколько более сложных и интересных картинок. Начнём с этого:
Белые точки генерируются случайным образом внутри круга. Каждая из них подвергается горизонтальному и вертикальному смещению на основе 2D шума. Интенсивность смещения становится равной нулю вблизи границы круга. Анимация выполняется с использованием той же техники 4D шума.
Вот код для генерации рамок GIF: noisetraj.pde
Обратите внимание, что метод drawCurve()
просто рисует круг, но его можно легко изменить.
Пример:
Здесь некоторые точки начинаются с решётки и рисуют свою траекторию вслед за шумовым полем. Это поле изменяется с использованием техники 4D-шума (код).
Пороговое значение для 2D шума определяет, что будет отображаться: «/» или «\». Снова анимирование с 4D шумом (код).
Линии представляют 2D поле на основе 2D шума. Всё то же анимирование с 4D шумом (код).
Вот кое-что посложнее. В основном здесь используются пороговые кривые 1D шума, чтобы определить, каким цветом будут отрисовываться элементы: чёрным или белым. Параметры для анимации каждого столбца различны. Большой диск рисуется в центре в режиме исключения (код для чего-то похожего).
В каждой строке используются независимые значения из 1D шума, анимация с трёхмерным шумом (код).
Заключение
Теперь вы знаете, как использовать функцию шума и различные её вариации для рисования собственных анимаций.
P.S.: openSimplex noise не симметрична, поэтому не возникает никаких проблем при использовании radius*cos(TWO_PI*t)
или radius*sin(TWO_PI*t)
в качестве входных данных. Поскольку при создании гиф-анимации используются положительные и отрицательные значения, возникнет нежелательный эффект, если шум будет симметричен. Но этого можно избежать, если применить сдвиг круга.
Перевод статьи «Drawing from noise, and then making animated loopy GIFs from there.»