2012-04-06 14 views
27

Sto lavorando a un motore 2D. Funziona già abbastanza bene, ma continuo ad avere errori di pixel.Opengl pixel disegno 2D perfetto

Ad esempio, la mia finestra è di 960x540 pixel, disegno una linea da (0, 0) a (959, 0). Mi aspetto che ogni pixel su scan-line 0 sia impostato su un colore, ma no: il pixel più a destra non viene disegnato. Lo stesso problema quando disegno verticalmente sul pixel 539. Ho davvero bisogno di disegnare su (960, 0) o (0, 540) per disegnarlo.

Come sono nato nell'era dei pixel, sono convinto che questo non è il risultato corretto. Quando il mio schermo era grande 320x200 pixel, potevo disegnare da 0 a 319 e da 0 a 199, e il mio schermo sarebbe pieno. Ora finisco con uno schermo con un pixel a destra/in basso non disegnato.

Questo può essere dovuto a cose diverse: dove mi aspetto che la linea primitiva opengl sia disegnata da un pixel a un pixel incluso, che l'ultimo pixel sia effettivamente esclusivo? È così? la mia matrice di proiezione non è corretta? Sono sotto falso presupposto che quando ho un backbuffer di 960x540, che in realtà ha un pixel in più? Qualcos'altro?

Qualcuno può aiutarmi? Ho esaminato questo problema da molto tempo, e ogni volta che pensavo fosse ok, ho visto dopo un po 'che in realtà non lo era.

Ecco un po 'del mio codice, ho provato a eliminarlo il più possibile. Quando chiamo la mia funzione di linea, ogni coordinata viene aggiunta con 0.375, 0.375 per renderla corretta su entrambi gli adattatori ATI e 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

Sto iniziando a pensare che sia la cosa "esclusiva esclusiva" di cui sto parlando. Se si utilizza QT o C++ Builder/Delphi o anche Windows API per disegnare linee, i pixel vengono disegnati escludendo il punto finale. Quindi è possibile che lo stesso sia fatto in opengl, e probabilmente anche quando si disegnano triangoli? Quando si disegna un rettangolo con QT, builder, ..., si ottiene anche l'angolo in basso a destra escluso ... – scippie

+0

Il seguente test mostra un accordo con quello che ho detto sopra: quando uso GL_POINT per riempire i pixel in tutti gli angoli della finestra, queste coordinate sono come mi aspetto che siano: (0,0), (larghezza-1, altezza -1). Questo mi mostra che il mio modo di disegnare è corretto e che la linea (e probabilmente il triangolo) non includerà semplicemente il punto finale. Qualcuno può concordare? – scippie

+0

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

risposta

15

In OpenGL, le righe vengono rasterizzate utilizzando la regola "Diamond Exit". Questo è quasi equivale a dire che coordinano la fine è esclusiva, ma non del tutto ...

Questo è ciò che le specifiche OpenGL ha da dire: http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node47.html

hanno anche uno sguardo alle FAQ OpenGL , http://www.opengl.org/archives/resources/faq/technical/rasterization.htm, articolo "14.090 Come ottengo la pixelizzazione esatta delle linee?". Dice "La specifica OpenGL consente una vasta gamma di hardware per il rendering di linee, quindi la pixelizzazione esatta potrebbe non essere affatto possibile."

Molti sostengono che non si dovrebbero usare le righe in OpenGL. Il loro comportamento si basa su come funzionava l'hardware SGI antico, non su ciò che aveva senso. (E le linee con larghezze> 1 sono quasi impossibili da usare in un modo che sembra buono!)

+0

Grazie, questa risposta è molto utile! Le linee sono a scopo di test, non per la freschezza del prodotto finale. Quindi sono molto felice con GL_LINES a questo punto. Posso supporre che i triangoli abbiano lo stesso risultato? Voglio dire, se disegno un rettangolo pieno disegnando due triangoli, posso aspettarmi lo stesso risultato? – scippie

+3

Ci scusiamo: 14.120: le primitive e le primitive line riempite seguono regole diverse per la rasterizzazione. – scippie

+0

> "E le linee con larghezze> 1 sono quasi impossibili da usare in un modo che sembra buono!" Si possono disegnare come rettangoli sottili e poi fare un po 'di magia nello shader dei frammenti per farli sembrare linea con quasi tutte le proprietà visive. Ma questo potrebbe essere costoso. –

6

noti che OpenGL spazio di coordinate non ha alcuna nozione di numeri interi, tutto è un galleggiante e il "centro" di un pixel OpenGL è davvero al 0.5,0.5 invece del suo angolo superiore sinistro. Pertanto, se si desidera una linea di larghezza 1px compresa tra 0,0 e 10,10, è necessario disegnare una linea da 0,5,0,5 a 10,5,10,5.

Questo sarà particolarmente evidente se si attiva l'anti-aliasing, se si dispone di anti-aliasing e si tenta di disegnare da 50,0 a 50,100 si potrebbe vedere una linea sfocata di 2px wide perché la linea è caduta tra due pixel.

+1

Capisco perfettamente di cosa stai parlando ma non è un problema qui. Usando la matrice di proiezione corretta e aggiungendo 0,375 a ogni pixel, sia ATI che nvidia arrotondano i numeri in modo che le coordinate siano pixel esatti. In quale altro modo, ad esempio, mac os disegnerebbe pixel perfect windows se non fossero pixel perfetti? – scippie

+0

@scippie: in realtà dovresti aggiungere 0.5. 0,5 è il centro del pixel. 0,375 non lo è. – SigTerm

+4

@SigTerm: questo non è vero, dovresti provarlo su hardware ATI e NVidia e l'incubo inizia. ATI e NVidia ruotano in modo diverso. Usando lo 0,375, l'arrotondamento è lo stesso su entrambi e i risultati sono perfetti (anche se potrebbe non essere corretto teoricamente) – scippie