Каков источник этих промежутков пикселей между идентичными вершинами в OpenGL Ortho? Как я могу их устранить?

Несмотря на передачу равных (точно равных) координат для «смежных» краев, я получаю странные линии между соседними элементами при масштабировании сетки визуализированных плиток.

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

Некоторые скриншоты:

Синие линии - это чистый цвет, просвечивающий сквозь них. Выбранная текстура не имеет прозрачных промежутков на листе тайлов, так как неиспользуемые тайлы окрашены в пурпурный цвет, а фактическая прозрачность обрабатывается альфа-слоем. Соседние плитки на листе имеют полную непрозрачность. Масштабирование достигается путем установки масштаба на нормализованное значение, полученное с помощью триггера геймпада между 1f и 2f, поэтому я не знаю, какой фактический масштаб был применен, когда был сделан снимок, за исключением макс/мин.

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

Масштабируется до 1x:

1x

Масштабируется до A, 1x ‹ Ax ‹ Bx :

Топор

Масштабируется до B, Ax ‹ Bx ‹ Cx:

Bx

Масштабируется до C, Bx ‹ Cx ‹ 2x:

Cx

Масштабируется в 2 раза:

2x

Функция настройки проекции

Для настройки ортогональной проекции (изменения только при изменении размера экрана):

    .......
    float nw, nh;
    nh = Display.getHeight();
    nw = Display.getWidth();
    GL11.glOrtho(0, nw, nh, 0, 1, -1);
    orthocenter.setX(nw/2);  //this is a Vector2, floats for X and Y, direct assignment.
    orthocenter.setY(nh/2);
    .......

Для целей снимка экрана nw равно 512, nh равно 384 (неявно преобразовано из int). Они никогда не меняются в приведенном выше примере.

Общий код чертежа GL

После вырезания ненужных атрибутов, которые не устранили проблему при вырезании:

@Override
public void draw(float xOffset, float yOffset, float width, float height,
        int glTex, float texX, float texY, float texWidth, float texHeight) {
    GL11.glLoadIdentity();
    GL11.glTranslatef(0.375f, 0.375f, 0f); //This is supposed to fix subpixel issues, but makes no difference here

    GL11.glTranslatef(xOffset, yOffset, 0f);

    if(glTex != lastTexture){
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, glTex);
        lastTexture = glTex;
    }
    GL11.glBegin(GL11.GL_QUADS);

    GL11.glTexCoord2f(texX,texY + texHeight);
    GL11.glVertex2f(-height/2, -width/2);

    GL11.glTexCoord2f(texX + texWidth,texY + texHeight);
    GL11.glVertex2f(-height/2, width/2);

    GL11.glTexCoord2f(texX + texWidth,texY);
    GL11.glVertex2f(height/2, width/2);

    GL11.glTexCoord2f(texX,texY);
    GL11.glVertex2f(height/2, -width/2);

    GL11.glEnd();
}

Код рисования сетки (отбрасывая те же параметры, что и «draw»):

//Externally there is tilesize, which contains tile pixel size, in this case 32x32
public void draw(Engine engine, Vector2 offset, Vector2 scale){
  int xp, yp;                                   //x and y position of individual tiles
  for(int c = 0; c<width; c++){                 //c as in column
      xp = (int) (c*tilesize.a*scale.getX());   //set distance from chunk x to column x
      for(int r = 0; r<height; r++){            //r as in row
        if(tiles[r*width+c] <0) continue;       //skip empty tiles ('air')
        yp = (int) (r*tilesize.b*scale.getY()); //set distance from chunk y to column y
        tileset.getFrame(tiles[r*width+c]).draw(                  //pull 'tile' frame from set, render.
            engine,                                               //drawing context
            new Vector2(offset.getX() + xp, offset.getY() + yp),  //location of tile
            scale                                                 //scale of tiles
          );
      }
    }
  }

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

Мой анализ

Математически каждая позиция является точным кратным масштабу*размеру плитки либо в направлении x, либо в направлении y, либо в обоих направлениях, которые затем добавляются к смещению положения сетки. Затем он передается как смещение в код рисования, который преобразует это смещение с помощью glTranslatef, затем рисует плитку с центром в этом месте, уменьшая вдвое размеры, а затем рисуя каждую пару плюс-минус.

Это должно означать, что когда плитка 1 рисуется, скажем, в начале координат, она имеет смещение, равное 0. Затем Opengl получает указание отрисовать четырехугольник с левым краем на -полуширине, правым краем на +половине, верхним краем на -половине. , а нижний край на +полувысоте. Затем ему предлагается нарисовать соседа, тайл 2, со смещением в одну ширину, поэтому он переводит от 0 до этой ширины, затем рисует левый край на -halfwidth, который по координатам должен быть точно таким же, как правый край тайла 1. Само по себе это должно работать, и оно работает. При рассмотрении постоянной шкалы она как-то ломается.

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

  • В OpenGL возникают проблемы с заполнением подпикселей, т.е. заполнение слева от вершины не заполняет пространство пикселей, содержащее вершину, и заполнение справа от той же самой вершины также не заполняет пространство пикселей, содержащее вершину.
  • Я сталкиваюсь с проблемами точности с плавающей запятой, где каким-то образом X+width/2 не равно X+width - width/2, где width = tilewidth*scale, tilewidth — целое число, а X — число с плавающей запятой.

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


person Community    schedule 08.09.2014    source источник


Ответы (1)


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

Математически каждая позиция является точным кратным [...]

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

В частности, в вашем случае похоже, что вы полагаетесь на личности этой формы:

i * width + width/2 == (i + 1) * width - width/2

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

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

В случае координат на сетке вы можете вычислить координаты для каждой линии сетки (границы плитки) один раз, а затем использовать эти значения для всех операций рисования. Скажем, если у вас есть n плитки в направлении x, вы вычисляете все значения x как:

x[i] = i * width;

а затем при рисовании плитки i используйте x[i] и x[i + 1] в качестве левой и правой координат x.

person Reto Koradi    schedule 08.09.2014
comment
Принимая во внимание, что в этой настройке ортопедия устанавливает целочисленное идеальное пространство пикселей (1f соответствует 1 пикселю), ошибка, необходимая для создания зазора в 1 пиксель, как видно, должна быть >.5f, что кажется далеким, и должна была бы имеют очень специфический сценарий, когда ошибка сосредоточена на точном целом числе, так что растеризатор округляет меньшее значение до пикселя 1, полностью пропускает пиксель 2, а затем округляет до пикселя 3. - person ; 08.09.2014
comment
Вероятно, поэтому вы видите видимые артефакты только при масштабировании. - person Reto Koradi; 08.09.2014
comment
Мне определенно придется подумать об изменении функции рисования платформы, чтобы разрешить спецификации вершин вместо выполнения вычислений центрирования для каждой вершины, но кажется ли разумным подходить к этой проблеме на этапе, когда я переключаю все с непосредственного режима на использование шейдеров? т.е. исправление поплавков в вершинном шейдере для более подходящего сопоставления с пиксельным пространством до попадания в растеризатор? - person ; 08.09.2014
comment
@IronPhysco: Вот еще одна вещь, которую следует учитывать: OpenGL не размещает центры пространства пикселей на экране в целочисленных координатах. Если вы используете glOrtho(0, viewport_width, 0, viewport_height, …), то центры пикселей фактически находятся на ….5, а не на ….0. Это делает все это еще более восприимчивым к ошибкам округления. - person datenwolf; 08.09.2014