Zubli dostał nowe ciało. Czego nauczyliśmy się, animując maskotkę piksel po pikselu.

Zubli to twarz naszej recepcji AI dla klinik stomatologicznych. Pacjent spotyka go w rogu strony kliniki późnym wieczorem, gdy recepcja dawno zamknięta, i pyta o cenę, wolny termin albo o to, czy ból zęba może poczekać do jutra. Postać, z którą ludzie rozmawiają o 23:00, ma jedno zadanie: sprawić, żeby rozmowa w pierwszych dwóch sekundach wydawała się bezpieczna.
W tym tygodniu zbudowaliśmy ciało Zubliego od zera. Nie osobowość, nie logikę rozmowy, tylko ciało. Najciekawszy nie jest efekt, lecz zakręt, który musieliśmy wziąć w połowie drogi. Ta historia pokazuje też, jak wygląda dzień pracy nad DentalDesk.pl.
Po co klinice maskotka w czacie?
Bo pacjent nie pisze do „systemu”. Pisze do kogoś. Maskotka obniża próg pierwszej wiadomości, zwłaszcza u osób, które boją się dentysty albo wstydzą się zapytać o cenę. Zubli nie diagnozuje i nie udaje lekarza. Odpowiada tylko na podstawie danych zatwierdzonych przez klinikę, a rano recepcja dostaje gotową kartę zgłoszenia z zapisem rozmowy.
Próba pierwsza: robot z brył
Pierwszy Zubli był „prawdziwym” 3D. Postać składała się w przeglądarce z kilkudziesięciu prostych brył: zaokrąglonych pudełek, kul, kapsuł i torusów. Każda miała własny materiał, całość oświetlał mały studyjny zestaw świateł, a animacja działała klatka po klatce. Z tego podejścia wyszły trzy prototypy: wesoły żółty robot ze stokrotką, wyluzowany w dzianinowej bluzie i dentystyczny „Doc” w czepku.
Poruszały się pięknie. Na tle grafiki koncepcyjnej wyglądały jednak jak zabawki obok fotografii. Faktura dzianiny, lakierowane odblaski na czarnym wizjerze, szwy bluzy, sznurówki sneakersów: każdy taki detal współczesny model graficzny renderuje w sekundy, a ręczne modelowanie zajęłoby tydzień.

Próba druga: zostawić piksele, dodać ruch
Odwróciliśmy problem. Zamiast modelować detale, wzięliśmy gotowy obraz i nauczyliśmy go ruszać się. Odpowiadają za to trzy warstwy.
Klatki są źródłem prawdy. Wygenerowaliśmy tę samą postać w sześciu pozach, w identycznym kadrze i na płaskim szarym tle. Skrypt w Pythonie wycina tło (segmentacja), wymazuje świecące oczy (inpainting) i wyrównuje każdą klatkę na dwóch kotwicach: na punkcie między oczami i na linii stóp. Zmiana pozy jest odtąd płynnym przenikaniem, nie skokiem. Przy przenikaniu pojawiały się szare obwódki. Zniknęły po przejściu na alfę premultiplikowaną, czyli zapis, w którym kolor piksela jest z góry pomnożony przez jego krycie.

Kukiełka, nie film. Każda klatka to tekstura na siatce z około pięciu tysięcy wierzchołków. Shader dzieli sylwetkę na miękko ważone strefy: głowa obraca się wokół szyi, tułów oddycha od stóp, rękawy zginają się w barkach, a cała postać kołysze się i spłaszcza przy lądowaniu. Ten sam pomysł napędza postacie Live2D w grach: deformuj obraz zamiast renderować model.
Oczy rysowane na żywo. Wizjer jest czystą czarną powierzchnią. Oczy rysujemy na osobnym, małym płótnie i składamy w shaderze fragmentów wewnątrz prostokąta wizjera, na tej samej siatce. Obracają się więc razem z głową. Daje to mruganie, spojrzenie podążające za kursorem, uśmiech, serduszka, a nawet linie CRT z pierwotnego renderu.

Jeden filtr CSS z cieniem po cichu zabierał połowę klatek. Na zintegrowanej karcie graficznej było 29 klatek na sekundę, po usunięciu filtra — 60.
Bryły 3D czy animowany render?
| Robot z brył 3D | Animowany render | |
|---|---|---|
| Jakość detalu | ograniczona, „zabawkowa” | jak na grafice koncepcyjnej |
| Zakres ruchu | nieograniczony | duże gesty z klatek, mikroruch z matematyki |
| Nowa poza | dni modelowania | jedna klatka przez pipeline |
| Nowa postać | nowy model od zera | ta sama ścieżka, inna klatka bazowa |
| Kolor pod markę kliniki | przemalowanie materiałów | kolor oczu z palety kliniki |
| Wydajność | zależna od liczby brył | zmierzone 60 klatek na sekundę na zintegrowanej grafice |

Jak to powstało
To była praca dwóch agentów. Claude zaprojektował pipeline, shader i runtime. GPT wygenerował klatki poz z jednego obrazu referencyjnego. Koordynował je herdr, nasz multiplekser terminali dla agentów kodujących. Przeglądarka bez interfejsu sprawdzała każdy gest zrzutami ekranu, zanim obejrzał je człowiek.
Czego nas ta budowa nauczyła
- Mierzymy animacje na zintegrowanej grafice. Jeden filtr CSS z cieniem zabierał połowę klatek: było 29, miało być 60.
- Sześć wyrównanych klatek zastępuje całą animację, jeśli siatka ma około pięciu tysięcy wierzchołków i ważone strefy.
- Szare obwódki przy przenikaniu klatek to problem zwykłej alfy. Alfa premultiplikowana rozwiązała go w shaderze.
- Oczy nie muszą być geometrią. Osobne płótno i złożenie w shaderze fragmentów dają mruganie i spojrzenie podążające za kursorem.
- Dwa agenty koordynowane przez herdr dzielą pracę jak dwie osoby: Claude pisał pipeline i shader, GPT robił klatki.
Zamiast budować robota, który próbuje wyglądać jak obrazek, wzięliśmy obrazek i nauczyliśmy go ruszać się. Cały ruch mieści się w sześciu klatkach i pięciu tysiącach wierzchołków.