惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
Engineering at Meta
Engineering at Meta
D
DataBreaches.Net
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
B
Blog RSS Feed
GbyAI
GbyAI
P
Proofpoint News Feed
aimingoo的专栏
aimingoo的专栏
MyScale Blog
MyScale Blog
D
Docker
阮一峰的网络日志
阮一峰的网络日志
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recorded Future
Recorded Future
美团技术团队
The Register - Security
The Register - Security
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tailwind CSS Blog
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
The Blog of Author Tim Ferriss
博客园 - 司徒正美
量子位
B
Blog
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
MongoDB | Blog
MongoDB | Blog
A
About on SuperTechFans
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
L
LangChain Blog
Latest news
Latest news
S
SegmentFault 最新的问题
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cisco Talos Blog
Cisco Talos Blog
F
Full Disclosure
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
W
WeLiveSecurity
T
Tenable Blog
T
Tor Project blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Почему Chrome весит 7 000 Марио или как сжать «Змейку» в 1 000 раз
inkedsymon ( · 2026-04-29 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение11 мин

Охват и читатели23

Аналитика

На вашем диске лежит семь одинаковых моделей птицы Додо. Не благодарите — это ARK заботливо положил их вам в каждое DLC.

Раньше Super Mario Bros весила 40 КБ. Сейчас одно обновление Chrome — это ~7 000 таких Марио. Как мы дошли до жизни такой, и почему все идет по кругу?

В статье пройдем путь от тайлов NES до Neural Texture Compression и рассмотрим змейку в трех версиях: по трем вехам сжатия. Одна из них в 1 120 раз меньше первой. И это не та, в которой ИИ.

Один скриншот тяжелее целой игры

Начнем с интересного факта. Super Mario Bros. на NES — одна из самых продаваемых игр в истории — занимала 40 КБ. Это 32 КБ программного кода и 8 КБ графики. В этих 40 КБ уместились 32 уровня, десятки врагов, физика, музыка и тот самый звук монетки (который вы, возможно, сейчас услышали в голове).

Обычный скриншот этой игры в PNG весит больше, чем вся игра целиком.

Теперь к расчетам. Установочный файл Google Chrome весит около 140 МБ. После установки, с учетом кеша и служебных файлов, объем разрастается до 280 МБ. А 286 720 КБ ÷ 40 КБ, эквивалентны, примерно, 7 000 копям Super Mario Bros.

То есть браузер весит как 7 000 полноценных видеоигр 1985 года.

Но Chrome — это еще цветочки. ARK: Survival Evolved со всеми DLC занимает 435 ГБ, а это уже около 11 млн копий Марио. Причем разработчики ARK не удосужились сделать общий репозиторий ассетов: каждое DLC тащит за собой дубликаты моделей. У вас на диске буквально семь одинаковых моделей птицы Додо.

Хаб Call of Duty (со всеми загруженными кампаниями последних частей и Warzone) переваливает за 250–300 ГБ. Microsoft Flight Simulator — 250+ ГБ. И это не какой-то уникальный графический шедевр в каждом пикселе, а в значительной степени дублирование ассетов и расчет на дешевые терабайты.

Как же мы пришли из точки «целая игра в 40 КБ» в точку «одно обновление стрелялки весит как маленькая библиотека»? И главное — как мы возвращаемся обратно?

NVIDIA L4 в облаке Selectel от 22,61 ₽/час

Для запуска современных языковых и мультимодальных моделей.

Подробнее →

Как раньше умещали слона в спичечный коробок

Тайловая магия NES

Разработчики Super Mario Bros. не хранили уровни как картинки. Вместо этого весь визуал состоял из тайлов — крошечных блоков 8×8 пикселей. Облако, куст, холм — это один и тот же тайл, просто перекрашенный через палитру. Куст Марио — это дефолтное облако, опущенное на землю и покрашенное в зеленый.

Источник

Источник

Весь спрайтшит игры — это несколько сотен таких восьмипиксельных квадратиков, которые комбинируются и переиспользуются бесконечно. Picture Processing Unit консоли сам собирал из них картинку, разработчикам нужно было только сказать: «поставь тайл #14 в координату (X, Y) с палитрой N».

А уровни? Они не хранились целиком. Вместо матрицы тайлов использовались команды: «отсюда начинается земля», «здесь поставь трубу высотой 3», «включи паттерн неба». По сути, это процедурная инструкция, а не растровая карта.

Палитра как прием

NES поддерживал всего 25 цветов на экране одновременно, и разработчики научились извлекать из этого максимум — меняя палитру, они, в действительности, меняли визуальный стиль целой локации. Они могли использовать одни и те же спрайты для подземелья и замка, но в результате локации выглядели совершенно по разному.

Музыка без аудиофайлов

Звуковой чип NES (APU) имел пять каналов: два прямоугольных, треугольный, шумовой и DPCM. Никаких MP3, WAV или OGG. Музыка в игре — это ноты и команды для синтезатора. Весь саундтрек Super Mario Bros. — каждая мелодия и каждый звуковой эффект — занимает пару килобайт инструкций.

Для сравнения: один несжатый сэмпл удара барабана в современной игре весит больше, чем весь звуковой код Марио.

Трюки 90-х и 00-х для оптимизации

С ростом мощности железа росли и аппетиты разработчиков, но некоторые умудрялись вмещать графику и звук в смешные по нынешним меркам объемы.

.kkrieger — шутер в 96 КБ

В 2004 году немецкая демогруппа .theprodukkt выпустила .kkrieger — полноценный 3D-шутер от первого лица, который занимал 96 КБ. Это меньше, чем аватарка в Telegram.

Внутри нет ни одного готового ассета. Все текстуры, модели, звуки и музыка генерировались процедурно и прямо при запуске. Вместо хранения текстуры как растра использовалась история ее создания — последовательность операций: «возьми шум Перлина, наложи размытие, добавь дисторсию». Пара десятков байт инструкций превращались в мегабайты визуала.

Они не хранили готовые текстуры и спрайты — вместо этого движок генерировал их при запуске по математическим формулам. Поэтому .kkrieger весит 96 КБ, в нем были только инструкции по созданию контента.

А вот если бы .kkrieger хранил ассеты традиционным способом, он бы весил 200–300 МБ — как современная небольшая инди-игра. А так — 96 КБ. Скриншот этого шутера весит больше, чем сам исполняемый файл.

Кармак и его черная магия

Джон Кармак в id Software тоже не разбрасывался ресурсами. Doom (1993) использовал BSP-деревья для разделения уровней — движок быстро отбрасывал невидимые полигоны, чтобы не нагружать процессор лишней отрисовкой. Quake (1996) ввел PVS — заранее рассчитанный набор того, что может видеть игрок из каждой точки карты. Если стены закрывают комнату, то  она вообще не рисуется.

Эти техники экономили и память, и трафик, и вычислительные ресурсы.

Трюки с LOD и импостерами

Level of Detail — древний, но рабочий подход: далекие объекты рендерятся упрощенными моделями. Дерево вдали — это не 50 000 полигонов с текстурами листьев, а прямоугольник с фотографией дерева (импостер).

Дистанция < 10м:

полная модель (50 000 полигонов)

10–50 м

упрощенная (5 000)

50–200 м

billboard (импостер, 2 треугольника)

> 200 м

не рендерится вообще

Этот подход до сих пор используется в каждой современной AAA-игре.

MIP-маппинг

Зачем хранить текстуру 4 096×4 096 для объекта, который на экране занимает 16 пикселей? MIP-цепочки — это набор заранее рассчитанных уменьшенных копий текстуры. GPU автоматически выбирает нужный уровень в зависимости от расстояния. Это экономит VRAM и убирает мерцание далеких текстур.

Эпоха «можно, а зачем»

Настало будущее: жесткие диски стали дешевыми, интернет — быстрым, а дедлайны — жесткими. На оптимизацию стали забивать сразу по нескольким фронтам.

К этому добавляется, во-первых, избыточная детализация: современные AAA-игры поставляются с несжатыми 4K-текстурами для объектов, которые игрок никогда не рассмотрит вблизи. Какой-нибудь мусорный бак в переулке, куда игрок не заглянет, имеет 4 096×4 096 пикселей, восемь PBR-каналов и весит 64 МБ.

Во-вторых, ресурсы перестали упаковывать плотно. Вместо одной аудиодорожки и декодинга на лету применяются предекодированные версии для каждой платформы, каждого формата, каждой частоты дискретизации. Для разработчика это экономит время: не нужно писать конверт под каждую платформу (и заодно следить за всеми совместимостями). Платит за это уже пользователь лишними гигабайтами на своем диске. 

А Unreal Engine 4/5 при первом запуске компилирует шейдеры локально, создавая гигабайты кеша. Это ускоряет рендер, но вопрос «а может, скомпилировать заранее?» как будто не приходит в голову.

Средний размер AAA-игры вырос в 100–150 раз за 20 лет. Для сравнения: культовая Max Payne 2 в 2003 году занимала на жестком диске около 1,5 ГБ. Call of Duty: Black Ops 6 требует 102 ГБ, но для полной установки с лаунчером CoD HQ и Warzone потребуется уже порядка 150 ГБ. А со всеми компонентами и кэшем размер может переваливать за 300 ГБ. Графика стала лучше, безусловно. Но в 100 раз? Серьезно?

Моя попытка: оптимизация змейки тремя эпохами

Теория — это хорошо, но я хотел пощупать руками, как каждая эпоха оптимизации чувствуется на практике. Поэтому я собрал ассет-пайплайн для классической змейки в трех вариантах. Геймплейную логику писать не стал, нас интересует только вес данных и три философии хранения: 

  • FAT — как писали бы в 2020-х, не думая о размере;

  • OLDSCHOOL — как писали бы в 90-х, экономя каждый байт;

  • AI — как пишут в 2026, с ИИ (или как будут писать завтра).

Змейка-FAT

Первая версия нарочито раздутая. 

  • Графика — 16 отдельных файлов (64×64 пикселя) в формате 32-bit RGBA. На диске PNG ужмет их до ~30 КБ, но в распакованном виде (raw RGBA, как они лежат в памяти при рендере) это 256 КБ — именно этот размер и считается «настоящим» для GPU, так что будем использовать это значение.

  • Звук поедания яблока — эффекты в WAV (44,1 кГц; 16 бит), потому что «а вдруг кто-то услышит разницу»;

  • Карта уровня — полный JSON-файл с подробным описанием координат и свойств каждой клетки;

  • Музыка — полноценный фоновый трек в формате MP3 (три дорожки: eat_sound, die_sound и bgm).

Каждый спрайт — отдельный файл.

Для экономии места в статье, сразу собрал все спрайты в один спрайтшит.

Для экономии места в статье, сразу собрал все спрайты в один спрайтшит.

Итоговый размер ассетов: ~3.7 МБ. 

Змейка-OLDSCHOOL: палитры, RLE, синтез

Теперь перевоплощаемся в программиста, у которого каждый байт на счету.

Шаг 1: Спрайтатлас + палитра

Все 16 спрайтов упакованы сразу в один файл. Вместо 32-битного RGBA будет индексированная палитра на 16 цветов. Змейка и так пиксельная, так зачем ей куча оттенков?

from PIL import Image

def build_atlas_indexed(sprite_paths, palette_size=16):
    """Собираем все спрайты в один атлас с общей палитрой"""
    # Склеиваем в одну полосу
    sprites = [Image.open(p).convert('RGBA') for p in sprite_paths]
    total_w = sum(s.width for s in sprites)
    atlas = Image.new('RGBA', (total_w, sprites[0].height))
    
    x = 0
    for s in sprites:
        atlas.paste(s, (x, 0))
        x += s.width
    
    # Квантизация: 16.7М цветов → 16 цветов
    indexed = atlas.quantize(colors=palette_size, method=Image.MEDIANCUT)
    return indexed
На гифке измеряем PNG-файл атласа на диске. В таблице ниже для FAT считается raw RGBA в VRAM — поэтому там сжатие ×128, а здесь скромнее.

На гифке измеряем PNG-файл атласа на диске. В таблице ниже для FAT считается raw RGBA в VRAM — поэтому там сжатие ×128, а здесь скромнее.

Шаг 2: Процедурный фон

Текстуру травы можно сгенерить из 28 байт:

import numpy as np
from scipy.ndimage import zoom

def generate_grass(size=512, seed=42):
    """Трава из шума — 28 байт инструкций вместо 48 КБ текстуры"""
    rng = np.random.RandomState(seed)
    texture = np.zeros((size, size))

    for octave in range(6):
        freq = 2**octave
        amp = 0.5**octave
        noise = rng.rand(freq, freq)
        texture += zoom(noise, size / freq, order=1)[:size, :size] * amp

    # Нормализация → зеленая палитра
    t = (texture - texture.min()) / (texture.max() - texture.min())
    return np.stack([
        (t * 25 + 15).astype(np.uint8),  # R — приглушенный
        (t * 80 + 40).astype(np.uint8),  # G — доминирующий
        (t * 20 + 10).astype(np.uint8),  # B — почти нет
    ], axis=-1)

Шаг 3: Звук через синтез

Вместо WAV-файлов у нас будут ноты и команды. Хруст яблока, конечно, в три ноты уместить сложно, мягко говоря, но попробовать можно.

import mido

def create_eat_sound():
    """Звук поедания яблока: 3 ноты = 48 байт вместо 86 КБ WAV"""
    mid = mido.MidiFile()
    track = mido.MidiTrack()
    mid.tracks.append(track)

    track.append(mido.Message('program_change', program=80))  # Square lead
    for note, dur in [(72, 60), (76, 60), (84, 120)]:  # C-E-C октавой выше
        track.append(mido.Message('note_on', note=note, velocity=100, time=0))
        track.append(mido.Message('note_off', note=note, time=dur))
    return mid

def create_bgm():
    """Фоновая музыка: 1.2 КБ вместо 3.2 МБ"""
    mid = mido.MidiFile(ticks_per_beat=480)
    track = mido.MidiTrack()
    mid.tracks.append(track)

    track.append(mido.Message('program_change', program=81))
    pattern = [60, 63, 67, 72, 67, 63]  # Cm арпеджио
    for bar in range(32):
        transpose = [0, 0, 5, 3][bar % 4]
        for note in pattern:
            track.append(mido.Message('note_on', note=note + transpose, velocity=80, time=120))
            track.append(mido.Message('note_off', note=note + transpose, time=120))
    return mid

Шаг 4: Карта через RLE

Карта змейки — это в основном пустое поле с границами. Идеальный случай для RLE:

def rle_encode(grid):
    """Run-Length Encoding: 'AAAAABBBCC' → '5A3B2C'"""
    flat = [cell for row in grid for cell in row]
    encoded = []
    i = 0
    while i < len(flat):
        val = flat[i]
        count = 1
        while i + count < len(flat) and flat[i + count] == val and count < 255:
            count += 1
        encoded.extend([count, val])
        i += count
    return bytes(encoded)

# Поле 30×20 = 600 клеток
# Наивно: 600 байт
# RLE: 112 байт (бордюр из стен + строки по шаблону «1 стена, 28 пустых, 1 стена»)

Итого после олдскула:

Компонент

FAT

OLDSCHOOL

Сжатие

Спрайты (16 шт)

256 КБ 

~2 КБ

×128

Фон (текстура)

48 КБ

28 байт

×1 700

Звуки + музыка

3,4 МБ

~1,2 КБ

×2 800

Карта

2,4 КБ

112 байт

×21

Итого ассеты

~3,7 МБ

~3,3 КБ

×1 120

По сути, мы получили ту же змейку, но в 1 120 раз меньше.

Змейка-AI: автоэнкодер сжимает спрайты

Олдскул впечатляет, но там все приходилось делать вручную. А что, если нейросеть сделает это за меня?

Я обучил маленький автоэнкодер на спрайтах змейки. Идея в том, чтобы скормить ИИ все 16 спрайтов, пусть выучит «сущность» каждого, а потом восстанавливает их из крошечного латентного кода.

import torch
import torch.nn as nn

class SpriteAutoencoder(nn.Module):
    """
    Автоэнкодер для спрайтов — карманный .kkrieger.

    Вход:   64×64×3 = 12 288 float (48 КБ)
    Латент: 32 float (128 байт)
    Сжатие одного спрайта: ×384
    """
    def __init__(self, latent_dim=32):
        super().__init__()
        
        # Сверхлегкий энкодер
        self.encoder = nn.Sequential(
            nn.Conv2d(3, 8, 4, stride=2, padding=1),   # → 32×32
            nn.ReLU(),
            nn.Conv2d(8, 16, 4, stride=2, padding=1),  # → 16×16
            nn.ReLU(),
            nn.Conv2d(16, 16, 4, stride=2, padding=1), # → 8×8
            nn.ReLU(),
            nn.Conv2d(16, 8, 4, stride=2, padding=1),  # → 4×4
            nn.ReLU(),
            nn.Flatten(),
            nn.Linear(8 * 4 * 4, latent_dim)
        )

        # Декодер на ~12 800 параметров (около 50 КБ)
        self.decoder = nn.Sequential(
            nn.Linear(latent_dim, 8 * 4 * 4), 
            nn.Unflatten(1, (8, 4, 4)),
            nn.ConvTranspose2d(8, 16, 4, stride=2, padding=1),  # → 8×8
            nn.ReLU(),
            nn.ConvTranspose2d(16, 16, 4, stride=2, padding=1), # → 16×16
            nn.ReLU(),
            nn.ConvTranspose2d(16, 8, 4, stride=2, padding=1),  # → 32×32
            nn.ReLU(),
            nn.ConvTranspose2d(8, 3, 4, stride=2, padding=1),   # → 64×64
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.decoder(self.encoder(x))

На обучение ушло 500 эпох и около трех минут работы GPU. В итоге вместо самих картинок мы храним 16 латентных кодов (это ровно 2048 байт) и веса декодера — легкую сверточную сеть объемом около 50 КБ. 

Суммарный вес ассетов составил примерно 52 КБ. За это мы получаем 16 спрайтов, которые восстанавливаются на видеокарте меньше чем за миллисекунду и визуально почти не отличаются от оригинала.

Очевидно, что для 16 спрайтов змейки, автоэнкодер — это как забивать гвозди микроскопом. Змейка получилась практически идентичной оригиналу уже на первых шагах (я до сих пор улыбаюсь от осознания того, на каком массиве я пытался обучить сетку). 50 КБ декодера вместе с латентными кодами против 2 КБ атласа — олдскул выигрывает. Нейросеть здесь проигрывает, потому что оверхед весов декодера слишком велик для такого маленького датасета.

Но. Если бы спрайтов было не 16, а 2 000 (как в нормальной инди-игре) — расклад бы перевернулся:

Количество спрайтов

Атлас с палитрой 

Автоэнкодер (декодер 50 КБ + латенты по 128 байт)

16

2 КБ

52 КБ

200

25 КБ

75 КБ

2 000

250 КБ

300 КБ 

20 000

2,5 МБ

2,55 МБ (тут они сравнялись)

Да, на малом объеме нейросеть проигрывает, но прелесть нейросети в том, что размер декодера фиксирован. Чем сложнее и разнообразнее ассеты, тем меньше 50 КБ декодера стоят относительно размера данных. 

На сложных PBR-текстурах с коррелированными каналами латент реально компактнее палитры — там AI уже выигрывает, а не просто догоняет.

Симуляция того, как это должно быть на практике, при большой выборке данных для сетки.

Симуляция того, как это должно быть на практике, при большой выборке данных для сетки.

Три змейки — финальный счет

FAT

OLDSCHOOL

AI

Спрайты

256 КБ

2 КБ

52 КБ

Фон

48 КБ

28 байт

28 байт

Звуки + музыка

3,4 МБ

1,2 КБ

1,2 КБ

Карта

2,4 КБ

112 байт

112 байт

Итого

~3,7 МБ

~3,3 КБ

~53,3 КБ

Сжатие

×1 120

×70

На масштабе змейки олдскул сделал всех. Это честный результат, и я не собираюсь его приукрашивать.

Но вот что важно: олдскул не масштабируется. Я потратил полчаса (я серьезно), вручную рисуя спрайты. Для 2 000 спрайтов мне бы потребовалось…  62,5 часа ручной работы — и это если рисовать также, как я рисовал змейку. А нейросеть обучится за те же четыре минуты.

Круг замкнулся: нейросети как новые демосценеры

Мы за статью прошли путь от ручной побайтовой оптимизации (80-е) через эпоху «кому вообще нужна оптимизация» (2010-е) и вернулись обратно, но теперь с нейросетями на борту.

Neural Texture Compression: текстуры через нейросеть

В апреле 2025 года NVIDIA представила обновленный NTC, а Intel следом показал собственный TSNC. Идея одна: вместо того, чтобы хранить текстуру попиксельно и сжимать блоками (BC1/BC7), мы обучаем маленькую нейросеть воспроизводить эту текстуру.

При рендере GPU запускает крошечную нейросеть прямо в шейдере, которая из латентного кода восстанавливает текстуру на лету. Intel заявляет о сжатии до 18× по сравнению с BC-форматами, а NVIDIA показала снижение потребления VRAM на 85% при сохранении визуального качества.

Мой автоэнкодер для змейки — это детская версия того же принципа. Разница в масштабе: Intel и NVIDIA обучают нейросети на наборах из тысяч PBR-текстур в семи слоях — нормалями, шероховатостью и тенями (окклюзией). А я — на 16 спрайтах змейки. Но математика одна.

Нейросетевой суперсэмплинг

DLSS, FSR, XeSS — все они позволяют рендерить картинку в низком разрешении и «дорисовывать» нейросетью. 

  • Без DLSS — рендер в 4K (8,3M пикселей) → вывод;

  • С DLSS — рендер в 1080p (2,1M пикселей) → нейросеть → вывод в 4K.

Четырехкратная экономия. Кармак бы одобрил.

Нейросеть использует приемы разработчиков из 2004

Еще раз вспомним .theprodukkt Просто сравните:

  • 2004 год, .theprodukkt — программист вручную пишет генератор текстуры камня: «возьми шум Перлина, наложи 3 октавы, примени gradient map». На это уходили сотни часов работы на каждый материал;

  • 2025 год, Intel TSNC / NVIDIA NTC — нейросеть смотрит на готовую текстуру камня и сама выводит алгоритм ее воспроизведения, обучаясь за минуты, а не за сотни часов.

Да, нейросеть делает буквально то же, что и разработчики из 00-х. Только она масштабируется на десятки тысяч ассетов без человеческого участия.

Что это значит для разработчиков (и для ваших SSD)

Мой эксперимент со змейкой — это микрокосм всей индустрии. FAT-версия — как современная AAA-игра, где ресурсы не важны. OLDSCHOOL — как NES-эра, когда дорог каждый байт. А AI-версия — то, к чему мы идем.

Видите паттерн? Ручная оптимизация → Изобилие ресурсов → Раздувание → Снова оптимизация (но уже с ИИ).

История повторяется: когда ресурсы заканчиваются, разработчики начинают думать. Сейчас ресурсы снова заканчиваются, только теперь кончается терпение пользователей, которым надоело качать 150 ГБ ради пятичасовой кампании.

P.S. Пока писал эту статью, Chrome снова обновился. Еще +15 МБ. Это 375 копий Марио.