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

推荐订阅源

N
News and Events Feed by Topic
V
Visual Studio Blog
Jina AI
Jina AI
云风的 BLOG
云风的 BLOG
C
Check Point Blog
M
MIT News - Artificial intelligence
罗磊的独立博客
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
Application and Cybersecurity Blog
Application and Cybersecurity Blog
B
Blog
N
News | PayPal Newsroom
爱范儿
爱范儿
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Privacy International News Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Security Archives - TechRepublic
Security Archives - TechRepublic
Scott Helme
Scott Helme
V2EX - 技术
V2EX - 技术
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Simon Willison's Weblog
Simon Willison's Weblog
H
Help Net Security
大猫的无限游戏
大猫的无限游戏
K
Kaspersky official blog
雷峰网
雷峰网
IT之家
IT之家
Vercel News
Vercel News
S
Schneier on Security
Schneier on Security
Schneier on Security
C
CERT Recently Published Vulnerability Notes
博客园_首页
T
Tailwind CSS Blog
T
The Exploit Database - CXSecurity.com
F
Full Disclosure
博客园 - 司徒正美
The Cloudflare Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cisco Blogs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News and Events Feed by Topic
Cyberwarzone
Cyberwarzone
P
Proofpoint News Feed
F
Fortinet All Blogs
有赞技术团队
有赞技术团队
S
Security Affairs
Latest news
Latest news

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром T-TOPS: Как распутать гордиев узел проекта после выхода в прод (меч не понадобится) Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как 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 в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Как заставить 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 миллионов точек без потерь
Сегодня я узнал нечто новое о GPU благодаря багу в своей игре
2026-04-15 · via Все публикации подряд на Хабре

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

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

Охват и читатели11K

Перевод

Я только что выпустил обновление моей игры Blackshift, в котором, среди прочего, были добавлены эти тайлы песка:

Всё было хорошо, пока не начали поступать отчёты о багах вот с такими скриншотами:

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

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

Тайл песка до того, как за него взялся вершинный шейдер2

Тайл песка до того, как за него взялся вершинный шейдер2

Для того, чтобы знать, где размещать тени, фрагментный шейдер считывает карту соседства. Это 8-битный integer для каждого тайла, по одному биту на каждое из соседних направлений.

В случае этого тайла шейдер получает значение 238 и отрисовывает тени вдоль южного ребра и северо-восточного угла.

В случае этого тайла шейдер получает значение 238 и отрисовывает тени вдоль южного ребра и северо-восточного угла.

Как и всё остальное в Blackshift, тайлы песка отрисовываются при помощи инстансинга GPU: все тайлы песка на экране отрисовываются одной группой. GPU получает матрицу преобразований для каждого экземпляра и знает, что для всех них нужно использовать один меш. Так как каждый экземпляр имеет собственные данные о соседстве, значение соседства тоже передается в данных каждого экземпляра вместе с матрицей преобразований3.

Значение соседства — это 8-битный integer, но поскольку bgfx в качестве данных экземпляров поддерживает только float, перед записью в буфер экземпляров этот integer преобразуется во float.

Вершинный шейдер считывает его и передаёт фрагментному шейдеру, который затем преобразует его обратно int и проверяет отдельные биты, чтобы знать, где располагать тени.

Разумеется, из-за проблем с точностью при работе с float нужно быть аккуратным, но для хранения любого integer от 0 до 255 у них достаточно точности, так что никаких сложностей здесь нет. Если CPU решает, что integer соседства равен 238, то он запишет 238.0f и шейдер считывает 238.0f, преобразует значение обратно в 238 и считывает биты4.

Вот так и рендерятся тайлы песка. Так где же баг?

* * *

Первым делом я подумал, что это похоже на Z-конфликты, но это не имело никакого смысла. Поверхность песка — это одна поверхность, ей не с чем конфликтовать. Чтобы убедиться в этом, я отключил z-буферизацию, и да, артефакты не пропали. Дело было не в Z-конфликтах.

Затем я проверил миниатюры списка уровней. В GUI списка уровней Blackshift рендерит миниатюры загружаемых игроками уровней, позволяющие выбирать, в какой из них играть. Эти миниатюры рендерятся немного иначе, чем кадры геймплея, поэтому иногда они могут быть хорошим тестовым случаем; если баг возникает на обычном рендере, но не на рендере миниатюр, то разница в техниках рендеринга может подсказать источник проблем.

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

