2D-чертеж в OpenGL: линейная фильтрация с точностью пикселя в собственном размере

Краткая версия:

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

Подробнее о проблеме:

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

Например, здесь находится шахматная доска размером 64x64:

checkerboard

... и один в своем атласе текстуры:

checkerboard in texture atlas

Примечание. Предположим, что окно просмотра настроено для отображения 1:1 на пиксели устройства.

Подходы

Изменить для ясности: обратите внимание, что первые 2 подхода ниже не дают желаемого результата, и там конкретно указано, что не работает.

1. Чтобы нарисовать спрайт в собственном размере, просто используйте GL_NEAREST

  • установить OpenGL для использования фильтрации MIN/mag GL_NEAREST
  • чтобы нарисовать в позиции (x, y), поместите вершины из (x, y) в (x + 64, y + 64)
  • использовать координаты текстуры (0,0) → (64/atlas_size, 64/atlas_size)

Это хорошо для рисования в собственном размере, но фильтрация NEAREST дает плохие результаты, когда пользователь рисует объект размером не 64x64. Это также заставляет тексели выравниваться с сеткой пикселей, что дает плохие результаты, когда объект рисуется в нецелочисленных расположениях пикселей:

drawn with gl_nearest at pixel offset of 0.25drawn with gl_nearest at pixel offset of 0.5

2. Включить GL_LINEAR

  • С линейной фильтрацией нам нужно сдвинуть наши uv-координаты на центр текселей: (0.5/atlas_size,0.5/atlas_size) до (63.5/atlas_size,63.5/atlas_size). В противном случае для самых отдаленных пикселей линейная фильтрация будет отображать соседние спрайты в атласе.
  • Затем нам нужно изменить наши вершины, так как продолжение использования вершин из (0,0) в (64,64) будет растягивать текстуру на 1px в обоих направлениях, например:

drawn with uv coords shifted in by 0.5 texels, resulting in stretching

  • поэтому нам нужно использовать вершины из (0,5,0,5) - (63,5,63,5). Как правило, когда текстура рисуется в соотношении ее собственного размера (a, b), мы должны будем поместить наши вершины "внутрь", по-моему, (a/2, b/2). Результат дает (на фиолетовом фоне):

drawn with uv shifted in by 0.5 texels, and vertices by 0.5 pixels

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

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

3. Pad спрайты в атласе текстуры

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

  • с 1 слоем прозрачных пикселей и развернуть вершины (0.5a, 0.5b), а не сжимать их
  • со второй копией внешних ярлыков спрайта и вернитесь к выборке текстуры от (0,0) до (64/atlas_size, 64/atlas_size).

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

Я что-то пропустил? Существует ли общее решение этой проблемы?

Ответы

Ответ 1

Трудно точно узнать, что такое "правильная вещь", которую вы ищете. Если у вас есть спрайт 64x64, и вы хотите масштабировать его до 65x65 или 63x63, никакая фильтрация не сделает его хорошим. Когда вы добавляете сглаживание в микс, помните, что мультисэмплинг не суперсэмплинг, и поэтому ваши текстуры не будут волшебным образом получать более мягкие интерьеры.


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

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

Вы не должны пытаться исправлять это, регулируя координаты текстуры (и, следовательно, вершины), так как это неправильно масштабирует координату текстуры по текстуре. Вместо этого я рекомендую закрепить текстурные координаты в желаемом диапазоне плюс/минус 0,5/texture_res в вашем шейдере.

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