Как избежать текстурного кровотечения в текстурном атласе?

46

В моей игре есть Minecraft-подобная местность из кубов. Я генерирую буфер вершин из данных вокселей и использую атлас текстуры для внешнего вида различных блоков:

текстурный атлас для воксельной местности

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

скриншот местности с полосами неправильного цвета

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

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

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

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

Данияр
источник
4
Проблема заключается в mipmapping - ваши текстурные координаты могут хорошо работать для самого большого mip-уровня, но по мере того, как дела идут дальше, граничащие плитки смешиваются. Вы можете бороться с этим, не используя mipmaps (вероятно, не очень хорошая идея), увеличивая свои защитные зоны (область между тайлами), динамически увеличивая защитные зоны в шейдере на основе уровня mip (хитрый), делая что-то странное при генерации mipmaps (можете Хотя, ни о чем не думаю) .. Я никогда сам не занимался этой проблемой, но есть кое-что, с чего можно начать .. =)
Яри ​​Комппа
Как я уже сказал, к сожалению, отключение mipmapping не решает проблему. Без мип-карт он будет либо интерполировать линейный, GL_LINEARчто даст аналогичный результат, либо выберет ближайший пиксель, GL_NEARESTчто в любом случае также приведет к полосам между блоками. Последняя упомянутая опция уменьшается, но не устраняет дефект пикселей.
Данияр
7
Одно из решений состоит в том, чтобы использовать формат, сгенерировавший мипмапы, тогда вы можете сами создать мипмапы и исправить ошибку. Другое решение состоит в том, чтобы принудительно установить определенный уровень mipmap в вашем фрагментном шейдере, когда вы выбираете текстуру. Другое решение состоит в том, чтобы заполнить mipmaps первым mipmap. Другими словами, когда вы создаете свою текстуру, вы можете просто добавить изображения к этому конкретному изображению, содержащему те же данные.
Tordin
1
У меня была эта проблема раньше, и она была решена добавлением 1-2-пиксельного отступа в ваш атлас между каждой другой текстурой.
Чак Д
1
@Kylotan. Проблема заключается в изменении размера текстуры независимо от того, выполняется ли это с помощью mip-карт, линейной интерполяции или выбора ближайшего пикселя.
Данияр

Ответы:

34

Хорошо, я думаю, что у вас есть две проблемы здесь.

Первая проблема связана с mipmapping. В общем, вы не хотите наивно смешивать atlasing с mipmapping, потому что, если все ваши подтексты не имеют размер 1x1 пикселя, вы будете испытывать наложение текстуры. Добавление отступов просто переместит проблему на более низкий уровень.

Как правило, для 2D, где вы обычно делаете атлас, вы не используете mipmap; в то время как для 3D, где вы делаете mipmap, вы не атласируете (на самом деле, вы снимаете кожу таким образом, что кровотечение не будет проблемой)

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

Давайте предположим, что текстура 8x8. Вы, вероятно, получаете верхнюю левую четверть, используя ультрафиолетовые координаты от (0,0) до (0,5, 0,5):

Неверное отображение

И проблема в том, что при u-координате 0,5 сэмплер будет интерполировать целевой фрагмент, используя половину левого текселя и половину правого текселя. То же самое произойдет в точке с координатами 0 и то же самое с координатами v. Это потому, что вы обращаетесь к границам между текселями, а не к центрам.

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

Правильное отображение

В этом случае вы всегда будете пробовать центр каждого текселя, и у вас не должно быть никакого кровотечения ... Если только вы не используете mipmapping, в этом случае у вас всегда будет кровотечение, начиная с уровня mip, где масштабированная субтекстура не является аккуратно вписаться в сетку пикселей .

В целом, если вы хотите получить доступ к определенному текселю, координаты рассчитываются следующим образом:

function get_texel_coords(x, y, tex_width, tex_height)
    u = (x + 0.5) / tex_width
    v = (y + 0.5) / tex_height
    return u, v
end

Это называется «полупиксельной коррекцией». Там есть более подробное объяснение в здесь , если вы заинтересованы.

Панда Пижама
источник
Ваше объяснение звучит очень многообещающе. К сожалению, так как мне сейчас нужна видеокарта, я пока не могу ее реализовать. Я сделаю это, если придет отремонтированная карта. И я сам вычислю мипакарты атласа без интерполяции по границам тайлов.
Данияр
@sharethis Если вам определенно необходим атлас, убедитесь, что ваши подтексты имеют размеры в степени 2, чтобы вы могли ограничить кровотечение самыми низкими уровнями мипов. Если вы сделаете это, вам не нужно создавать свои собственные мипмапы.
Панда Пижама
Даже текстуры PoT-размера могут иметь артефакты кровотечения, потому что билинейная фильтрация может выполнять выборку через эти границы. (По крайней мере, в реализациях, которые я видел.) Что касается полупиксельной коррекции, должна ли она применяться в этом случае? Например, если я хочу нанести на карту его текстуру камня, то это всегда будет от 0,0 до 0,25 вдоль координат U и V?
Kylotan
1
@Kylotan Если размер вашей текстуры четный, а все подтекстуры имеют четные размеры и расположены по четным координатам, билинейная фильтрация при уменьшении размера текстуры вдвое не будет смешиваться между подтекстурами. Обобщайте для степеней двойки (если вы видите артефакты, вы, вероятно, используете бикубическую, а не билинейную фильтрацию). Что касается полупиксельной коррекции, это необходимо, потому что 0,25 - это не самый правый тексель, а средняя точка между двумя подтекстами. Чтобы представить это в перспективе, представьте, что вы растеризатор: какого цвета текстура (0,00, 0,25)?
Панда Пижама
1
@Sharethis проверить этот другой ответ, который я написал, который может помочь вам gamedev.stackexchange.com/questions/50023/…
Panda Pajama
19

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

