English · Русский · WebGL Ripper на GitHub
Проект начинался как доработка WebGL Ripper 0.6 от Rilshrink, а закончился переписыванием. Чтобы показать разницу на деле, обе версии установлены как настоящие расширения в один и тот же браузер и запущены на одинаковых тестовых страницах. В оригинале ничего не менялось, ему выставлены его лучшие настройки.
| Оригинал 0.6 | Эта версия (1.1.2) | |
|---|---|---|
| Сцена three.js с тенями и пост-обработкой | неправильно: все меши в начале координат, без нормалей, чужая текстура на всех мешах, сохранена в 16 раз больше | правильно: меши на местах, нормали, цвета, настоящая текстура |
| Время до файла, сцена three.js | 0,56–0,82 с | 0,17 с (GLB), 0,24–0,42 с (OBJ ZIP) |
| Зависание страницы при экспорте, сцена three.js | 385–585 мс | 41–80 мс |
| 3000 вызовов отрисовки | за 3 минуты файла нет, страница зависла, +1,3 ГБ | 0,28 с (GLB) / 0,6–0,8 с (OBJ ZIP), +42–78 МБ |
| 250 000 вершин, две текстуры 4096² | 3,9 с, зависание 1,5 с, +816 МБ, ZIP 60 МБ, модель не на месте | 1,6 с (GLB) / 2,7 с, зависание меньше 0,2 с, +177–253 МБ, ZIP 5,6 МБ, на месте |
| Буферы в стиле Unity / Emscripten | мусор | точно |
| WebGL создаётся во время загрузки страницы | не перехватывается (3 из 7 тестовых страниц) | перехватывается |
Страницы, которые не вызывают gl.clear() |
никогда не заканчивает | перехватываются |
| Время кадра, 3000 вызовов отрисовки, без захвата | +0,36–0,53 мс (≈ в 2 раза дольше) | ±0,1 мс (погрешность) |
| Время кадра, 2000 загрузок буферов WebGL 1 | +0,85–1,1 мс | +0,5–1,1 мс |
Абсолютное время зависит от компьютера; важно соотношение двух версий. Сцены и скрипт лежат в
tests/compare/, см. Как повторить.
| Страница | Что это | Зачем |
|---|---|---|
three.html |
three.js r170: текстурированный куб, тор-узел, икосаэдр и пол; тени от всех мешей, пост-обработка EffectComposer, оси-гизмо ViewHelper |
типичный современный просмотрщик |
bench.html |
WebGL 2: 3000 мелких текстурированных вызовов отрисовки за кадр из одного общего буфера, 8 текстур; WebGL 1: N вызовов bufferSubData за кадр |
много объектов; цена каждого кадра |
heavy-late.html |
один меш на 250 000 вершин, сдвинутый матрицей модели в (1, 2, 3), две текстуры 4096×4096 | большая модель, как из фотограмметрии или генерации ИИ |
heap-late.html |
WebGL 1, буферы загружаются срезами одной большой «кучи», которая сразу перезаписывается | так загружают данные Unity и приложения на Emscripten |
early.html, heap.html, heavy.html |
контекст создаётся, пока страница ещё загружается | момент запуска самого расширения |
Страницы -late создают WebGL через полсекунды после загрузки. Оригинал подгружает свой код асинхронно и пропускает
контексты, созданные раньше; варианты -late позволяют измерить его на том, что он может захватить.
Сколько расширение стоит странице всё время — просто тем, что установлено.
| Сцена | Без расширения | Оригинал 0.6 | Эта версия |
|---|---|---|---|
| 3000 вызовов отрисовки за кадр (WebGL 2) | 0,40–0,62 мс | 0,88–1,00 мс | 0,39–0,71 мс |
| 2000 загрузок буферов за кадр (WebGL 1) | 0,70–0,78 мс | 1,55–1,83 мс | 1,22–1,79 мс |
Оригинал оборачивает 24 функции WebGL на каждом контексте и ведёт собственную копию состояния GL в JavaScript, поэтому за это платит каждый вызов отрисовки, привязки и указателя. Эта версия перехватывает отрисовку только пока записывается кадр; остаётся лишь копирование, без которого не обойтись для загрузок WebGL 1 (WebGL 1 не умеет читать буферы обратно).
| Объект (где он на самом деле) | Оригинал 0.6 | Эта версия |
|---|---|---|
| Текстурированный куб в (−2; 0,7; 0) | 24 вершины в (0; 0; 0) | 8 вершин в (−2; 0,7; 0), с нормалями |
| Тор-узел в (0,4; 1,2; 0) | 4025 вершин в (0,1; 0; 0) | 3840 вершин в (0,42; 1,2; −0,02), с нормалями |
| Икосаэдр в (2,6; 0,8; −0,5) | 240 несвязанных вершин в (0; 0; 0) | 42 сваренные вершины в (2,6; 0,8; −0,5), с нормалями |
| Пол 12 × 12 в начале координат | стоит вертикально (12 × 12 × 0) | лежит горизонтально (12 × 0 × 12) |
| Нормали | нет | есть |
| Цвета материалов | нет | Kd / baseColorFactor из шейдера |
| Текстура | клетка куба 256×256 сохранена как 4096×4096 и назначена всем 4 мешам | 256×256, только у куба |
| Файл | ZIP 0,95 МБ | ZIP 0,13 МБ / GLB 0,14 МБ |
| Время / самый долгий кадр | 0,56–0,82 с / 385–585 мс | 0,24–0,42 с (OBJ), 0,17 с (GLB) / 41–80 мс |
Оригинал записывает от одного gl.clear() до следующего. В этой сцене это, по всем признакам, проход карты теней
(отрисовка только глубины, без нормалей): никакой знакомой ему трансформации не применено, поэтому все объекты
оказались в начале координат, а текстура — та, что осталась привязанной. Эта версия записывает весь кадр и оставляет
проход, который доходит до экрана.
| Оригинал 0.6 | Эта версия | |
|---|---|---|
| Результат | за 3 минуты файла нет; страница перестала отвечать | 3000 мешей на своих местах, 8 текстур |
| Время | — | 0,28 с (GLB), 0,62–0,76 с (OBJ ZIP, 3011 файлов) |
| Самый долгий кадр | страница перестала отвечать | 68–112 мс |
| Дополнительная память | +1335 МБ (+197 МБ в GPU) | +42–78 МБ |
На каждый вызов отрисовки оригинал дважды перебирает все активные uniform-переменные программы и читает матрицы и
сэмплеры через getUniform — синхронные запросы к процессу GPU, — а каждую текстуру читает в угаданном размере
4096×4096 через 2D canvas. Если в кадре нет gl.clear(), он вообще не заканчивает.
| Оригинал 0.6 | Эта версия | |
|---|---|---|
| Результат | число вершин верное, но в (0; 0; 0) вместо (1; 2; 3) | на месте, в (1; 2; 3) |
| Время | 3,9 с | 1,6 с (GLB), 2,7 с (OBJ ZIP) |
| Самый долгий кадр | 1539 мс | 134–186 мс |
| Дополнительная память | +590 МБ (+226 МБ в GPU) | +169–245 МБ (+8 МБ в GPU) |
| Файл | ZIP 60 МБ (без сжатия) | ZIP 5,6 МБ / GLB 14 МБ |
Когда та же страница создавала контекст во время загрузки (heavy.html), оригинал не захватил ничего.
| Оригинал 0.6 | Эта версия | |
|---|---|---|
| Контекст создан после загрузки | OBJ на 1,1 МБ с одной вершиной в (3,4·10³⁸; 3,4·10³⁸; 3,4·10³⁸) | квадрат, точно |
| Контекст создан во время загрузки | ничего | квадрат, точно |
Оригинал хранит ссылку на загруженный срез вместо копии; к моменту чтения «куча» уже переиспользована и содержит
другие данные (здесь байты 0x7F7F7F7F, что как float равно 3,4·10³⁸).
early.html создаёт контекст в <script> в заголовке страницы — так часто делают небольшие демо и загрузчики
движков. Код оригинала приходит позже и этот контекст не видит: файла нет. Эта версия запускается раньше скриптов
страницы и сохраняет треугольник точно.
| Оригинал 0.6 | Эта версия | |
|---|---|---|
| Форматы | OBJ + MTL + PNG | GLB (glTF 2.0) и/или OBJ + MTL + PNG |
| Выбор, что сохранить | — | 3D-превью на странице |
| Сохранение одного объекта | — | клик по нему (режим выбора) |
| Проходы теней, гизмо, пост-обработка, дубликаты | сохраняются, если попали в записанный проход | убираются |
| Vertex array objects (WebGL 2) | не учитываются | читаются у WebGL |
| Размер текстур | известен только для загрузок из картинки/canvas, иначе 4096×4096 | отслеживается для всех способов загрузки |
| Сжатые, float, luminance-текстуры | не читаются | читаются через проход отрисовки |
| Цвет под прозрачными пикселями | теряется (toDataURL домножает на альфу) |
сохраняется, PNG байт в байт |
| Сварка вершин, недостающие нормали | — | есть |
| Скачивание | несжатый ZIP или по файлу с паузами 0,5 с | один сжатый ZIP или один GLB, потоково |
| Как настройки попадают в страницу | через скрытый <div> в странице |
из хранилища расширения, сразу |
| Кнопка, статус, сочетания клавиш | только клавиша Insert | значок, окно, настраиваемые клавиши, сочетания браузера |
| Firefox | инструкция в его README | пакет для Firefox 128+, проверен тестами |
| Тесты | — | тесты движка, three.js, большого экспорта и расширения целиком в Chrome и Firefox |
Нужны Node.js 22+, Chrome и распакованный в папку оригинал.
node tests/compare/compare.mjs --original=<папка WebGL Ripper 0.6>
node tests/compare/compare.mjs --original=<папка> --scenes=three,heavy-late --engines=orig,new,new-glb
Скрипт собирает пакет релиза, запускает свежий headless Chrome на каждую версию, печатает по строке на страницу и
оставляет скачанные файлы и results.json во временной папке системы (webglripper-compare). Память замеряется
только в Windows. Тяжёлым страницам нужно несколько ГБ свободной памяти; скрипт останавливает прогон, если её
становится мало.