Бесшовное рендеринг тайла карты (соседние изображения без полей)

18

У меня есть движок 2D игры, который рисует карты тайлов, рисуя плитки из изображения набора тайлов. Поскольку по умолчанию OpenGL может оборачивать только всю текстуру ( GL_REPEAT), а не только ее часть, каждая плитка разделяется на отдельную текстуру. Затем области одной и той же плитки оказываются смежными друг с другом. Вот как это выглядит, когда работает как задумано:

Карта листов без швов

Однако, как только вы введете дробное масштабирование, появятся швы:

Плитка с швами

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

Вещи, которые я пробовал / рассматривал:

  • сглаживание уменьшает, но не полностью устраняет швы
  • отключение mipmapping, не имеет никакого эффекта
  • Визуализируйте каждую плитку отдельно и вытяните края на 1 пиксель - но это неоптимизация, поскольку она больше не может рендерить области плиток за один раз и создает другие артефакты по краям областей прозрачности
  • добавьте границу в 1 пиксель вокруг исходных изображений и повторите последние пиксели - но тогда они перестают быть степенью двойки, вызывая проблемы совместимости с системами без поддержки NPOT
  • написание собственного шейдера для обработки мозаичных изображений - но что бы вы сделали по-другому? GL_REPEATСледует захватывать пиксель с противоположной стороны изображения на границах, а не подбирать прозрачность.
  • геометрия точно смежна, ошибок округления с плавающей запятой нет.
  • если фрагментный шейдер жестко запрограммирован для возврата того же цвета, швы исчезают .
  • если GL_CLAMPвместо текстур заданы текстуры GL_REPEAT, швы исчезают (хотя рендеринг неправильный).
  • если текстуры установлены на GL_MIRRORED_REPEAT, швы исчезают (хотя рендеринг снова неверный).
  • если я сделаю фон красным, швы по-прежнему белые. Это говорит о том, что это выборка непрозрачного белого цвета откуда-то, а не прозрачность.

Так что швы появляются только тогда, когда GL_REPEATустановлено. По какой-то причине только в этом режиме, по краям геометрии есть некоторая утечка / утечка / прозрачность. Как это может быть? Вся текстура непрозрачна.

AshleysBrain
источник
Вы можете попытаться установить сэмплер текстуры на зажим или отрегулировать цвет границы, так как я подозреваю, что это может быть связано с этим. Кроме того, GL_Repeat просто переносит координаты UV, поэтому я лично немного растерялся, когда вы говорите, что он может повторять только всю текстуру, а не ее часть.
Эван
1
Возможно ли, что вы как-то вводите трещины в свой меш? Вы можете проверить это, установив шейдер так, чтобы он возвращал только сплошной цвет вместо выборки текстуры. Если вы все еще видите трещины в этой точке, они в геометрии. Тот факт, что сглаживание уменьшает швы, предполагает, что это может быть проблемой, поскольку сглаживание не должно влиять на выборку текстуры.
Натан Рид
Вы можете реализовать повторение отдельных плиток в текстурном атласе. Тем не менее, вам нужно сделать дополнительную математику координат текстуры и вставить текстуру границ вокруг каждой плитки. Эта статья объясняет многое из этого (хотя в основном с акцентом на раздражающее соглашение о текстурных координатах D3D9). В качестве альтернативы, если ваша реализация достаточно новая, вы можете использовать текстуры массива для вашей карты тайлов; Предполагая, что каждая плитка имеет одинаковые размеры, это будет прекрасно работать и не потребует дополнительной математики координат.
Андон М. Коулман,
Трехмерные текстуры с GL_NEARESTвыборкой в Rнаправлении координат также работают так же хорошо, как массивные текстуры для большинства вещей в этом сценарии. Mipmapping не будет работать, но, судя по вашему приложению, вам, вероятно, все равно не нужны mipmaps.
Андон М. Коулман,
1
В чем заключается неправильность рендеринга при установке на CLAMP?
Мартин Курто

Ответы:

1

Швы правильного поведения для выборки с GL_REPEAT. Рассмотрим следующую плитку:

бордюрная плитка

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

масштабированная плитка

сглаживание уменьшает, но не полностью устраняет швы

MSAA работает, беря больше образцов по краям многоугольника. Сэмплы, взятые слева или справа от края, будут «зелеными» (опять же, учитывая левый край первого изображения), и только один сэмпл будет «на краю», частично сэмплируя из «бежевой» области. Когда образцы смешаны, эффект будет уменьшен.

