Как вы перемещаете спрайт с шагом в субпиксель?

11

Пиксели либо включены, либо выключены. Минимальная сумма, которую вы можете переместить спрайт, составляет один пиксель. Так как же заставить спрайт двигаться медленнее, чем 1 пиксель на кадр?

Я сделал так, чтобы добавить скорость к переменной и проверить, достигла ли она 1 (или -1). Если бы это произошло, то я бы переместил спрайт и сбросил переменную на 0, вот так:

update(dt):
    temp_dx += speed * dt
    temp_dy += speed * dt

    if (temp_dx > 1)
        move sprite
        reset temp_dx to 0
    if (tempy_dy > 1)
        move sprite
        reset temp_dy to 0

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

bzzr
источник
Единственное, что вы можете сделать, это билинейная фильтрация.
AturSams
Если вы перемещаете менее 1 пикселя на кадр, как это может выглядеть отрывисто?

Ответы:

17

Есть несколько вариантов:

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

    tempx += speed * dt
    
    while (tempx > 0.5)
      move sprite to x+1
      tempx -= 1
    while (tempx < -0.5)
      move sprite to x-1
      tempx += 1
    

    это должно быть лучше. Я переключил if, чтобы использовать 0.5, так как, когда вы пройдете 0.5, вы ближе к следующему значению, чем предыдущее. Я использовал циклы while для перемещения более чем на 1 пиксель за шаг по времени (это не лучший способ сделать это, но он обеспечивает компактный и читаемый код). Для медленно движущихся объектов это все равно будет нервным, так как мы не занимались фундаментальной проблемой выравнивания пикселей.

  2. Иметь несколько графиков для вашего спрайта и использовать другую в зависимости от смещения субпикселя. Например, чтобы выполнить субпиксельное сглаживание только по x, вы можете создать графику для вашего спрайта в x+0.5, а если позиция между x+0.25и x+0.75, использовать этот спрайт вместо вашего оригинала. Если вы хотите более точное позиционирование, просто создайте субпиксельную графику. Если вы сделаете это в, xи yваше число рендеринга может быстро взлететь, так как число масштабируется с квадратом числа интервалов: интервал 0.5потребует 4 рендеринга, 0.25потребует 16.

  3. Сверхтиповой. Это ленивый (и потенциально очень дорогой) способ создания изображения с субпиксельным разрешением. По сути, удвойте (или более) разрешение, при котором вы визуализируете вашу сцену, а затем уменьшите ее во время выполнения. Я бы порекомендовал здесь осторожность, поскольку производительность может быстро упасть. Менее агрессивный способ сделать это - просто перепробовать ваш спрайт и уменьшить его во время выполнения.

  4. Как предполагает Zehelvion , возможно, используемая вами платформа уже поддерживает это. Если вам разрешено указывать нецелые координаты x и y, могут быть варианты изменить фильтрацию текстуры. Ближайший сосед часто используется по умолчанию, и это вызовет «резкое» движение. Другая фильтрация (линейная / кубическая) привела бы к гораздо более гладкому эффекту.

Выбор между 2. 3. и 4. зависит от того, как реализована ваша графика. Растровый стиль графики намного лучше подходит для предварительного рендеринга, тогда как векторный стиль может подходить для суперсэмплинга спрайтов. Если ваша система поддерживает это, 4. вполне может быть, путь.

Еж
источник
Зачем заменять порог на 0,5 в опции 1? Я также не понимаю, почему вы переместили операторы в цикл while. Функции обновления вызываются один раз за такт.
BZZR
1
Хороший вопрос - я уточнил ответ на основе вашего комментария.
Jez
Суперсэмплинг может быть «ленивым», но во многих случаях снижение производительности при 2-кратной или даже 4-кратной передискретизации не представляет особой проблемы, особенно если это позволяет упростить другие аспекты рендеринга.
суперкат
В играх с большим бюджетом я обычно вижу вариант 3, указанный в настройках графики как сглаживание.
Кевин
1
@ Кевин: Вы на самом деле, вероятно, нет. В большинстве игр используется мультисэмплинг , который несколько отличается. Другие варианты также широко используются, например, с различным охватом, а также глубиной выборки. Суперсэмплинг практически никогда не выполняется из-за стоимости выполнения фрагментного шейдера.
Ималлетт
14

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