Это стало отличной подсказкой. Самая важная разница между рендерами миниатюр и игры заключается в том, что для миниатюр вообще не используется инстансинг GPU; всё это отключено. В нём выполняется примерно по одному вызову отрисовки на объект и на материал, а всё, что обычно передаётся как данные экземпляров, отправляется в виде uniform.

Я потратил кучу времени на дотошное исследование кода инстансинга и проверил всё дважды, но в конечном итоге выяснилось, что это ложная улика. После отключения инстансинга в основном рендере баг сохранился. И это значило, что... разумеется, оставалась единственная причина. В конце концов, между рендерами миниатюр и игры было только ещё одно различие.

Вернёмся к нашим float. CPU решает, что integer соседства равен 238, и записывает в буфер данных экземпляров 238.0f; вершинный шейдер извлекает это значение и записывает его в varying, а фрагментный шейдер считывает его оттуда, интерпретирует и отрисовывает тени.

Дело в том, что когда 238.0f добирается из вершинного во фрагментный шейдер, как и любая другая переменная varying, она интерполируется GPU по поверхности отрисовываемого треугольника. А поскольку значения всех трёх углов каждого треугольника одинаковы, я думал, что получающиеся интерполированные значения в каждой точке треугольника должны быть одинаковыми. И они действительно были... на моей машине.

Но когда эту интерполяцию выполняют GPU, они делают это с перспективной коррекцией, при которой выполняется деление на глубину каждого фрагмента с последующим умножением в конце5; если немного упростить, то можно сказать, что это может вызывать появление численной неточности. Наверно, мой GPU достаточно умён, чтобы заметить, что все три точки треугольника имеют одинаковое значение, и пропустить все эти вычисления, но GPU некоторых игроков этого не делают. Они выполняют все эти перспективные деления и умножения, получая немного отличающиеся значения float.

Это объясняет наблюдаемые артефакты; когда после интерполяции значение соседства пикселя оказывается меньше (даже на бесконечно малое значение), чем нужно, оно интерпретируется, как integer на единицу ниже, что соответствует совершенно иной карте соседства, а значит, и другому паттерну теней.

В конечном итоге я исправил это на стороне CPU; строку

(float)adjacency

в буфере экземпляров я заменил на

(float)adjacency+0.5f

Любые колебания теперь безопасно оказываются в пределах одного и того же integer, а артефакты пропадают6.

Так почему же артефакты не появлялись на рендерах миниатюр? Присмотритесь. Видите ответ?

Ответ

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

Заключение

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

Сноски

  1. Я воспринимаю это как фальшивое ambient occlusion. На самом деле, всё ambient occlusion фальшивое, как и вся остальная компьютерная графика.

  2. Четыре четырёхугольных полосы по краям становятся видимыми гранями тайлов без песка, соседних с тайлом песка. На изображении они представлены в виде вертикальных боков серых тайлов. Если тайла нет, вершинный шейдер просто перемещает их все из вида.

  3. Почему бы просто не хранить карту соседства для всего уровня в виде текстуры? Даже если бы я сделал так, шейдеру всё равно нужно было знать, на какие координаты в текстуре смотреть, поэтому мне всё равно пришлось бы отправлять координаты отрисовываемой ячейки в качестве данных экземпляров. То есть если данные экземпляров в любом случае необходимы, можно просто отправлять само значение, а не какие-то координаты для поиска значения в текстуре. Кроме того, bgfx ограничивает нас двадцатью float на экземпляр, а у меня был только один запасной. Думаю, можно было бы воссоздавать координаты ячейки, заглядывая в матрицу преобразований, уже занимающую бóльшую часть этих 20 floats; думаю, это бы сработало. Но в то время я об этом не думал. Разумеется, если бы я использовал текстуру, то нужно было бы поддерживать её актуальность, и мне бы понадобилось множество текстур, потому что иногда Blackshift отрисовывает множество уровней одновременно (например, когда игрок скроллит список уровней и смотрит миниатюры уровней других пользователей; они рендерятся конкурентно на стороне клиента). Это просто было бы сложнее.

  4. Зачем вообще использовать float? Потому что bgfx поддерживает только float. Зачем преобразовывать их значения вместо выполнения bitcasting? Потому что я поддерживаю старый OpenGL, не умеющий выполнять bitcasting.

  5. Есть хорошее объяснение интерполяции с перспективной коррекцией.

  6. Почему просто не пометить переменную varying, как flat? Повторюсь, я поддерживаю старый OpenGL, где flat отсутствует.