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:
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
}
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Ę.
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();
}
void draw() {
background(0);
// tutaj wywołaj fukcje rysujące na teksturach
drawStrobo(0);
mapper.drawContent(textures);
mapper.drawGUI();
}
void draw() {
background(0);
// tutaj wywołaj fukcje rysujące na teksturach
drawStrobo(0);
drawStrobo(1);
drawStrobo(2);
mapper.drawContent(textures);
mapper.drawGUI();
}
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();
}
void draw() {
background(0);
// tutaj wywołaj fukcje rysujące na teksturach
drawFeedback(0);
drawFeedback(1);
drawFeedback(2);
mapper.drawContent(textures);
mapper.drawGUI();
}
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();
}
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);
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();
}
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();
}
0 komentarzy