Mapping i Processing

lip 30, 2020 | Interaktywny Mapping

Tą część poradnika podzielimy na dwie części – pierwsza z nich będzie wprowadzeniem do środowiska Processing dla początkujących. W drugiej części zajmiemy się tworzeniem generatywnych animacji na potrzeby interaktywnego mappingu.

Wprowadzenie

Processing to narzędzie dedykowane do kreatywnego programowania i nauki. Składa się z edytora kodu (IDE -integrated development environment) oraz zestawu bibliotek i narzędzi do wykorzystania podczas tworzenia własnych programów. Processing oparty jest na uproszczonej wersji języka programowania Java oraz znacznie upraszcza wiele zadań związanych z programowaniem grafiki w tym języku. Ze względu na otwartość, wokół środowiska istnieje bardzo duża społeczność, co skutkuje istnieniem wielu
dodatków i rozszerzeń funkcjonalności oraz wysokiej jakości dokumentacji wraz z przykładami.

Kolejną zaletą jest fakt, że biblioteka posiada kilka trybów pracy, niektóre z nich polegają na wykorzystaniu biblioteki OpenGL do realizacji podstawowych operacji graficznych co znacznie zwiększa jej wydajność. Istnieje możliwość pisania własnych shaderów w języku GLSL.

W dalszej części poradnika wymagana będzie podstawowa znajomość programowania, w tym m.in.:

  • Czym jest funkcja
  • Czym jest zmienna
  • Podstawowe typy i struktury danych (int, float, char, String, tablice)

Jeżeli to Twoja pierwsza styczność z programowaniem, polecamy naukę rozpocząć właśnie od środowiska Processing, dostępnych jest wiele poradników, które w przystępny sposób pokazują podstawy programowania i koncepcje wykorzystywane w Processingu. https://processing.org/tutorials/

W Processingu programy tworzymy w postaci tzw. Sketch-ów. Składają się one z dwóch głównych części – inicjalizacji programu (setup) oraz rysowania (draw). Generowany obraz składa się z pikseli, każdy z nich posiada informację na temat jego koloru. W środowisku Processing, domyślnie kolor podajemy w formacie ARGB (A – alpha – przezroczystość piksela, R – red – czerwony, G – green – zielony, B – blue – niebieski). Wszystkie funkcje w Processingu przyjmujące kolor jako argument pozwalają na podanie tylko jednej wartości – operujemy wtedy w skali szarości. Dozwolone wartości dla każdego kanału kolorystycznego to liczby całkowite z zakresu <0; 255>

Poniżej pokazane jest okno programu Processing i przykładowy sketch:

Po jego uruchomieniu (za pomocą przycisku ze strzałką – play) powinien wygenerować taką grafikę:

Sketch można wyłączyć za pomocą przycisku z kwadratem (stop – obok play), klikając w krzyżyk na belce okna ze sketchem lub wciskając klawisz ESC na klawiaturze.

Program domyślnie będzie wykonywał funkcję draw dla każdej rysowanej klatki, co daje możliwość tworzenia programu generującego animację.

Dla przykładu zmodyfikujemy powyższy program, aby uzyskać animację zmiany koloru prostokąta. W tym celu potrzebujemy stworzyć zmienną, w której będziemy przechowywać aktualny kolor prostokąta, wartość tą będziemy stopniowo modyfikować w każdej kolejnej klatce powodując płynną zmianę koloru prostokąta od czarnego do białego w przeciągu 255 kolejnych klatek. W Processingu dostępna jest zmienna frameCount zawierająca numer aktualnie generowanej klatki. Wiemy już, że wartość określająca kolor w Processigu musi zawierać się w przedziale <0; 255>, wykorzystamy więc operator % (modulo – reszta z dzielenia) do ograniczenia wartości zmiennej frameCount do tego przedziału.


void setup() {
  size(500, 500); // ustawienie rozmiaru generowanej grafiki
  noStroke();     // wyłączenie rysowania konturów obiektów
}

void draw() {
  background(0); // zamalowanie tła na kolor czarny
  fill(frameCount % 255); // zmiana koloru prostokąta zależna od numeru generowanej klatki
  rect(200, 200, 100, 100); // rysowanie prostokąta
}
Po uruchomieniu sketcha, zobaczymy taką animację:
Na oficjalnej stronie Processingu znajduje się rozpiska wszystkich dostępnych funkcji wraz ze szczegółowym opisem i przykładami https://processing.org/reference/. Jest to niezwykle przydatne podczas pisania własnych programów. Nauka programowania wymaga sporo praktyki, ale jesteś już w dobrym miejscu. Niniejszy wstęp miał na celu oswoić Cię ze środowiskiem Processing i pomóc wykonać pierwsze kroki. W dalszej części poradnika zajmiemy się stworzeniem generatywnych animacji, które wykorzystamy w naszym mappingu.