Возможные решения:

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

  1. Включите сглаживание, чтобы немного сгладить переход между тайлами.

  2. Установите фильтрацию текстуры на GL_NEAREST. Это дает всем пикселям твердые края, поэтому края полигонов / спрайтов становятся неразличимыми, но это, очевидно, несколько меняет стиль игры.

  3. Добавьте уже обсуждаемую границу 1px, просто убедитесь, что граница имеет цвет смежной плитки (а не цвет противоположного края).

    • Это также может быть хорошим временем для перехода к текстурному атласу (просто увеличьте его, если вы беспокоитесь о поддержке NPOT).
    • Это единственное «идеальное» решение, позволяющее GL_LINEARвыполнять фильтрацию по краям полигонов / спрайтов.
Йенс Нолте
источник
О, спасибо - обтекание текстуры действительно объясняет это. Я посмотрю на эти решения!
AshleysBrain
6

Поскольку по умолчанию OpenGL может оборачивать только всю текстуру (GL_REPEAT), а не только ее часть, каждая плитка разделяется на отдельную текстуру. Затем области одной и той же плитки оказываются смежными друг с другом.

Рассмотрим отображение одного обычного текстурированного квадра в OpenGL. Есть ли какие-либо швы в любом масштабе? Нет никогда. Ваша цель - плотно упаковать все плитки сцены в одну текстуру, а затем отправить их на экран. РЕДАКТИРОВАТЬ Чтобы прояснить это далее: Если у вас есть отдельные ограничивающие вершины на каждом кваде плитки, в большинстве случаев вы будете иметь неоправданные швы. Это то, как работает аппаратное обеспечение графического процессора ... ошибки с плавающей запятой создают эти промежутки на основе текущей перспективы ... ошибки, которых избегают на унифицированном коллекторе, поскольку, если две грани находятся в одном и том же коллекторе(submesh), аппаратное обеспечение будет отображать их без швов, гарантировано. Вы видели это в бесчисленных играх и приложениях. Вы должны иметь одну плотно упакованную текстуру на одной подмешке без двойных вершин, чтобы избежать этого раз и навсегда. Это проблема, которая возникает снова и снова на этом сайте и в других местах: если вы не объединяете угловые вершины своих плиток (каждые 4 плитки имеют угловую вершину), ожидайте швы.

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

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

Это также открывает ряд различных путей для масштабирования:

  • масштабирование размера четырехугольника, к которому вы будете визуализировать FBO
  • меняя UVs на этом четырехугольнике
  • возиться с настройками камеры
инженер
источник
Я не думаю, что это будет хорошо работать для нас. Похоже, вы предлагаете, чтобы при 10% увеличении мы визуализировали область в 10 раз больше. Это не сулит ничего хорошего для устройств с ограниченной скоростью заполнения. Также я до сих пор не понимаю, почему именно GL_REPEAT вызывает только швы, а другой режим - нет. Как это может быть?
AshleysBrain
@AshleysBrain Non sequitur. Я озадачен вашим заявлением. Пожалуйста, объясните, как увеличение масштаба на 10% увеличивает скорость заполнения в 10 раз? Или как увеличить увеличение в 10 раз - поскольку отсечение области просмотра предотвратит более чем 100% заполнение за один проход? Я предлагаю вам показать именно то, что вы уже намерены - не больше, не меньше - в том, что, вероятно, более эффективно и плавно, объединяя ваш конечный результат с использованием RTT, обычной техники. Аппаратные средства будут обрабатывать отсечение, масштабирование, смешивание и интерполяцию в области просмотра практически бесплатно, поскольку это всего лишь 2D-движок.
инженер
@AshleysBrain Я отредактировал свой вопрос, чтобы прояснить проблемы с вашим текущим подходом.
инженер
@ArcaneEngineer Привет, я попробовал твой подход, который отлично решает проблему швов. Однако теперь, вместо швов, у меня есть ошибки пикселей. Вы можете иметь представление о проблеме, к которой я обращаюсь здесь . У вас есть идеи, что может быть причиной этого? Обратите внимание, что я делаю все в WebGL.
Немикол,
1
@ArcaneEngineer Я задам новый вопрос, объясняя здесь слишком громоздко. Я прошу прощения за раздражение.
Nemikolh
1

Если изображения должны отображаться как одна поверхность, визуализируйте их как одну текстуру поверхности (масштаб 1х) на одной трехмерной сетке / плоскости. Если вы визуализируете каждый как отдельный 3D-объект, у него всегда будут швы из-за ошибок округления.