http://www.opengl.org/wiki/Array_Texture

ccxvii
источник
Если они доступны, это гораздо лучшее решение - вы получаете и другие функции, такие как обтекание текстур, которые вам не хватает в атласах.
Яри ​​Комппа
@JariKomppa. Я не хочу использовать массивы текстур, потому что таким образом у меня будут дополнительные шейдеры для ландшафта. Поскольку движок, который я пишу, должен быть как можно более общим, я не хочу делать это исключение. Есть ли способ создать один шейдер, который может обрабатывать как текстурные массивы, так и отдельные текстуры?
Данияр
8
@sharethis Отказ от однозначно превосходящих технических решений, потому что вы хотите быть «общим», - это рецепт неудачи. Если вы пишете игру, не пишите движок.
Сэм Хоцевар
@SamHocevar. Я пишу движок, и мой проект посвящен конкретной архитектуре, в которой компоненты полностью отделены. Поэтому компонент формы не должен заботиться о компоненте terrain, который должен обеспечивать только стандартные буферы и стандартную текстуру.
Данияр
1
@ поделись, все в порядке. Меня как-то ввела в заблуждение часть вопроса «В моей игре».
Сэм Хоцевар
6

После долгих попыток решить эту проблему, я наконец нашел решение.

Чтобы использовать оба, текстурный атлас и mipmapping, я должен сам выполнить понижающую дискретизацию, потому что в противном случае OpenGL интерполировал бы границы границ плиток в атласе. Более того, мне нужно было установить правильные параметры текстуры, потому что интерполяция между мипмапами также вызывала бы наложение текстуры.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

С этими параметрами кровотечение не происходит.

Есть одна вещь, которую вы должны заметить при предоставлении пользовательских mipmaps. Поскольку нет смысла сокращать атлас, даже если каждая плитка уже есть 1x1, максимальный уровень mipmap должен быть установлен в соответствии с тем, что вы предоставляете.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); // pick mipmap level 7 or lower

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

Данияр
источник
приятно знать, что вы решили это. Вы должны пометить этот ответ как правильный, а не мой.
Панда Пижама
mipmapping - это метод исключительно для понижающей дискретизации, который не имеет смысла для повышающей выборки. Идея состоит в том, что если вы собираетесь нарисовать маленький треугольник, потребуется много времени, чтобы сэмплировать большую текстуру, чтобы получить несколько пикселей из нее, поэтому вы должны предварительно сэмплировать ее и использовать соответствующий уровень mip, когда рисование маленьких треугольников. Для больших треугольников использование чего-либо, отличного от большего уровня mip, не имеет смысла, поэтому ошибочно делать что-то вродеglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_FILTER, GL_NEAREST_MIPMAP_LINEAR);
Panda Pajama
@PandaPajama. Вы правы, я исправил свой ответ. Для того, чтобы установить GL_TEXTURE_MAX_FILTERна GL_NEAREST_MIPMAP_LINEARпричины кровотечения не по причине множественного отображения, но по причине интерполяции. Так что в этом случае это эквивалентно, GL_LINEARтак как в любом случае используется самый низкий уровень mipmap.
Данияр
@danijar - Можете ли вы объяснить более подробно, как вы выполняете понижающую дискретизацию самостоятельно, и как вы сообщаете OpenGL, где (и когда) использовать атлас пониженной выборки?
Тим Кейн
1
@TimKane Разделить атлас на плитки. Для каждого уровня mipmap билинейно уменьшайте плитки, объединяйте их и загружайте в графический процессор, указав уровень mipmap в качестве параметра для glTexImage2D(). Графический процессор автоматически выбирает правильный уровень в зависимости от размера объектов на экране.
Данияр
4

Похоже, что проблема может быть вызвана MSAA. Смотрите этот ответ и связанную статью :

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

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

msell
источник
Как я уже писал в другом комментарии, на данный момент у меня нет работающей видеокарты, поэтому я не могу проверить, является ли это реальной проблемой. Я сделаю это, как только смогу.
Данияр
Я отключил аппаратное сглаживание, но кровотечение все еще остается. Я уже делаю текстуры, просматривая фрагментный шейдер.
Данияр
1

Это старая тема, и у меня была проблема, похожая на тему.

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

public static void tween(Camera cam, Direction dir, Vec2 buffer, Vec2 start, Vec2 end) {
    if(step > steps) {
        tweening = false;
        return;
    }

    final float alpha = step/steps;

    switch(dir) {
    case UP:
    case DOWN:
        buffer = new Vec2(start.getX(), Util.lerp(start.getY(), (float)end.getY(), alpha));
        cam.getPosition().set(buffer);
        break;
    case LEFT:
    case RIGHT:
        buffer = new Vec2(Util.lerp(start.getX(), end.getX(), alpha), start.getY());
        cam.getPosition().set(buffer);
        break;
    }

    step += 1;
}

Вся проблема заключалась в том, что Util.lerp () возвращает число с плавающей запятой или двойное число. Это было плохо, потому что камера переводила вне сетки и вызывала некоторые странные проблемы с фрагментами текстуры, где> 0 в X и> 0 в Y.

TLDR: не твинируй и не используй поплавки

Коди Кодер
источник