Написать пост

Создание GIF-анимаций из шума на языке Processing

Аватар Klara Oswald

Статья познакомит вас с функцией Noise() языка Processing, с помощью которой можно создавать gif-анимации из картинок с шумом.

Обложка поста Создание GIF-анимаций из шума на языке Processing

Это руководство научит вас рисовать объекты из функции шума на языке 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();
}
		

Можно получить такой результат:

Создание GIF-анимаций из шума на языке Processing 1

Параметр 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);
    }
  }
}
		

Результат:

Создание GIF-анимаций из шума на языке Processing 2

Единственным способом визуализации трёхмерного шума является использование времени в качестве третьего измерения.

			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();
}
		

Результат:

Создание GIF-анимаций из шума на языке Processing 4

Фактически здесь используется 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");
}
		

Результат:

Создание GIF-анимаций из шума на языке Processing 5

Оживление шума

Это хороший способ сделать идеальное зацикливание. Он будет работать везде, где используется 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) в качестве входных данных. Поскольку при создании гиф-анимации используются положительные и отрицательные значения, возникнет нежелательный эффект, если шум будет симметричен. Но этого можно избежать, если применить сдвиг круга.

Следите за новыми постами
Следите за новыми постами по любимым темам
6К открытий6К показов