У меня есть движок 2D игры, который рисует карты тайлов, рисуя плитки из изображения набора тайлов. Поскольку по умолчанию OpenGL может оборачивать только всю текстуру ( GL_REPEAT
), а не только ее часть, каждая плитка разделяется на отдельную текстуру. Затем области одной и той же плитки оказываются смежными друг с другом. Вот как это выглядит, когда работает как задумано:
Однако, как только вы введете дробное масштабирование, появятся швы:
Почему это происходит? Я думал, что это связано с линейной фильтрацией, смешивающей границы квадратов, но это все же происходит с точечной фильтрацией. Единственное решение, которое я нашел до сих пор, состоит в том, чтобы обеспечить позиционирование и масштабирование только при целочисленных значениях и использовать точечную фильтрацию. Это может ухудшить визуальное качество игры (в частности, субпиксельное позиционирование больше не работает, поэтому движение не так плавно).
Вещи, которые я пробовал / рассматривал:
- сглаживание уменьшает, но не полностью устраняет швы
- отключение mipmapping, не имеет никакого эффекта
- Визуализируйте каждую плитку отдельно и вытяните края на 1 пиксель - но это неоптимизация, поскольку она больше не может рендерить области плиток за один раз и создает другие артефакты по краям областей прозрачности
- добавьте границу в 1 пиксель вокруг исходных изображений и повторите последние пиксели - но тогда они перестают быть степенью двойки, вызывая проблемы совместимости с системами без поддержки NPOT
- написание собственного шейдера для обработки мозаичных изображений - но что бы вы сделали по-другому?
GL_REPEAT
Следует захватывать пиксель с противоположной стороны изображения на границах, а не подбирать прозрачность. - геометрия точно смежна, ошибок округления с плавающей запятой нет.
- если фрагментный шейдер жестко запрограммирован для возврата того же цвета, швы исчезают .
- если
GL_CLAMP
вместо текстур заданы текстурыGL_REPEAT
, швы исчезают (хотя рендеринг неправильный). - если текстуры установлены на
GL_MIRRORED_REPEAT
, швы исчезают (хотя рендеринг снова неверный). - если я сделаю фон красным, швы по-прежнему белые. Это говорит о том, что это выборка непрозрачного белого цвета откуда-то, а не прозрачность.
Так что швы появляются только тогда, когда GL_REPEAT
установлено. По какой-то причине только в этом режиме, по краям геометрии есть некоторая утечка / утечка / прозрачность. Как это может быть? Вся текстура непрозрачна.
GL_NEAREST
выборкой вR
направлении координат также работают так же хорошо, как массивные текстуры для большинства вещей в этом сценарии. Mipmapping не будет работать, но, судя по вашему приложению, вам, вероятно, все равно не нужны mipmaps.Ответы:
Швы правильного поведения для выборки с GL_REPEAT. Рассмотрим следующую плитку:
Выборка по краям плитки с использованием дробных значений смешивает цвета от края и противоположного края, что приводит к неправильным цветам: левый край должен быть зеленым, но представляет собой смесь зеленого и бежевого. Правый край должен быть бежевым, но это также смешанный цвет. Особенно хорошо видны бежевые линии на зеленом фоне, но если вы присмотритесь, вы увидите, как зелёный цвет окутывает края:
MSAA работает, беря больше образцов по краям многоугольника. Сэмплы, взятые слева или справа от края, будут «зелеными» (опять же, учитывая левый край первого изображения), и только один сэмпл будет «на краю», частично сэмплируя из «бежевой» области. Когда образцы смешаны, эффект будет уменьшен.
Возможные решения:
В любом случае вам следует переключиться на,
GL_CLAMP
чтобы предотвратить кровотечение из пикселя на противоположной стороне текстуры. Тогда у вас есть три варианта:Включите сглаживание, чтобы немного сгладить переход между тайлами.
Установите фильтрацию текстуры на
GL_NEAREST
. Это дает всем пикселям твердые края, поэтому края полигонов / спрайтов становятся неразличимыми, но это, очевидно, несколько меняет стиль игры.Добавьте уже обсуждаемую границу 1px, просто убедитесь, что граница имеет цвет смежной плитки (а не цвет противоположного края).
GL_LINEAR
выполнять фильтрацию по краям полигонов / спрайтов.источник
Рассмотрим отображение одного обычного текстурированного квадра в OpenGL. Есть ли какие-либо швы в любом масштабе? Нет никогда. Ваша цель - плотно упаковать все плитки сцены в одну текстуру, а затем отправить их на экран. РЕДАКТИРОВАТЬ Чтобы прояснить это далее: Если у вас есть отдельные ограничивающие вершины на каждом кваде плитки, в большинстве случаев вы будете иметь неоправданные швы. Это то, как работает аппаратное обеспечение графического процессора ... ошибки с плавающей запятой создают эти промежутки на основе текущей перспективы ... ошибки, которых избегают на унифицированном коллекторе, поскольку, если две грани находятся в одном и том же коллекторе(submesh), аппаратное обеспечение будет отображать их без швов, гарантировано. Вы видели это в бесчисленных играх и приложениях. Вы должны иметь одну плотно упакованную текстуру на одной подмешке без двойных вершин, чтобы избежать этого раз и навсегда. Это проблема, которая возникает снова и снова на этом сайте и в других местах: если вы не объединяете угловые вершины своих плиток (каждые 4 плитки имеют угловую вершину), ожидайте швы.
(Учтите, что вам могут даже не понадобиться вершины, за исключением четырех углов всей карты ... зависит от вашего подхода к затенению.)
Чтобы решить: визуализируйте (в 1: 1) все ваши текстуры плитки в FBO / RBO без пробелов, затем отправьте это FBO в стандартный кадровый буфер (экран). Поскольку FBO само по себе представляет собой единую текстуру, вы не можете получить пробелы при масштабировании. Все границы текселей, которые не попадают на границы пикселов экрана, будут смешаны, если вы используете GL_LINEAR .... это именно то, что вы хотите. Это стандартный подход.
Это также открывает ряд различных путей для масштабирования:
источник
Если изображения должны отображаться как одна поверхность, визуализируйте их как одну текстуру поверхности (масштаб 1х) на одной трехмерной сетке / плоскости. Если вы визуализируете каждый как отдельный 3D-объект, у него всегда будут швы из-за ошибок округления.
Ответ @ Nick-Wiggill правильный, я думаю, вы его неправильно поняли.
источник
2 возможности, которые стоит попробовать:
Используйте
GL_NEAREST
вместоGL_LINEAR
фильтрации текстур. (Как отмечает Andon M. Coleman)GL_LINEAR
будет (фактически) размывать изображение очень немного (интерполируя между ближайшими пикселями), что обеспечивает плавный переход цвета от одного пикселя к другому. Это может помочь улучшить внешний вид текстур во многих случаях, так как это препятствует тому, чтобы ваши текстуры имели эти блочные пиксели повсюду. Это также делает так, чтобы немного коричневого от одного пикселя одного тайла могло быть размыто до смежного зеленого пикселя смежного тайла.GL_NEAREST
просто находит ближайший пиксель и использует этот цвет. Нет интерполяции. В результате это на самом деле немного быстрее.Немного уменьшите текстурные координаты для каждой плитки.
Вроде как добавление этого дополнительного пикселя вокруг каждой плитки в качестве буфера, за исключением того, что вместо расширения плитки на изображении вы уменьшаете плитку в программном обеспечении. Плитки размером 14x14 пикселей при рендеринге вместо листов размером 16x16 пикселей.
Возможно, вам даже удастся отделаться плиткой 14,5х14,5 без добавления большого количества коричневого.
--редактировать--
Как показано на изображении выше, вы все еще можете легко использовать степень двух текстур. Таким образом, вы можете поддерживать оборудование, которое не поддерживает текстуры NPOT. Что изменится в ваших текстурных координатах, вместо того, чтобы перейти от
(0.0f, 0.0f)
к(1.0f, 1.0f)
вам, вы перейдете от(0.03125f, 0.03125f)
к(0.96875f, 0.96875f)
.Это немного помещает ваши текс-координаты в элемент мозаичного изображения, что снижает эффективное разрешение в игре (хотя и не на аппаратном уровне, так что текстуры по-прежнему имеют степень двойки), однако должно иметь тот же эффект рендеринга, что и расширение текстуры.
источник
Вот что, я думаю, может произойти: там, где две плитки сталкиваются, компонент х их ребер должен быть одинаковым. Если это не так, они могут округляться в противоположном направлении. Поэтому убедитесь, что они имеют одинаковое значение х. Как вы гарантируете, что это произойдет? Вы можете попытаться, скажем, умножить на 10, округлить и затем разделить на 10 (делайте это только на процессоре, прежде чем ваши вершины попадут в вершинный шейдер). Это должно дать правильные результаты. Кроме того, не рисуйте мозаику по плитке с использованием матриц преобразования, но поместите их в пакетное VBO, чтобы убедиться, что неправильные результаты не приходят из системы с плавающей запятой с потерями IEEE в сочетании с умножением матриц.
Почему я предлагаю это? Поскольку из моего опыта, если вершины имеют точно такие же координаты, когда они выходят из вершинного шейдера, заполнение соответствующих треугольников создаст плавные результаты. Имейте в виду, что что-то, что математически правильно, может быть немного неправильным из-за IEEE. Чем больше вычислений вы выполняете для своих чисел, тем менее точным будет результат. И да, для умножения матриц требуется довольно много операций, которые могут быть выполнены только умножением и сложением при создании вашего VBO, что даст более точные результаты.
Проблема также может заключаться в том, что вы используете таблицу спрайтов (также называемую атласом) и что при выборке текстуры выбираются пиксели смежной текстуры плитки. Чтобы этого не произошло, создайте крошечную рамку в UV-отображениях. Итак, если у вас есть плитка размером 64x64, ваше UV-отображение должно охватывать немного меньше. Сколько? Я думаю, что я использовал в своих играх 1 четверть пикселя с каждой стороны прямоугольника. Поэтому сместите ваши UV на 1 / (4 * widthOfTheAtlas) для компонентов x и 1 / (4 * heightOfTheAtlas) для компонентов y.
источник
Чтобы решить эту проблему в трехмерном пространстве, я смешал массивы текстур с предложением Вольфганга Скайлера. Я поместил 8-пиксельную рамку вокруг моей реальной текстуры для каждой плитки (120x120, 128x128 всего), которая для каждой стороны, которую я мог бы заполнить «обернутым» изображением или только что развернутой стороной. Сэмплер считывает эту область при интерполяции изображения.
Теперь с фильтрацией и mipmapping сэмплер все еще может легко читать за всю 8-пиксельную границу. Чтобы уловить эту небольшую проблему (я говорю «маленькая», потому что это происходит только на нескольких пикселях, когда геометрия действительно перекошена или находится далеко), я разбил плитки на массив текстур, так что каждая плитка имеет свое собственное пространство текстуры и может использовать зажим на кромки.
В вашем случае (2D / flat) я бы, конечно, пошел с рендерингом сцены с идеальным пикселем, а затем с масштабированием желаемой части результата в окне просмотра, как предложил Ник Виггил.
источник