2012-04-06 2 views
27

Я работаю над двигателем 2d. Он уже работает неплохо, но я продолжаю получать пиксельные ошибки.Opengl pixel perfect 2D drawing

Например, мое окно составляет 960x540 пикселей, я рисую линию от (0, 0) до (959, 0). Я ожидал бы, что каждый пиксель на линии сканирования 0 будет установлен в цвет, но нет: правый пиксель не нарисован. Такая же проблема, когда я рисую вертикально на пиксель 539. Мне действительно нужно рисовать (960, 0) или (0, 540), чтобы нарисовать ее.

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

Это может быть связано с различными вещами: , где я ожидаю, что примитив линии opengl будет отрисован от пикселя до пикселя включительно, последний пиксель на самом деле является эксклюзивным? Это оно? Моя проекционная матрица неверна? Я под ложным предположением, что когда у меня есть backbuffer 960x540, у этого на самом деле есть еще один пиксель? Что-то еще?

Может кто-нибудь, пожалуйста, помогите мне? Я давно искал эту проблему, и каждый раз, когда я думал, что все в порядке, через какое-то время я увидел, что на самом деле это не так.

Вот некоторые из моих кодов, я попытался снять его как можно больше. Когда я вызываю свою линейную функцию, каждая координата добавляется с 0,375, 0,375, чтобы сделать ее правильной как для адаптеров ATI, так и для nvidia.

int width = resX(); 
int height = resY(); 

for (int i = 0; i < height; i += 2) 
    rm->line(0, i, width - 1, i, vec4f(1, 0, 0, 1)); 
for (int i = 1; i < height; i += 2) 
    rm->line(0, i, width - 1, i, vec4f(0, 1, 0, 1)); 

// when I do this, one pixel to the right remains undrawn 

void rendermachine::line(int x1, int y1, int x2, int y2, const vec4f &color) 
{ 
    ... some code to decide what std::vector the coordinates should be pushed into 
    // m_z is a z-coordinate, I use z-buffering to preserve correct drawing orders 
    // vec2f(0, 0) is a texture-coordinate, the line is drawn without texturing 
    target->push_back(vertex(vec3f((float)x1 + 0.375f, (float)y1 + 0.375f, m_z), color, vec2f(0, 0))); 
    target->push_back(vertex(vec3f((float)x2 + 0.375f, (float)y2 + 0.375f, m_z), color, vec2f(0, 0))); 
} 

void rendermachine::update(...) 
{ 
    ... render target object is queried for width and height, in my test it is just the back buffer so the window client resolution is returned 
    mat4f mP; 
    mP.setOrthographic(0, (float)width, (float)height, 0, 0, 8000000); 

    ... all vertices are copied to video memory 

    ... drawing 
    if (there are lines to draw) 
     glDrawArrays(GL_LINES, (int)offset, (int)lines.size()); 

    ... 
} 

// And the (very simple) shader to draw these lines 

// Vertex shader 
    #version 120 
    attribute vec3 aVertexPosition; 
    attribute vec4 aVertexColor; 
    uniform mat4 mP; 
    varying vec4 vColor; 
    void main(void) { 
     gl_Position = mP * vec4(aVertexPosition, 1.0); 
     vColor = aVertexColor; 
    } 

// Fragment shader 
    #version 120 
    #ifdef GL_ES 
    precision highp float; 
    #endif 
    varying vec4 vColor; 
    void main(void) { 
     gl_FragColor = vColor.rgb; 
    } 
+0

Я начинаю думать, что это «точечное эксклюзивное», о котором я говорю. Если вы используете QT или C++ Builder/Delphi или даже API Windows для рисования линий, пиксели рисуются за исключением конечной точки. Итак, возможно, что то же самое сделано в opengl, и, вероятно, также при рисовании треугольников? Когда вы рисуете прямоугольник с QT, builder, ..., вы также получаете нижний правый угол, исключенный ... – scippie

+0

Следующий тест показывает согласие с тем, что я сказал выше: когда я использую GL_POINT для заполнения пикселей во всех углах окна, эти координаты, как я ожидаю, будут такими: (0,0), (ширина-1, высота -1). Это показывает мне, что мой способ рисования верен и что линия (и, возможно, треугольник) просто не включает конечную точку. Может кто-нибудь согласиться? – scippie

+0

связано http://stackoverflow.com/questions/5467218/opengl-2d-hud-over-3d –

ответ

15

В OpenGL строки растеризуются с использованием правила «Алмазный выход». Это почти же, как и о том, что конец координат является эксклюзивным, но не совсем ...

Это то, что OpenGL спецификации должен сказать: http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node47.html

также посмотреть на OpenGL FAQ , http://www.opengl.org/archives/resources/faq/technical/rasterization.htm, item "14.090 Как получить точную пикселизацию строк?". В нем говорится: «Спецификация OpenGL позволяет использовать широкий спектр оборудования для рендеринга линий, поэтому точная пикселизация может быть невозможна вообще».

Многие утверждают, что вы не должны использовать линии в OpenGL вообще. Их поведение основано на том, как работает старое SGI-оборудование, а не на том, что имеет смысл. (И линии с шириной> 1 почти невозможно использовать таким образом, чтобы это выглядело хорошо!)

+0

Спасибо, этот ответ очень полезен! Линии предназначены для тестирования, а не для охлаждения продукта. Поэтому я очень доволен GL_LINES на этом этапе. Могу ли я предположить, что треугольники имеют одинаковый результат? Я имею в виду, если я рисую заполненный прямоугольник, рисуя два треугольника, могу ли я ожидать того же результата? – scippie

+3

К сожалению: 14.120: заполненные примитивы и примитивы строк соответствуют различным правилам растеризации. – scippie

+0

> «И линии с шириной> 1 почти невозможно использовать так, как это выглядит!» Можно нарисовать их как тонкие прямоугольники, а затем сделать некоторые магии в шейдере фрагмента, чтобы они выглядели как линии с почти любыми визуальными свойствами. Но это может быть дорого. –

6

Обратите внимание, что OpenGL координатного пространства не имеет ни малейшего представления о целых числах, весь поплавок и «центр» из OpenGL пикселя действительно на 0.5,0.5 вместо его верхнего левого угла. Поэтому, если вам нужна линия шириной 1px от 0,0 до 10,10 включительно, вам действительно нужно провести линию от 0,5,0,5 до 10,5,10,5.

Это будет особенно заметно, если вы включите сглаживание, если у вас есть сглаживание, и вы пытаетесь провести от 50,0 до 50,100, вы можете увидеть размытую 2px широкую линию, потому что линия упала между двумя пиксели.

+1

Я полностью понимаю, о чем вы говорите, но это не проблема. Используя правильную матрицу проекции и добавляя 0,375 к каждому пикселю, оба ATI и nvidia будут округлять числа, чтобы координаты были точными пикселями. Как еще, например, mac os нарисовал бы идеальные окна с пикселями, если бы они не были идеальным пикселем? – scippie

+0

@scippie: Вы должны добавить 0.5. 0.5 - середина пикселя. 0,375 нет. – SigTerm

+4

@SigTerm: это неправда, вы должны попробовать его на аппаратных средствах ATI и NVidia, и начнется кошмар. ATI и NVidia работают по-разному. Используя 0,375, округление одинаково на обоих, и результаты являются идеальными (хотя теоретически это может быть неверно) – scippie