Ответ @ Nick-Wiggill правильный, я думаю, вы его неправильно поняли.

Барри Стес
источник
0

2 возможности, которые стоит попробовать:

  1. Используйте GL_NEARESTвместо GL_LINEARфильтрации текстур. (Как отмечает Andon M. Coleman)

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

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

  2. Немного уменьшите текстурные координаты для каждой плитки.

    Вроде как добавление этого дополнительного пикселя вокруг каждой плитки в качестве буфера, за исключением того, что вместо расширения плитки на изображении вы уменьшаете плитку в программном обеспечении. Плитки размером 14x14 пикселей при рендеринге вместо листов размером 16x16 пикселей.

    Возможно, вам даже удастся отделаться плиткой 14,5х14,5 без добавления большого количества коричневого.

--редактировать--

Визуализация объяснения в варианте № 2

Как показано на изображении выше, вы все еще можете легко использовать степень двух текстур. Таким образом, вы можете поддерживать оборудование, которое не поддерживает текстуры NPOT. Что изменится в ваших текстурных координатах, вместо того, чтобы перейти от (0.0f, 0.0f)к (1.0f, 1.0f)вам, вы перейдете от (0.03125f, 0.03125f)к (0.96875f, 0.96875f).

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

Вольфганг Скайлер
источник
Я уже упоминал, что попробовал GL_NEAREST (точечная фильтрация), но это не исправляет. Я не могу сделать № 2, потому что мне нужно поддерживать устройства NPOT.
AshleysBrain
Сделано редактирование, которое может прояснить некоторые вещи. (Я предполагаю, что «необходимость поддержки устройств NPOT [(не-power-of-two)]» должна означать что-то вроде того, что вам нужно, чтобы текстуры имели некоторую степень 2?)
Вольфганг Скайлер
0

Вот что, я думаю, может произойти: там, где две плитки сталкиваются, компонент х их ребер должен быть одинаковым. Если это не так, они могут округляться в противоположном направлении. Поэтому убедитесь, что они имеют одинаковое значение х. Как вы гарантируете, что это произойдет? Вы можете попытаться, скажем, умножить на 10, округлить и затем разделить на 10 (делайте это только на процессоре, прежде чем ваши вершины попадут в вершинный шейдер). Это должно дать правильные результаты. Кроме того, не рисуйте мозаику по плитке с использованием матриц преобразования, но поместите их в пакетное VBO, чтобы убедиться, что неправильные результаты не приходят из системы с плавающей запятой с потерями IEEE в сочетании с умножением матриц.

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

Проблема также может заключаться в том, что вы используете таблицу спрайтов (также называемую атласом) и что при выборке текстуры выбираются пиксели смежной текстуры плитки. Чтобы этого не произошло, создайте крошечную рамку в UV-отображениях. Итак, если у вас есть плитка размером 64x64, ваше UV-отображение должно охватывать немного меньше. Сколько? Я думаю, что я использовал в своих играх 1 четверть пикселя с каждой стороны прямоугольника. Поэтому сместите ваши UV на 1 / (4 * widthOfTheAtlas) для компонентов x и 1 / (4 * heightOfTheAtlas) для компонентов y.

Мартейн Курто
источник
Спасибо, но, как я уже заметил, геометрия определенно точно смежна - на самом деле все координаты приводят к точным целым числам, так что это легко понять. Атласы здесь не используются.
AshleysBrain
0

Чтобы решить эту проблему в трехмерном пространстве, я смешал массивы текстур с предложением Вольфганга Скайлера. Я поместил 8-пиксельную рамку вокруг моей реальной текстуры для каждой плитки (120x120, 128x128 всего), которая для каждой стороны, которую я мог бы заполнить «обернутым» изображением или только что развернутой стороной. Сэмплер считывает эту область при интерполяции изображения.

Теперь с фильтрацией и mipmapping сэмплер все еще может легко читать за всю 8-пиксельную границу. Чтобы уловить эту небольшую проблему (я говорю «маленькая», потому что это происходит только на нескольких пикселях, когда геометрия действительно перекошена или находится далеко), я разбил плитки на массив текстур, так что каждая плитка имеет свое собственное пространство текстуры и может использовать зажим на кромки.

В вашем случае (2D / flat) я бы, конечно, пошел с рендерингом сцены с идеальным пикселем, а затем с масштабированием желаемой части результата в окне просмотра, как предложил Ник Виггил.

мукунда
источник