То есть, если ваш спрайт будет перемещаться менее чем на один пиксель на кадр (или, особенно, если отношение пикселей к кадру должно быть чем-то странным, например, 2 пикселя в 3 кадрах), вы можете скрыть резкость, сделав n цикл анимации кадров, который в течение этих n кадров заканчивался перемещением спрайта на несколько k < n пикселей.

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


Я не мог найти фактический спрайт из старой видеоигры, чтобы проиллюстрировать это (хотя я думаю, что, например, некоторые из анимаций копания из Леммингов были такими), но оказывается, что «планерная» модель из игры Жизни Конвея делает очень хорошая иллюстрация:

введите описание изображения здесь
Анимация от Kieff / Wikimedia Commons , используемая по лицензии CC-By-SA 3.0 .

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

Илмари Каронен
источник
2
Это отличный подход, если скорость фиксирована, или если она меньше 1/2 пикселя на кадр, или если анимация «достаточно большая» и достаточно быстрая, чтобы скрыть изменения в движении.
суперкат
Это хороший подход, хотя, если камера должна следовать за спрайтом, вы все равно столкнетесь с проблемами, потому что она не будет двигаться достаточно плавно.
Элден Абоб
6

Позиция спрайта должна сохраняться как величина с плавающей запятой и округляться до целого числа только перед отображением.

Вы также можете поддерживать спрайт в супер-разрешении и уменьшать его до отображения. Если вы поддерживаете спрайт с разрешением экрана 3x, у вас будет 9 различных фактических спрайтов в зависимости от субпиксельной позиции спрайта.

ddyer
источник
3

Единственным реальным решением здесь является использование билинейной фильтрации. Идея состоит в том, чтобы позволить GPU вычислить значение каждого пикселя на основе четырех пикселей спрайта, которые перекрывают его. Это распространенная и эффективная техника. Вам просто нужно поместить спрайт на 2D-плоскость (рекламный щит) в качестве текстуры; затем используйте графический процессор для визуализации этих равнин. Это работает хорошо, но ожидайте получить несколько размытые результаты и потерять 8-битный или 16-битный вид, если вы к этому стремитесь.

плюсы:

Уже реализованное, очень быстрое аппаратное решение.

минусы:

Потеря 8-битной / 16-битной верности.

AturSams
источник
1

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

Однако есть другой способ сделать то, что вы делаете в данный момент, но немного менее вяло: фиксированная точка. 32-разрядное значение позиции может представлять значения от 0 до 4 294 967 295, даже если ваш экран почти наверняка имеет менее 4 000 пикселей по любой оси. Поэтому поместите фиксированную «двоичную точку» (по аналогии с десятичной точкой) посередине, и вы можете представить позиции пикселей от 0 до 63636 с другими 16 битами с субпиксельным разрешением. Затем вы можете добавить числа как обычно, вам просто нужно помнить, что нужно конвертировать, сдвигая вправо 16 бит при преобразовании в пространство экрана или когда вы умножили два числа вместе.

(Я написал 3D-движок с 16-битной фиксированной точкой еще в тот день, когда у меня был Intel 386 без модуля с плавающей запятой. Он достиг скорости ослепления 200 полигонов с затененным фонгом на кадр.)

pjc50
источник
1

В дополнение к другим ответам здесь вы можете использовать дизеринг в некоторой степени. Дизеринг - это то, где края пикселей объекта становятся светлее / темнее по цвету, чтобы соответствовать фону, делающему более мягкий край. Например, скажем, у вас был квадрат 4 пикселя, с которым я бы приблизился:

OO
OO

Если бы вы переместили это 1/2 пикселя вправо, на самом деле ничего бы не двигалось. Но с некоторым колебанием вы могли бы немного создать иллюзию движения. Считайте O черным, а o серым, поэтому вы можете сделать следующее:

oOo
oOo

В одном кадре и

 OO
 OO

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

Serpardum
источник