2012-04-06 23 views
27

Pracuję na silniku 2d. Działa już całkiem nieźle, ale ciągle dostaję błędy pikseli.Opengl pixel idealny rysunek 2D

Na przykład moje okno ma 960 x 540 pikseli, rysuję linię od (0, 0) do (959, 0). Spodziewam się, że każdy piksel na linii zerowej 0 zostanie ustawiony na kolor, ale nie: piksel o wartości maksymalnie prawej nie zostanie narysowany. Ten sam problem, gdy rysuję pionowo do piksela 539. Naprawdę muszę narysować (960, 0) lub (0, 540), aby go narysować.

Ponieważ urodziłem się w erze pikseli, jestem przekonany, że to nie jest właściwy wynik. Gdy mój ekran miał wielkość 320x200 pikseli, mogłem rysować od 0 do 319 i od 0 do 199, a mój ekran byłby pełny. Teraz kończę z ekranem, którego piksel prawy/dolny nie jest narysowany.

To może być spowodowane różnymi rzeczami: gdzie mogę się spodziewać, że prymityw linii prostej OpenGL zostanie narysowany od piksela do piksela włącznie, że ostatni piksel jest tak naprawdę wyłączny? Czy to to? Moja macierz projekcji jest niepoprawna? Jestem pod fałszywym założeniem, że kiedy mam backbuffer z 960x540, to faktycznie ma jeden piksel więcej? Coś jeszcze?

Czy ktoś może mi pomóc? Od dawna zajmowałem się tym problemem i za każdym razem, gdy myślałem, że wszystko jest w porządku, po pewnym czasie zobaczyłem, że tak naprawdę nie było.

Oto niektóre z mojego kodu, starałem się rozebrać go tak bardzo, jak to możliwe. Kiedy zadzwonię do mojej funkcji liniowej, każda współrzędna jest dodawana z 0.375, 0.375, aby była poprawna zarówno dla adapterów ATI, jak i 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

Zaczynam myśleć, że to jest "punkt wyłączności" o czym mówię. Jeśli używasz QT lub C++ Builder/Delphi lub nawet Windows API do rysowania linii, piksele są rysowane z wyłączeniem punktu końcowego. Więc możliwe, że to samo dzieje się w opengl, a prawdopodobnie także podczas rysowania trójkątów? Gdy narysujesz prostokąt z QT, konstruktorem, ..., wykluczony zostanie również prawy dolny róg ... – scippie

+0

Poniższy test pokazuje zgodność z tym, co powiedziałem powyżej: kiedy używam GL_POINT do wypełnienia pikseli we wszystkich rogach okna, te współrzędne są takie, jak się spodziewam: (0,0), (szerokość-1, wysokość -1). To pokazuje mi, że mój sposób rysowania jest poprawny i że linia (i ewentualnie trójkąt) po prostu nie będzie zawierała punktu końcowego. Czy ktoś może się zgodzić? – scippie

+0

powiązane http://stackoverflow.com/questions/5467218/opengl-2d-hud-over-3d –

Odpowiedz

15

W OpenGL linie są rasteryzowane za pomocą reguły "Diamond Exit". To prawie samo jak powiedzenie, że koniec koordynować jest wyłączna, ale nie całkiem ...

To właśnie specyfikacja OpenGL ma do powiedzenia: http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node47.html

Również spojrzeć na OpenGL FAQ , http://www.opengl.org/archives/resources/faq/technical/rasterization.htm, pozycja "14.090 Jak uzyskać dokładną pikselizację linii?". Mówi "Specyfikacja OpenGL pozwala na szeroki zakres sprzętu do renderowania linii, więc dokładna pixelizacja może w ogóle nie być możliwa."

Wiele osób twierdzi, że w ogóle nie należy używać linii w OpenGL. Ich zachowanie opiera się na działaniu starego sprzętu SGI, a nie na tym, co ma sens. (I linie o szerokościach> 1 są prawie niemożliwe do użycia w sposób, który wygląda dobrze!)

+0

Dzięki, ta odpowiedź jest bardzo pomocna! Linie są przeznaczone do testowania, a nie do chłodzenia produktu końcowego. Tak więc jestem bardzo zadowolony z GL_LINES w tym momencie. Czy mogę założyć, że trójkąty mają taki sam efekt? Mam na myśli, że jeśli narysuję wypełniony prostokąt rysując dwa trójkąty, czy mogę oczekiwać tego samego wyniku? – scippie

+3

Niestety: 14.120: Wypełnione prymitywy i prymitywy liniowe podlegają innym regułom rasteryzacji. – scippie

+0

> "Linie o szerokościach> 1 są prawie niemożliwe do użycia w sposób, który wygląda dobrze!" Można je narysować jako cienkie prostokąty, a następnie wykonać trochę magii w cieniowanym fragmencie, aby wyglądały jak linie z prawie wszystkimi właściwościami wizualnymi. Ale to może być kosztowne. –

6

Należy zauważyć, że przestrzeń współrzędnych OpenGL ma pojęcie liczb całkowitych, wszystko jest pływak i „centrum” piksela OpenGL jest naprawdę na 0.5,0.5 zamiast jej lewym górnym rogu. Dlatego jeśli chcesz szeroką linię 1px od 0,0 do 10,10 włącznie, naprawdę musisz narysować linię od 0,5,0,5 do 10,510,5.

Będzie to szczególnie widoczne po włączeniu wygładzania, jeśli masz wygładzanie i próbujesz narysować od 50,0 do 50,100, możesz zobaczyć rozmytą szeroką linię 2px, ponieważ linia spadła pomiędzy dwoma piksele.

+1

W pełni rozumiem, o czym mówisz, ale to nie jest problem. Używając poprawnej matrycy projekcyjnej i dodając 0,375 do każdego piksela, zarówno ATI, jak i nvidia zaokrąglają liczby tak, aby współrzędne były dokładnymi pikselami. Jak inaczej na przykład Mac OS rysowałby doskonałe okna w pikselach, gdyby nie były perfekcyjne w pikselach? – scippie

+0

@scippie: Powinieneś dodać 0.5. 0,5 to środek piksela. 0.375 nie jest. – SigTerm

+4

@SigTerm: to nie jest prawda, powinieneś wypróbować to na sprzęcie ATI i NVidia i zaczyna się koszmar. ATI i NVidia zaokrąglają inaczej. Używając 0.375, zaokrąglenie jest takie samo w obu przypadkach, a wyniki są doskonałe (choć teoretycznie może nie być poprawne). – scippie