Mapping w Processingu

Ta część poradnika będzie bardziej zaawansowana. Aby stworzyć mapping, musimy najpierw wiedzieć jak będzie wyglądał obiekt, na którym odbędzie się projekcja. W naszym przypadku będą to trzy ścianki sześcianu. Processing nie posiada wbudowanych funkcjonalności dedykowanych do konfiguracji mappingu, dlatego przygotowaliśmy Sketch zawierający kod pozwalający dostosować generowany obraz do bryły na której będzie wyświetlany. Bazowy projekt możesz pobrać tutaj.

Po jego uruchomieniu w Processigu zobaczymy prosty interfejs pozwalający skonfigurować nasz mapping. Myszką można łapać i przeciągać rogi czworokątów. Aktywna figura zaznaczona jest kolorem łososiowym. Możemy zmienić aktywną figurę naciskając TAB na klawiaturze. Aby zapisać aktualne ułożenie figur należy wcisnąć klawisz S, aby wczytać poprzednio zapisane ułożenie należy wcisnąć L. Konfiguracja mappingu zapisywana jest w pliku data/mappings. Aby wyłączyć tryb edycji mappingu wciśnij SPACJĘ.

Jeżeli poprawnie wykonałeś ustawienia projektora w pierwszym artykule z poradnika, szkic powinien wyświetlić się na projektorze. Jeśli tak się nie stało, w funkcji setup() zmodyfikuj numer wyświetlacza podany do funkcji fullScreen(P2D, 2); (gdzie 2 to numer wyświetlacza, https://processing.org/reference/fullScreen_.html). Dopasuj figury tak, aby ich krawędzie pokrywały się z krawędziami sześcianu (tego na którym robimy mapping) i zapisz konfigurację klawiszem S. Sketch jest zrobiony tak, aby w prosty sposób dało się dodać do niego własne animacje – nakłada na figury wszystko co narysujesz na teksturach zdefiniowanych jako PGraphics textures[]. PGraphics to obiekt pozwalający nam generować grafikę i korzystać z niej w innych częściach programu jak z obrazka https://processing.org/reference/PGraphics.html. W naszym programie będziemy korzystać z 3 tekstur, po jednej dla każdej widocznej ściany sześcianu.

Animacje

Stworzymy trzy proste i efektowne animacje – strobo, feedback i glitch. Zacznijmy pierwszej i najprostszej z nich – strobo. W każdej klatce nadamy naszej teksturze losowy odcień szarości. Sama animacja jest trywialna, ale za jej pomocą poznasz w jaki sposób połączyć Twój kod generujący animację z resztą kodu służącą do konfiguracji mappingu. Przejdź do zakładki content (w niej umieścimy kod generujący animacje na teksturach) i dodaj następującą fukcję:

void drawStrobo(int gIndex) {
  PGraphics g = textures[gIndex];
  g.beginDraw();
  g.background(random(255));
  g.endDraw();
}
Funkcja ta posiada argument int gIndex – jest to index tekstury na której ma zostać umieszczona animacja. W pierwszej linijce kodu pobieramy teksturę o tym indexie do zmiennej o nazwie g. Kod modyfikujący zawartość tekstury (PGraphics) musimy umieścić pomiędzy beginDraw() i endDraw() (https://processing.org/reference/PGraphics.html). W przypadku naszej animacji jest to po prostu ustawienie koloru tła (funkcja background) na losowy odcień szarości. Aby zobaczyć animację na projekcji wywołaj funkcję drawStrobo w głównej funcji draw() naszego programu w następujący sposób:

void draw() {
  background(0);
 
  //  tutaj wywołaj fukcje rysujące na teksturach
  drawStrobo(0); 
 
  mapper.drawContent(textures);
  mapper.drawGUI();
}
Dodając drawStrobo(0) narysujemy animację na ściance o numerze 0, dostępne są jeszcze ścianki o numerach 1 i 2. Aby narysować animację na pozostałych ściankach możesz dodać kolejne wywołania funcji drawStrobo z odpowiednimi numerami ścianek, np.:

void draw() {
  background(0);
 
  //  tutaj wywołaj fukcje rysujące na teksturach
  drawStrobo(0); 
  drawStrobo(1);
  drawStrobo(2);  
 
  mapper.drawContent(textures);
  mapper.drawGUI();
}
Po uruchomieniu sketcha zobaczysz Twoją animację wyświetloną na sześcianie. Skoro wiesz już w jaki sposób dodać własną animację do naszego sketchu do mappingu, pokażemy jak zrobić dwie kolejne animacje. Możesz też stworzyć swoje własne i wykorzystać je w projekcji. Następną animacją będzie feedback – sprzężenie zwrotne, efekt często wykorzystywany w syntezie video. Polega na wykorzystaniu obrazu uzyskanego w poprzedniej klatce do stworzenia aktualnej klatki. Efekt ten w najprostszej postaci można zobaczyć w miejscu, gdzie dwa lustra skierowane są w swoją stronę lub gdy wyświetlisz na telewizorze obraz z podłączonej kamery, a kamerę skierujesz na ekran telewizora – zobaczysz wówczas coś przypominającego nieskończony tunel. Aby uzyskać taki efekt w Processingu, na naszej teksturze będziemy rysować nieco powiększoną teksturę wygenerowaną w poprzedniej klatce. Aby efekt sprzężenia zwrotnego był widoczny, potrzebujemy narysować na teksturze jakiś obiekt, w naszym przypadku będzie to kwadrat. W zakładce content stwórz fukcję drawFeedback, jak pokazano poniżej:

void drawFeedback(int gIndex) {
 PGraphics g = textures[gIndex];
 g.beginDraw();
 g.image(g, - 4, - 4, g.width + 8, g.height + 8);
 g.strokeWeight(3);
 g.stroke(255);
 g.fill(0);
 g.rect(g.width / 2 - 30, g.height / 2 - 30, 60, 60);
 g.endDraw();
}

Aby zobaczyć animację, wywołaj ją w funcji draw naszego sketcha:

void draw() {
  background(0);
 
  //  tutaj wywołaj fukcje rysujące na teksturach
  drawFeedback(0); 
  drawFeedback(1);
  drawFeedback(2);
 
  mapper.drawContent(textures);
  mapper.drawGUI();
}

Po uruchomieniu zobaczysz, że animacja trwa tylko przez krótką chwilę, po czym przestaje się zmieniać. Kwadrat musi zmieniać swój kolor w kolejnych klatkach, w przeciwnym wypadku cała tekstura szybko zaleje się w całości jego kolorem (sprzężenie zwrotne się wysyci). Zmodyfikuj funkcję drawFeedback tak, aby w kolejnych klatkach ustawiać kolor kwadratu na biały lub czarny. Aby jakieś zdarzenie działo się w zależności od numeru klatki, wykorzystamy wbudowaną zmienną frameCount tak jak w przykładzie na początku tego artykułu. Chcąc aby zmiana działa się np. w co czwartej klatce, podzielimy wartość frameCount przez 4 (dzielenie bez reszty)

void drawFeedback(int gIndex) {
 PGraphics g = textures[gIndex];
 g.beginDraw();
 g.image(g, - 4, - 4, g.width + 8, g.height + 8);
 g.strokeWeight(3);
 g.stroke(((frameCount / 4) % 2) * 255);
 g.fill(0);
 g.rect(g.width / 2 - 30, g.height / 2 - 30, 60, 60);
 g.endDraw();
}

Ostatnią modyfikacją, będzie nadanie kwadratowi delikatnego ruchu, pozwoli to uzyskać złudzenie deformowania się sześcianu podczas mappingu. Jednym ze sposobów na uzyskanie oscylującego ruchu jest wykorzystanie funkcji trygonometrycznych do wyznaczania kolejnych pozycji jakiegoś obiektu. Ich argumentem będzie odpowiednio przeskalowana wartość zmiennej frameCount. Podobnie jak wyżej, aby kontrolować prędkość ruchu będziemy dzielić jej wartość. Zmodyfikuj fragment funkcji odpowiedzialny za rysowanie kwadratu:

float arg = ((frameCount / 3.0) % 100 / 100.0) * TWO_PI;
g.rect(sin(arg) * 10 + g.width / 2 - 30,
       cos(arg) * 10 + g.height / 2 - 30,
       60, 60);

Po uruchomieniu sketcha nasza animacja powinna wyglądać tak (przeskok animacji poniżej wynika z gifa – w Twoim sketchu wszystko powinno być płynne):
Zabawa z generatywnymi animacjami w dużej mierze polega na eksperymentowaniu z różnymi wartościami parametrów. Zachęcamy do zmiany niektórych z nich i obserwowaniu rezultatów. Przejdźmy teraz do stworzenia animacji o nazwie glitch. W tym celu najpierw stworzymy animację szumu, a następnie zabawimy się z kodowaniem kolorów. Animacja szumu bazuje na przykładzie Noise2D dołączonym do Processingu. Zmodyfikowaliśmy ją, aby dodać nieco ruchu w podobny sposób jak w poprzedniej animacji. Stwórz w zakładce content funkcję drawGlitch jak pokazano poniżej:

void drawGlitch(int gIndex) {
  PGraphics g = textures[gIndex];
  g.beginDraw();      
  g.loadPixels();
  noiseDetail(8, 0.01);
  float xoff = 0.3 * sin(frameCount / 80.0);
  float inc = 0.002;
  for (int x = 0; x < g.width; x++) {
      xoff += inc;
      float yoff = 0.3 * cos(frameCount / 90.0);
      for (int y = 0; y < g.height; y++) {
        yoff += inc;
        int i = x + y * g.width;
        g.pixels[i] = color(noise(xoff, yoff) * 255);
      }
  }
  g.updatePixels();
  g.endDraw();
}

Pamiętaj, aby podobnie jak w przypadku poprzednich animacji, wywołać funkcję drawGlitch w funkcji draw naszego sketcha. Po uruchomieniu widać delikatnie zmieniające się odcienie szarości na teksturze. Teraz pora na nieco magii. Kolory w Processingu przechowywane są tak jak pisaliśmy wcześniej jako zestawy liczb z zakresu <0; 255>, można powiedzieć, że wartość każdego kanału koloru da się zapisać na 1 bajcie. Pełny kolor składa się z czterech takich liczb czyli sekwencji ARGB. Aby szybciej wykonywać operacje na pikselach, wartości te zakodowane są w ramach jednej liczby typu int (4 bajty – ARGB – to tyle samo co zmienna typu int) – w informatyce nazywamy to kodowaniem. Nic nie stoi na przeszkodzie, aby zapomnieć na chwilę o formacie w jakim przechowywane są kolory i potraktować liczbę zawierającą zakodowaną informację o 4 kanałach kolorystycznych jako zwykłą liczbę, wykonać na niej jakieś obliczenia, a następnie zinterpretować uzyskany wynik jako kolor. Podejście takie potrafi przynieść bardzo ciekawe i nieprzewidywalne rezultaty – eksperymentujmy! Oto ukończona funkcja drawGlitch, dająca bardzo ciekawe rezultaty:

void drawGlitch(int gIndex) {
  PGraphics g = textures[gIndex];
  float alpha = noise(frameCount / 100.0) * 0.07;
  g.beginDraw();      
  g.loadPixels();
  noiseSeed(gIndex);
  noiseDetail(8, 0.01);
  float xoff = 0.3 * sin(frameCount / 80.0);
  float inc = 0.002;
  for (int x = 0; x < g.width; x++) {
      xoff += inc;
      float yoff = 0.3 * cos(frameCount / 90.0);
      for (int y = 0; y < g.height; y++) {
        yoff += inc;
        int i = x + y * g.width;
        g.pixels[i] = color(noise(xoff, yoff) * 255);
      }
  }
  g.pixels[0] = (int)(alpha * g.pixels[0]);
  for (int i = 1; i < g.pixels.length; i++) {
      g.pixels[i] = (int)(g.pixels[i - 1] + alpha * (g.pixels[i] - g.pixels[i - 1]));
  }
  g.updatePixels();
  g.endDraw();
}

W tym artykule pokazaliśmy podstawy korzystania ze środowiska Processing oraz przykład, w jaki sposób można zrealizować mapping za jego pomocą. Pokazane tutaj sposoby tworzenia animacji są jedynie małą próbką możliwości jakie daje kreatywne programowanie. Aby naprawdę skorzystać z tego artykułu zachęcamy do zabawy ze stworzonymi algorytmami, ich parametrami itd. W kolejnych częściach pokażemy, w jaki sposób można stworzyć animację w programie After Effects, a następnie zajmiemy się dostosowywaniem naszego sketcha tak, aby stworzony mapping był interaktywny.

0 komentarzy

Wyślij komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *