Extended · ~40 хв Рівень: upper-beginner Фокус: модель виконання + експорт

Cables.gl: нодовий граф від нуля до експорту

Браузерний редактор нодового графа, що генерує WebGL-сцену в реальному часі. Урок для того, хто вже бачив ноди (TouchDesigner, shader-графи, Blender geometry nodes) і вміє читати WebGL/three.js-логіку, але в cables руками ще не збирав патч і не доводив його до експортованої сторінки.

Передумови: уявлення про canvas/WebGL і про те, що таке нода з входами-виходами; решта пояснюється на місці.

§Термінологія наперед

Нижче — рівно ті слова, що зустрічаються далі в тексті. Кожне: одне речення суті плюс англійський оригінал, як воно підписане в UI.

PatchДокумент cables — увесь граф разом з ассетами; має URL, автозберігається в акаунт. patch
OpВузол графа: коробочка з портами, що виконує одну дію. operator
NamespaceПовне ім'я op-а через крапку — Ops.Graphics.Meshes.Circle_v4; у пошуку набирається коротка частина. namespace
PortТипізований вхід або вихід op-а; кабель чіпляється саме за порт. port
CableЗ'єднання порт→порт, забарвлене за типом даних. cable
TriggerОсобливий порт: несе не значення, а виклик виконання. trigger port
MainLoopКорінь рендер-дерева; раз на кадр смикає свій trigger і запускає весь ланцюг. Ops.Gl.MainLoop_v2
Texture-портОб'єктний порт із текстурою — аналог TOP у TouchDesigner: результат рендера, що передається далі. texture / object port
Sidebar / op-searchЛіва панель і діалог додавання op-а по Esc; головний спосіб знайти потрібний вузол. op search
AttachmentПрикріплений до op-а файл; GLSL-код шейдера живе саме тут. attachment
SubpatchЗгорнутий шматок графа, поданий як один вузол — для повторюваних ланцюгів. SubPatchOp
VariableІменоване значення, доступне з будь-якої точки патча без кабеля. Vars
FilesВкладка ассетів патча: картинки, звук, 3D, шрифти. files tab
ExportЗбірка патча в zip зі сторінкою, рушієм і ассетами для власного хостингу. export
StandaloneОфлайн-версія редактора на Electron — файли з диска, без хмари. cables standalone
cables-cliЕкспортер із командного рядка для деплой-скриптів і CI. cables-cli
Patch JSONВнутрішній формат патча (граф як дані) всередині експорту — його можна імпортнути назад. patch .json

1Що це і коли брати

cables — це редактор, де замість коду пишеться граф: коробочки-ops із портами, з'єднані кабелями, а на виході — звичайна веб-сторінка з canvas. Розробляє undev (Берлін); рушій і бібліотека ops відкриті під MIT, реєстрація і патчинг безкоштовні, ліцензію купувати не треба.

Чому це окремий інструмент, а не просто «three.js мишкою»: граф дає миттєвий зворотний зв'язок і прибирає цілий клас рутини (компіляція, boilerplate сцени, ручний рендер-цикл), тому чорновий візуал збирається за хвилини. Ціна — менше контролю над архітектурою, ніж у коді.

Як це працює на рівні продукту

Патч живе в акаунті за URL, рендериться в реальному часі просто в редакторі, і будь-якої миті експортується в статичну теку. Усе, що вміє браузер, доступне з коробки: 3D-меші, пост-обробка, відео- й вебкам-текстури, WebAudio-аналіз, MIDI, DOM поверх сцени.

Приклад доречності: аудіо-реактивний фон для лендінга чи кліпу збирається графом швидше, ніж пишеться руками. Типова помилка вибору: тягнути в cables повноцінний застосунок зі станом, роутингом і тестами — це візуальний рушій сцени, а не фреймворк для аплікух; логіку застосунку туди пхати не варто.

Ментальна модель, якщо йти з TouchDesigner: ops ≈ оператори, patch ≈ мережа, texture-порт ≈ TOP. Але cables рендерить деревом виконання згори вниз від MainLoop, а не тягне дані з кінця ланцюга. Саме через цю різницю в перший день «нічого не малюється».
Анатомія op-а: trigger-порт і data-порти
Винести з картинки одне: жовтий кабель — це «коли виконати», кольоровий — «що прочитати». Op без вхідного жовтого кабеля мертвий, скільки б даних у нього не входило.

2Модель: patch → op → port → cable

Уся робота в cables — чотири сутності, вкладені одна в одну. Зрозуміти їхній стосунок важливіше, ніж вивчити будь-який конкретний op: далі все зводиться до цих чотирьох.

Чотири рівні

Ключова теза, яку варто прийняти одразу: trigger-порт — це не дані, це виклик. MainLoop раз на кадр смикає свій trigger, той смикає наступний op, той — наступний. Усе, що не підвішене до цього ланцюга, не виконується взагалі.

Інтерактив: збери trigger-ланцюг

Перемикай з'єднання і дивись прев'ю праворуч. Обірваний жовтий ланцюг = порожній екран — та сама логіка, через яку в реальному патчі «нічого не малюється».

Типова помилка: новачок під'єднує data-кабель (наприклад, Time у rotation) і чекає картинку, забувши, що сам меш не підвішений до trigger-ланцюга. Дані течуть, виклику нема — екран порожній.

3Порти й типи: що куди підключається

Колір кабеля кодує тип, і це не косметика — з'єднати різнотипні порти система не дасть. Знання, який порт що несе, економить більшість часу дебагу: половина «не працює» — це кабель не того типу або не в той порт.

Таблиця типів портів

Тип портуЩо несеКуди типово йде
Triggerвиклик виконання (не значення)trigger-вхід наступного op; корінь — MainLoop
Number / Floatодне числоrotation, scale, колірний канал, індекс, uniform
Stringтекстшлях до файлу, назва, ім'я uniform-а в шейдері
Booleantrue/falseвмикач гілки, прапорець матеріалу
Arrayмасив чисел або об'єктівпозиції інстансів, спектр з аудіо-аналізу
Objectскладений об'єктgeometry, material, shader
Textureоб'єктний порт із текстуроювхід матеріалу, пост-ефекту, наступного рендера (≈ TOP)
⚠ПЕРЕВІР: точні кольори кабелів по типах різняться між білдами cables і темами редактора — конкретний відтінок треба звірити прямо в патчі, не запам'ятовувати з таблиці. Незмінне тут одне: trigger має окремий колір від усіх data-типів.

Приклад і типова помилка

Реактив будується так: number-вихід аудіо-аналізу заводиться в number-порт scale або rotation трансформа. Типова помилка — тягнути object/texture-кабель у number-порт (не приєднається) або чекати, що boolean «сам» стане числом. Різнотипні порти конвертуються тільки через явний op-конвертер.

4Перший патч за 5 хвилин

з'єднання: тільки drag з жовтого trigger-output-порту  ·  op через [esc]: лягає на канвас БЕЗ кабеля  ·  без trigger-кабеля: рендер порожній (checkerboard)

Мінімальний робочий патч — MainLoop, меш і трансформ на одному trigger-ланцюзі. Але ланцюг збирається не додаванням ops поруч, а перетягуванням кабеля з жовтого output-порту. Це та деталь, на якій старий варіант уроку давав порожній екран.

Крок за кроком, як воно є в живому редакторі:

  1. Логін на cables.gl → New Patch. Канвас порожній, по центру напис «This patch is empty. press escape to add operators»; preview — прозорий шаховий фон, ?? FPS.
  2. Esc → op-search → набрати Main → MainLoop (Ops.Gl.MainLoop_v2, «Trigger other ops once every frame») → Add. Корінь рендер-дерева стоїть сам і ще нічого не малює.
  3. Ключовий крок. Навести на жовтий trigger-output MainLoop — окремий жовтий квадратик у нижньому-лівому куті коробки — і потягнути з нього кабель у порожнє місце. З точки, де кабель відпущено, одразу відкривається op-search. Кабель не з'являється сам: його тягне рука.
  4. У цьому op-search набрати circle → Circle (Ops.Graphics.Meshes.Circle_v4, «Draws a circle») → Add. Op лягає вже під'єднаним: жовтий trigger MainLoop заходить у вхід Circle, підписаний Render. У preview — сірий диск на чорному.
  5. Вставити Transform у розрив: клікнути по кабелю MainLoop→Circle і в op-search додати Transform — він сідає між ними, кабель стає MainLoop→Transform→Circle.
  6. Esc → Time (або Sin) → завести number-вихід у number-порт rotation трансформа — коло обертається.
  7. mod + s — зберегти. Статус-тост знизу справа: «Patch saved (N ops / …kb)».
MainLoop ▸trigger→ Transform ▸trigger→ Circle.Render    Time ▸value→ Transform.rotZ

Типова помилка — саме та, що дає порожній екран: додати Circle через [esc] і чекати картинку. Op лягає на канвас, але без вхідного trigger-кабеля він не під деревом виконання MainLoop — preview лишається порожнім, навіть коли обидва op стоять поруч. З'єднання MainLoop→меш ставиться виключно перетягуванням з жовтого output-порту (крок 3), а не автоматично в момент додавання.

Circle з підключеним trigger-кабелем від MainLoop — сірий диск рендериться Той самий Circle без trigger-кабеля — порожній checkerboard
Контрольна пара: той самий Circle, різниця тільки в одному жовтому кабелі. Є trigger від MainLoop — сірий диск рендериться; нема — порожній checkerboard. Рендер вмикає з'єднання, а не присутність op на канвасі.

Чому важливо: це весь принцип у мініатюрі. Далі той самий ланцюг, тільки замість Circle — меш із геометрії й матеріалу, а замість Time — аудіо, миша чи змінна. Але кожна нова ланка чіпляється до trigger-дерева тим самим рухом — drag з жовтого порту.

5UI, op-search і хоткеї

Швидкість у cables — це хоткеї плюс op-search; миша по меню програє на порядок. [mod] тут — Cmd на макі, Ctrl на вінді/лінуксі.

Найкорисніші хоткеї

КлавішаДія
Escдодати op / закрити діалог op-search
mod + sзберегти патч (або файл у редакторі коду)
mod + shift + sзберегти як новий патч
mod + nновий патч
spaceplay/pause таймлайну
space + dragпанорамування полотна
mod + c / mod + v / delкопі / паст / видалити виділені ops
dвимкнути op, не видаляючи його
aAlign selected ops — вирівняти виділені ops
shift + aCompress selected ops vertically — стиснути виділені ops вертикально (не «вирівняти по горизонталі»)
fToggle flow visualization — не пошук: пошуку ops за тегом unconnected хоткеєм mod+f у вкладці Keyboard Shortcuts нема, ⚠ПЕРЕВІР реальний виклик у редакторі
e / t / x / y / c / uEdit op code / Set Title / Unlink selected ops / Cut Cables / Center Selected Ops / Goto parent subpatch

Дрібниці UI, що економлять час

Клік по op-у відкриває панель параметрів; лівий клік по output-полю копіює значення в буфер (зручно вигрібати згенеровані масиви). Виділені ops → Create Area робить кольорову групу; Alt-drag тягне area без вмісту. Правий клік по кінцю кабеля з Shift+Alt перекидає з'єднання на інший порт.

Джерело істини по клавішах — сам редактор: вкладка Keyboard Shortcuts відкривається прямо в патчі. ⚠ПЕРЕВІР: mod+s / mod+shift+s / mod+n у видимій частині вкладки (секція Global) цим кадром не підтверджені; звірятися треба саме там, а не гадати.
Порядок виконання гілок від MainLoop згори вниз
Винести з картинки одне: порядок гілок = їхній порядок згори вниз на полотні. Пересунув op вище — він виконається раніше; клавіша d глушить гілку, не видаляючи її.

6Порядок виконання: trigger vs data

Дев'ять із десяти «нічого не рендериться» — це порядок виконання, а не битий op. Модель проста, але контрінтуїтивна для тих, хто звик до pull-графів (TouchDesigner, шейдер-ноди), де дані тягнуться з кінця. Тут — push згори вниз.

Trigger проти data — у чому різниця

АспектTrigger-портData-порт
Несевиклик: «виконайся зараз»значення: «ось число/об'єкт»
Колір кабеляокремий (жовтий)за типом даних
Задає послідовністьтак — визначає, що коли виконуєтьсяні — просто читається в момент виклику
Без з'єднанняop взагалі не виконуєтьсяпорт бере дефолтне значення
Коли читаєтьсящоразу, як прийшов виклику момент виклику власника порту

Правила, що з цього випливають

Debug-прийом і типова помилка: тимчасово d (disable) на половині гілок і видно, що зникло. Логіка «знайти висячі ops без trigger-кабеля» — найчастіша причина порожнього екрану, але хоткей mod+F → тег unconnected зі старого тексту не підтверджений: ⚠ПЕРЕВІР реальний спосіб виклику цього пошуку в UI.

Тренажер: порти, ops і порядок виконання

Сім питань по моделі виконання й портах. Помилка — правильна відповідь підсвітиться зеленим.

7Файли, CORS і аудіо-реактив

Ассети й зовнішній звук — місце, де патч найчастіше «мовчки ламається» без помилки на екрані. Причина майже завжди одна з двох: CORS або відсутність кліку користувача перед стартом звуку.

Files і зовнішні URL

Аудіо-реактив

WebAudio-ops дають спектр і гучність як звичайні number/array-порти. Реактив = завести цей number у масштаб, колір чи зсув. Типова помилка: браузер не стартує звук без жесту користувача, тому в експорті завжди потрібна кнопка старту — інакше на чужій сторінці буде тиша й порожній аналіз, і це виглядає як «зламаний патч».

8Свої ops, subpatch і змінні

Коли граф починає розповзатися, рятують три речі: власний op, subpatch і змінні. Усі троє про одне — прибрати дублювання й довгі кабелі через пів-полотна.

Власний op

Op — це JS-файл із оголошеними портами:

const inVal  = op.inFloat("value", 0);
const outVal = op.outNumber("result");

inVal.onChange = () => { outVal.set(inVal.get() * 2); };

Subpatch і змінні

SubPatchOp згортає шматок графа в один вузол — доречно, коли той самий ланцюг копіпаститься втретє. Variables дають іменоване значення, доступне з будь-якої точки без кабеля: зручно для глобальних параметрів (гучність, тема, час), які інакше тяглися б кабелем через увесь патч.

⚠ПЕРЕВІР: точні сигнатури op.inFloat/op.outNumber та назви (наприклад, inValueFloat у частині версій) варто звірити з чинним API на docs.cables.gl — назви методів портів між великими версіями рушія мінялися.

9Перформанс і дебаг

Просідання fps у cables майже завжди діагностується двома вбудованими ops — тому здогадуватися не треба, треба виміряти. Спершу цифри, потім оптимізація.

Два вимірювачі

Типові помилки → симптом → лікування

ПомилкаСимптомЛікування
Op не під trigger-ланцюгомнічого не малюєтьсяпід'єднати trigger; mod+F → unconnected
Матеріал після мешамеш білий / без текстурипорядок material ▸ mesh, матеріал вище по гілці
Зовнішній URL без CORSчорна текстура, помилка в консолізаголовок Access-Control-Allow-Origin або залити у Files
Звук без кнопки стартутиша, аналіз = 0кнопка старту (жест користувача) в експорті
Масив перебудовується щокадруfps просідає, Profiler у тисячахбудувати масив раз, кешувати; інстансинг замість десятків мешів
Експорт відкрито по file://порожня сторінкапідняти HTTP-сервер, відкрити через localhost

Мобілка окремо: WebGL1-фолбек, обмежена пам'ять текстур і жодного автоплею звуку. Перевіряти на телефоні треба до експорту, а не після.

10Експорт і вбудовування

Patch → Export дає zip: JS-рушій, JSON патча і ассети — тобто готову статичну сторінку. Але «експорт» — це чотири різні шляхи з різною ціною, і вибір між ними визначає, як патч потім оновлювати.

Матриця експорту

СпосібЩо даєЧого коштує
Patch Export (zip)повна самодостатня сторінка: рушій + JSON + ассетиважить більше; оновлення = переекспорт і перезаливка
Export without filesтой самий zip без ассетів — вони роздаються з власного CDNтреба самому стежити за шляхами й CORS ассетів
iframe embedнайшвидше в чужу CMS / лендінгокремий контекст; керувати сценою ззовні складніше
cables-cliекспорт зі скрипта в CI / деплой без кліківпотрібен standalone-білд і токен; GUI-майстра нема

Як віддати сторінку

  1. Розпакувати zip у теку на своєму хостингу.
  2. Відкрити index.html — це вже готова сторінка з canvas.
  3. Далі або віддати як є, або смикати патч зі своєї сторінки: cables API дає доступ до змінних і зовнішніх тригерів, тобто власний JS міняє параметри сцени на льоту.

Патч-експорт (JSON графа) можна імпортнути назад через ZIP-Upload у «My Data» — це резервна копія самого графа, не сторінки.

⚠ПЕРЕВІР: локально по file:// експорт не запуститься — рушій вантажить ассети по HTTP. Треба підняти python3 -m http.server у теці й дивитися на localhost. Наявність саме готового index.html у корені zip варто підтвердити на чинній версії експортера — структуру архіву cables оновлював.

11Standalone і CLI

Для роботи без хмари й для автоматизації є два окремі інструменти поза браузерним редактором. Обидва беруть той самий патч, але знімають різні обмеження.

Приклад: нічний білд візуалу для сайту — cables-cli експортує патч, скрипт кладе результат у веб-корінь, сторінка оновилась без відкриття редактора. Типова помилка: чекати від standalone тих самих шляхів до ассетів, що й у хмарі — офлайн вони резолвляться від диска, і забуті відносні шляхи ламають експорт.

12Приватність і шеринг патча

Патч за замовчуванням прив'язаний до акаунта, але видимість і право редагування налаштовуються окремо — це варто зрозуміти до того, як ділитися посиланням. Публічний патч видно й копіювати іншим, приватний — ні.

Ділитися можна лінком (перегляд/копія) або запросити в команду на редагування. Типова помилка: дати публічний лінк на патч із приватними ассетами чи ключами в тексті op-а — усе, що в патчі, їде разом із ним.

⚠ПЕРЕВІР: точні рівні приватності й ліміти безкоштовного акаунта (кількість приватних патчів, обсяг Files) залежать від чинного тарифу cables — актуальні цифри треба звіряти в акаунті, а не запам'ятовувати.

13Частинки на GPU: emit → update → render

модель: emit → update → render, щокадру  ·  стан частинки: у буфері на GPU, персистить між кадрами  ·  op: Ops.Extension.GlParticles

Частинки в cables — це не ще один op у trigger-ланцюзі, який «віддає число далі». Це окрема модель, де між кадрами живе буфер стану тисяч частинок, і кожен кадр цей буфер спершу поповнюється, потім просувається, потім малюється одним проходом. Три фази:

Схема emit → update → render з буфером стану частинок між кадрами
Винести з діаграми: три фази йдуть одним ортогональним ланцюгом emit→update→render, і буфер між ними — не миттєве значення, а стан, що персистить від кадру до кадру.

Чому це не звичайний op-ланцюг. У звичайному ланцюгу порт несе одне миттєве значення, яке читається в момент виклику trigger-а і забувається. Тут стан не миттєвий: буфер лежить у GPU-пам'яті (текстура або VBO) і накопичується між кадрами — update бачить те, що emit/update записали раніше. Op не має «одного числа на виході»; він тримає масив на частинку, який еволюціонує в часі.

Атрибути частинки

АтрибутТипСенс
positionvec2 / vec3де частинка зараз
velocityvec2 / vec3напрям і швидкість руху за крок симуляції
lifetime / agefloatскільки лишилось жити; на нулі — respawn або зникнення
sizefloatрозмір точки / спрайта при render
color / seedvec4 / floatколір або зерно рандому на конкретну частинку

Приклад використання: іскри/дим/поле точок під музику — emit сипле частинки з темпом, прив'язаним до number-виходу аудіо-аналізу; update штовхає їх velocity + шум і гасить по lifetime; render малює як спрайти, а size/color беруться з буфера. Реактив тут заходить у параметр фази (темп емісії, сила поля), а не в атрибут окремої частинки.

Типова помилка. Трактувати атрибут як миттєве число — завести один Number у «velocity» і чекати, що симуляція оживе. Атрибут — це буфер на частинку; одне значення в порт або перезадає весь буфер константою (усі частинки однакові, рух вироджується в один вектор), або взагалі не туди. Вплинути на стан треба через фазу update (сила, шум, dt), а не підстановкою скаляра.

⚠ПЕРЕВІР усі конкретні деталі UI GlParticles — назви ops (чи emit/update/render це окремі ops, чи один composite), точні імена портів і полів, чи Ops.Extension.GlParticles — один op чи набір. Живого кадру частинок серед скріншотів нема, конкретику UI треба вивірити прямо в редакторі.

§Use cases

Аудіо-реактивний візуал під рейв / DJ-сет
WebAudio-аналіз дає спектр і гучність як number/array-порти; вони заводяться в масштаб, колір і зсув мешів. Граф збирається швидше, ніж пишеться шейдер руками, а реактив крутиться прямо в редакторі під живий вхід. Обов'язково — кнопка старту звуку, бо браузер не дасть автоплей.
Вбудований патч як фон сторінки / лендінга
Експорт кладеться в теку хостингу, а index.html віддається як фон під контент. Через cables API власний JS сторінки міняє параметри сцени (тема, швидкість, реакція на скрол) — сцена перестає бути статичною картинкою і реагує на поведінку відвідувача.
Живий VJ-сет із патча в редакторі
Патч грає прямо в браузері в реальному часі; параметри крутяться мишею, MIDI-контролером або аудіо-входом без переекспорту. Таймлайн (space) і Variables дозволяють тримати кілька станів сцени й перемикатися між ними наживо.
Обмін патчем і рев'ю в команді
Патч має URL, тож показати колезі — це надіслати лінк, а не файл. Публічний патч можна відкрити й скопіювати собі як заготовку; приватний — розшарити точково на редагування. Патч-експорт (JSON) служить резервною копією графа поза акаунтом.
Прототип шейдера / пост-ефекту без boilerplate
GLSL пишеться в attachments одного op-а, а ShaderInfo показує, що скомпілювалось. Обв'язку сцени (рендер-цикл, uniforms, текстурні входи) дає граф, тож ітерація по самому шейдеру йде без ручного каркаса three.js.
Нічний білд візуалу через CLI
cables-cli експортує патч зі скрипта, деплой кладе результат у веб-корінь — сторінка оновлюється без відкриття редактора. Доречно, коли візуал править дизайнер у патчі, а публікація автоматизована в CI.

§Практика й інструменти

cables.gl/docs

Офіційна документація — найточніше джерело по ops, портах, експорту й API. Перше, куди йти, коли UI виглядає інакше, ніж в уроці (cables оновлюється часто).

cables.gl/patches

Галерея публічних патчів. Будь-який відкривається в редакторі й копіюється собі — найшвидший спосіб розібрати, як зроблений чужий ефект.

cables.gl/standalone

Сторінка завантаження офлайн-редактора (Electron, mac/win/linux) — коли треба працювати з файлами з диска без хмари.

Discord спільноти cables

Живі відповіді від undev і користувачів — сюди з edge-кейсами по портах, шейдерах і експорту, які не покриті доками.

github.com/cables-gl/cables

Репозиторій рушія й бібліотеки ops під MIT. Корисно, щоб глянути реальну реалізацію op-а, коли поведінка неочевидна.

github.com/cables-gl/cables_dev

Дев-збірка для standalone і cables-cli — звідси піднімається локальна інсталяція й автоматизований експорт.

§Що далі