Создатель Papers, Please Лукас Поуп работает над новым трёхмерным проектом Return of the Obra Dinn, в котором пытается с помощью эффекта дизеринга воссоздать в игре ощущение старинной книги.
Для начала краткое объяснение: Obra Dinn выполняет внутренний рендеринг всего в 8-битной палитре в градациях серого, а затем на этапе постобработки преобразует конечные выходные данные в 1-битные значения. Преобразование из 8-битного в 1-битный цвет выполняется сравнением каждого пикселя исходного изображения с соответствующей точкой в тайловом паттерне дизеринга. Если значение пикселя изображения больше значения точки паттерна дизеринга, то выходному биту присваивается значение 1, в противном случае оно равно 0. Выходные данные упрощаются до 1-битных значений, а глаз зрителя объединяет пиксели, аппроксимируя из них больше битов.
Преобразование исходного изображения по шаблону дизеринга
Двумя компонентами этого процесса являются исходное изображение и паттерн дизеринга. В различных случаях Obra Dinn использует два отличающихся паттерна: матрица Байера 8x8 для более плавного диапазона оттенков и поле синего шума 128x128 для менее упорядоченного вывода.
Байер/синий шум
Результат внутри движка без каркасных линий. Байер на сфере, синий шум на всём остальном.
Классический процесс дизеринга отлично работает для статичных изображений и гораздо хуже выглядит на движущихся и анимированных изображениях. Когда исходное изображение покадрово изменяется, то серьёзной проблемой становится статичный паттерн дизеринга и вывод в низком разрешении. То, что должно быть сплошными формами и оттенками, превращается в мерцающий хаос пикселей.
Сегодня дизеринг в основном используется для статичных исходных изображений или при высоком разрешении выходных данных. Первое, что думаешь, глядя на этот плавающий эффект дизеринга, это не «да, именно так работает дизеринг», а «что это за дёргающийся эффект и как мне его отключить».
Попробуйте сфокусироваться на каком-нибудь объекте, пока он двигается, и вы поймёте, в чём основная проблема Obra Dinn в полноэкранном режиме. Существуют способы исправить это, и чаще всего они сводятся к «этот стиль не работает, замени его». Я довольно далеко зашёл на этом пути, экспериментируя с различными стилями, но потом вернулся назад и задал себе вопрос — возможно, не стоит давать этим гадским пикселям мешать мне.
Стабилизируем дизеринг
Чтобы дать глазам возможность наилучшим образом всё рекомбинировать, дизеринг оптимальнее всего использовать с точками паттерна дизеринга, имеющими корреляцию 1:1 с выходными пикселями. Но если будет присутствовать корреляция «только» с выходными данными, то при применении постэффектов сцены не будет никакой связи между отрендеренной геометрией и пороговым паттерном. В каждом кадре у движущихся элементов сцены будет новое пороговое значение. Вместо этого я хочу, чтобы паттерн дизеринга был «приклеен» к геометрии и казался стабильным при движении вместе с остальной сценой.
Здесь возникает проблема наложения. Существует конфликт между «идеальным» наложением паттерна дизеринга (1:1 с экраном) и идеальным наложением на сцену (x:1 с геометрией), так что нужно быть готовым идти на компромиссы. БОльшая часть моей работы посвящена наложению входного паттерна дизеринга на различные пространства, которое обеспечивает наилучшее совпадение паттерна с геометрией сцены. Здесь всё выполняется на этапе до задания порогов.
Пространство текселов
Первой моей попыткой было наложение паттерна дизеринга на пространство текселов. Это аналогично дизерингу текстур объектов во время рендеринга сцены вместо выполнения постобработки 8-битного выходного изображения. Я не ожидал, что это сработает, но всё равно хотел посмотреть, как будет выглядеть идеально совпадающее со сценой наложение.
Паттерн дизеринга в пространстве текселов
Ну, в целом ожидания себя оправдали. Наложение на все объекты выполнено по-разному, поэтому масштабы из паттернов не совпадают. Их можно унифицировать. Но настоящая проблема заключается в искажениях. Любой ресемплинг из одного пространства в другое приведёт к искажениям, а для паттернов дизеринга не так просто выполнить mip-текстурирование или фильтрацию, как для традиционных текстур. Однако доведём это до конца:
Всё не так плохо — паттерн неплохо привязан к геометрии. Искажение создаёт собственный плавающий эффект, а унифицирование или масштабирование наложения в этом ничем не поможет. Текселы меняют размер в зависимости от расстояния до камеры, поэтому всегда найдутся пиксели паттерна дизеринга, которые при ресемплинге на экране будут ужасно искажаться.
Деформация при движении
Если я хотел, чтобы паттерн дизеринга отслеживал движение геометрии под ним, то почему бы просто не деформировать паттерн на основании изменения позиции каждого отрендеренного пикселся в сцене? Действительно, почему бы не попробовать. Это немного похоже на motion blur, при котором каждый пиксель отслеживает своё движение относительно предыдущего кадра. В этом случае я обновляю текстуру дизеринга, чтобы её паттерн двигался вместе со сценой. Если пиксель сцены не присутствовал на предыдущем кадре, то в нём паттерн дизеринга перезагружается. Реализацию этой техники очень облегчила статичность игры — мне нужно было беспокоиться о движении камеры, а не отдельных объектов.
Это была довольно «быстрая и грязная» попытка, но стали очевидны некоторые факты. Во-первых, это в чём-то работает. Во-вторых, паттерну дизеринга нужно учитывать соседей — он не может быть просто отдельными пикселями. Если рассматривать каждый пиксель отдельно, как делается в этом способе, то очевидно, что мы получим разрывы и искажения в паттерне. В этой тестовой сцене я сдвинул камеру, чтобы показать это на примере сундука. Посмотрев на сам искажённый паттерн дизеринга, легче это заметить.
Эти разрывы возникают из-за разной глубины пикселей и выбранных порогов. Я подумывал о сложной системе исправления проблемы на основе отслеживания областей, усреднения их глубины и смещения всех точек паттерна дизеринга в каждой области на одинаковое значение. Разрывы вдоль границ областей можно скрыть резкой сменой освещения или каркасной линией. Это не получилось бы реализовать из-за того, что игра использовала для генерации каркасов моделей цветные области. Когда я приступил к реализации всего этого, то сначала упустил в уравнении глубину, что дало мне гораздо более простую альтернативу:
Наложение на экран со смещением
При составлении уравнений для деформируемого дизеринга из них выпало очень простое преобразование:
DitherOffset = ScreenSize * CameraRotation / CameraFov
В сущности, это выражает то, что я хотел: сдвиг наложенного на экран паттерна дизеринга ровно на один экран при повороте камеры на одну область обзора. Благодаря этому сохраняется наложение 1:1 с экраном, но при этом также учитывается упрощённое преобразование видимой геометрии сцены. На самом деле это соответствует только движению в центре экрана, но, к моему счастью, выглядит достаточно хорошо.
Заметьте: похоже, что подвергнувшиеся дизерингу пиксели стула в основном движутся с геометрией. То же самое относится и к сфере. Более перпендикулярные к полю зрения плоскости отображаются не очень хорошо — пол по-прежнему выглядит хаотичным.
Хотя подход и не идеален, простой сдвиг наложенного на экран дизеринга сохраняет общий паттерн и движение сцены, чтобы глазу удобнее было отслеживать вместе. Я был этим очень доволен. Занимаясь подчисткой кода и коммитами, выпустив один-два поста в devlog, я всё равно не мог избавиться от мысли об идеально прилепленном дизеринге:
Пространство мира — кубическое наложение
Предыдущие эксперименты показали, что любая корреляция между паттерном дизеринга и геометрией сцены должна игнорировать информацию глубины, получаемую от сцены. На практике это означает, что дизеринг можно прицеплять к геометрии во время поворота камеры, но не её перемещения. Это не так уж плохо для Obra Dinn, учитывая медленный темп игры и наблюдательную роль игрока. Обычно в игре он ходит по кораблю, останавливается и смотрит на объекты. При ходьбе на экране происходит так много изменений, что плавающий дизеринг не особо очевиден.
С учётом этого, моей следующей попыткой стало наложение паттерна дизеринга на геометрию ненапрямую, с помощью предварительного рендеринга паттерна на стороны куба, центрированного вокруг камеры. Куб перемещается с камерой, но остаётся ориентированным относительно мира. Получается смесь: немного экрана, немного сцены.
Паттерн дизеринга наложен на куб, центрированный относительно камеры
Вид из камеры, смотрящей в угол. Масштаб наложения для наглядности увеличен.
Наложение на куб работает неплохо, когда смотришь на стороны, но не так хорошо, когда камера направлена на угол. Паттерн дизеринга по-прежнему идеально зафиксирован в 3D-пространстве при повороте камеры. Даже при грубых проверках результат выглядит многообещающим.
Дело, наконец, сдвинулось с места. Благодаря тому, что это постобработка, такой подход более общий, чем наложение в пространстве текселов, что хорошо. Проблема теперь сводится к конкретному кубическому наложению. При идеальном наложении один тексел на кубе всегда соответствует одному пикселю на экране, вне зависимости от поворота камеры. Для куба это невозможно…
Пространство мира — сферическое наложение
… но благодаря сфере я подобрался достаточно близко.
Наложение паттерна дизеринга на внутренность сферы
Поиск этого конкретного сферического наложения потребовал определённого времени. Не существует способов идеального замощения сферы квадратной текстурой. Можно было бы переопределить матрицы дизеринга через сетку шестиугольников или чего-то подобного, что хорошо замощает сферу. Возможно, получилось бы, но я не пробовал. Вместо этого я «взломал» замощение сферы, добившись тщательной настройкой того, чтобы «кольцевое» наложение исходного паттерна дизеринга давало хорошие результаты.
Лучше, чем с кубом, но по-прежнему много искажений. Размер сферически наложенной точки очень похож на размер экранного пикселя — отличается ровно настолько, чтобы создавать муар. Я чувствовал, что близок к решению, и очень просто исправить такие искажения с помощью суперсемплирования: применить порог дизеринга при более высоком разрешении, а затем снизить его.
Сферически наложенный паттерн дизеринга при увеличении 2x и со сниженным до 1x разрешением
Это пока самый лучший из полученных мной результатов. Тут есть несколько компромиссов:
- Точки паттерна дизеринга становятся больше в размерах и менее эффективными по краям экрана
- Паттерн не выравнен по направлениям «верх-низ-лево-право» для большинства поворотов камеры
- Выходные данные больше не являются 1-битными из-за конечного снижения разрешения
Но преимущества очень велики:
- Дизеринг отлично прикрепляется ко всем поворотам камеры. В игре это ощущается немного странно.
- Дискомфорт от плавающего дизеринга совершенно пропал, даже в полноэкранном режиме.
- Сохраняется пикселизированный стиль игры
Можно полностью избавиться от недостатка 3, снова ограничив выходные данные 1-битными значениями с помощью простого порога в 50%. Результат по-прежнему лучше, чем без суперсемплинга (ниже представлены три примера для сравнения).
Подводим итог
Кажется немного странным потратить 100 часов на то, отсутствия чего даже не заметят. Никто точно не подумает «блин, да этот дизеринг адски стабилен, это какая-то магия». Но я не хотел, чтобы у людей возникали проблемы, которые должны были бы возникнуть, так что их стоило устранить.
Наложение в экранном пространстве со смещением работает лучше всего при масштабе 1x, а сферическое наложение — при 2x. Вся сцена сейчас рендерится в разрешении 800x450 (поднял разрешение с 640x360), что повышает разборчивость, при этом не требуется жертвовать стилем low-res. В готовой игре будет два режима отображения:
ЦИФРОВОЙ — дизеринг в пространстве экрана со смещением, 1-битный вывод.
АНАЛОГОВЫЙ — полноэкранный наложенный на сферу дизеринг, сглаженный вывод.
Комментарии (38)
GeMir
02.12.2017 12:32Раз уж нет аналога на русском, почему бы не использовать dithering не пытаясь его транслитерировать?
QDeathNick
02.12.2017 12:39Тогда уж и ???????? пишите и translitteration.
lorc
02.12.2017 13:03Если игрок не движется, а только вращает камерой, то можно было бы просто отрендерить один раз сферическую панораму, а потом просто двигать окно по ней. Или я что-то упустил?
hdfan2
02.12.2017 14:46По-моему, дизеринг и близко не подходит для имитации иллюстраций из старых книг. Если я правильно понял, имелось в виду нечто вроде:
Там совершенно другой тип искажения. С дизерингом ничего общего не имеет.KoCMoHaBT61
02.12.2017 14:52Это гравюра. Её автоматом пока делать никто не умеет.
Есть только полуручной инструмент Strokes Maker, который плотность штриха уменьшает/увеличивает относительно яркости подложки. И то большое достижение.
VaalKIA
02.12.2017 16:56+1Не соглашусь, вообще, то о чём говориться в статье, больше походит на элементарную пастеризацию, то есть уменьшение цветов, при этом её можно сдлеать чанками и вместо оттенков применять шаблоны разреженных точек (нечто вроде jpeg), при этом разрешение катастрофически падает. А под дизерингом всегда понимался алгоритм, переноса ошибки (в процессе пастеризации) на соседние пиксели. Так вот, переносить ошибку можно только на чётные/нечётные строки, что даст эффект полосатости, как черезстрочная развёртка телевизора. Вроде бы, этот эффект можно было наблюдать в формате видео под DOS smk с маленькой палитрой, который позже стал bik но уже полноцветный. А автору, я думаю, подошла бы элементарная заливка поверхностей по патерну, без учёта глубины, о чём он, в общем-то упоминает:
Sentinel ZX Spectrummwizard
03.12.2017 04:49Постеризация, от слова "постер", т.е. плакат. Пастеризация — это процесс обеззараживания пищевых продуктов, названный в честь Луи Пастера.
VaalKIA
02.12.2017 17:25Примеры в интернете найти тяжело, но выглядело это вроде такого:
неудачный пример, просто что бы было понятно как это выгляделотипа черезстрочный дизеринг
чанки
amarao
02.12.2017 19:08По поводу гравюр уже написали выше, хочу добавить чуть-чуть, как интересующийся рисованием.
Правила рисования:
1. Штрих по форме. Внезапно, 3D модели знают свою форму, так что мы можем для любого ракурса реконструировать штрих по форме.
2. Линия должна быть живой — её толщина должна показывать «энергичность» движения в данном месте. «Движение» в рисовании — направление изгиба основной оси объекта, игнорируя мелкие детали. В первом приближении — изгиб центра симметрии. Чем сильнее изгиб, тем больше движение. Энергичность движения — его значимость для зрителя (художника). Машинно определить это сложно, но довольно просто разметить на 3D-модели.
3. Штрихи могут менять яркость (нажим), толщину, расстояние между ними, количество слоёв (в контексте гравюры — не больше двух).
4. Если у художника mad skill, то он может использовать штрихи для передачи характерных элементов текстуры (волос, ткани, волокон дерева).
5. При переломе формы лучше менять направление штриха. Перелом формы — это место, где резко уменьшается радиус кривизны. Вычисляется на 3д-модели в пол-пинка.
Это всё вполне в рамках машины. Даже если пренебречь «энергичностью» движения, всё остальное вычисляется. Неужели никто не пробовал?staticlab
02.12.2017 21:09А по правилу 2 можете привести наглядные примеры? А то что-то не воспринимается.
amarao
02.12.2017 22:12Я быстро набросал с вакомом (я им хуже владею, чем карандашом). Слева — штрих без формы, в центре — по форме, справа — рисунок по форме.
(https://snag.gy/wMZpXL.jpg)
Показать вопросы с движением и т.д. будет много сложнее, т.к. это уже потребует настоящего рисунка.staticlab
02.12.2017 23:47Эх, жаль. Про штрих по форме я и так сам прекрасно понял. А вот про движение как раз хотел пояснений...
amarao
02.12.2017 23:57А, движение — это сложно.
Олег хорошо объясняет. Это видео и остальные смежные:
www.youtube.com/watch?v=BRW9rUTnSa8
KoCMoHaBT61
02.12.2017 22:11На эту тему есть несколько диссертаций, но результаты очень убогие.
amarao
02.12.2017 22:14то есть вы хотите сказать, что если сделать что-то такое — это будет новое и неизведанное, а не изобретение велосипеда?
KoCMoHaBT61
02.12.2017 22:51Будет новое и неизведанное. Пока никто ничего толкового не сделал по этой теме.
Самое первое приближение из коммерческих 3D рендеров это Modo NPR Kit.
khim
03.12.2017 05:11Скорее всего будет и то и другое. То есть с одной стороны вы сможете опубликоваться в престижном журнале, с другой стороны — через NNN лет обнаржится что кто-то эту проблему уже давно решил в какой-нибудь трешовой игре. Как в XKCD.
KoCMoHaBT61
03.12.2017 08:30Это вряд-ли.
Дело в том, что проблема гравюрного NPR и правильной штриховки лежит рядышком с честным рейтрейсом поверхностей (без триангуляции). Ни в каких трешовых играх это не применяется.
А с анимацией всё будет значительно хуже — будет конкретное мельтешение штриховки, мельтешение дизеринга и мельтешение вообще всего вокруг.
DrZlodberg
03.12.2017 15:30KoCMoHaBT61
03.12.2017 15:51Хм… Этой не видел… Хотя, вроде 2001 год, а я бросил этим заниматься в районе 2004.
А вторая — забавная штукенция.
AngReload
03.12.2017 08:23Вообще, статья рассматривает только стабильность паттерна.
Даже если у вас будет какойнибудь способ генерироовать гравюры на основании сцены, кадры не будут волшебным образом связаны друг с другом. Ведь придется на каком-то основании выбрать какие пиксели будут штрихами, а какие — фоном, когда выбор равнозначен. Так в одном кадре стул будет раскрашен в черно-белую полоску, а в следущем в бело-черную, всё будет нещадно мерцать.
Для того чтобы избежать этого, должна быть некая текстура — паттерн с опорными точками, который хорошо бы двигать от кадра к кадру совместно с объектами сцены.
Вам не придётся выкинуть эту часть, если вы выберите (реализуете) более умный и красивый метод дизеринга.
DrZlodberg
03.12.2017 15:27Почему не пробовали? 2001 год вроде бы.
Ну и ещё куча примеров по запросам типа «real-time hatching shader»amarao
03.12.2017 15:45Божественно. Это как раз то, про что я говорил. Дальше вопрос только файнтюнинга, а принцип — именно он.
vitaliy91
02.12.2017 19:28Хотелось бы попробовать поиграть например в третьего ведьмака с таким эффектом как на образце А
shaman4d
02.12.2017 23:25Еще бы кода автор занес бы…
Bookvarenko
04.12.2017 08:24Там, по ссылке на оригинал статьи, можно найти и остальные статьи о напилинге игры. Из них следует что одного кода было недостаточно для достижения этого обалденного эффекта. Там ещё изрядно дизайна уровней — светильники слои сцены и всё такое. Впрочем можно код из демки наковырять. Оно же C# и Unity. Ну, или автору написать, ежели языками иноземными владеешь.
maniacscientist
03.12.2017 00:11Мнда… Движущийся паттерн убивает всю идею дизеринга как ретроизврата. При статичном паттерне мельтешение пикселей происходит потому, что синий шум — это хлам. Оттенок изменился незначительно, а паттерн — до мерцания. Целлшейдинг врежьте в середину, чтоли — сразу поприятнее станет.
Bookvarenko
03.12.2017 11:02Этой статье не помешали бы ссылки на собственно проект или ютубчик.
Например https://youtu.be/ElrXAHogqBM
Впрочем, как и комментаторам навык беглого гугления по картинке хотя-бы.
Игра шикарная получилась — 3d c эффектом книжной иллюстрации. Спасибо переводчику!
NikitaBogdanov
03.12.2017 11:02Возможно проблема в том что человеческий глаз резко реагирует на искажение горизонтальных линий между кадрами (прямая линия при повороте камеры становиться ступенчатой), если уменьшить ступенчатость при смене кадров возможно станет лучше. Гравюры делались на основе оттисков, в которых иглой соскребалось наиболее тёмная часть, так что реализовать алгоритм тоже можно, сложность только в том, что каждый художник использовал свой стиль.
1eqinfinity
05.12.2017 10:56Мне оригинал все-таки больше нравится, но учитывая цели, которые разработчик ставил, результат конечно отличный.
aureliano_b
06.12.2017 07:52Эстетически, первый вариант выглядит лучше, на мой взгляд. Статичная, по отношению к зрителю, «сетка» дизеринга — это, на мой взгляд, то, что ожидает сознание от такого рода картинки. В этом смысле, первый вариант выигрышнее, как мне видится.
perfect_genius
Мне и оригинал вполне себе.
Не решал ли он 100 часов проблему, которая проблемой не являлась? Может, стоило спросить других? Странно.Atterratio
Я сначала не понял какую он проблему решал. Но по конечному результату понял. Есть такое ание «Граф Монте-Кристо» и там похожая ситуация. Большинство моих знакомых его хвалят, а я дольше 5 минут просто не могу смотреть у меня начинают глаза отваливаться. В чём же дело? А дело в том что часть движущихся персонажей представлено статическими задниками, т.е. персонаж перемещается, и допустим на поделадке его плаща так же перемещается узор. С одной стороны это очень интересный эффект, с другой для некоторых людей он является вырвиглазным. В изначальном варианте дизеринг очень похож на этот эффект, только если в аниме это фича, то тут это баг.