2012-04-20 4 views
7

Sto cercando di ottenere il blocco degli oggetti in movimento (in generale) e delle strisce (in particolare) in modo più efficiente in opengl e quindi sto scrivendo un'applicazione in cui più segmenti di linea viaggiano con una velocità costante da destra a sinistra . In ogni momento il punto più a sinistra verrà rimosso, l'intera linea verrà spostata a sinistra e verrà aggiunto un nuovo punto all'estremità destra della linea (questo nuovo punto dati viene trasmesso/ricevuto/calcolato al volo ogni 10ms circa). Per illustrare quello che voglio dire, vedo questa immagine:qual è il modo più efficiente di spostare più oggetti (memorizzati in VBO) nello spazio? dovrei usare glTranslatef o uno shader?

example showing line strip

Perché voglio lavorare con molti oggetti, ho deciso di usare oggetti vertex buffer per ridurre al minimo la quantità di gl* chiamate. Il mio codice corrente simile a questa:

A) di impostazione vertici iniziali:

# calculate my_func(x) in range [0, n] 
# (could also be random data) 
data = my_func(0, n) 

# create & bind buffer 
vbo_id = GLuint() 
glGenBuffers(1, vbo_id); 
glBindBuffer(GL_ARRAY_BUFFER, vbo_id) 

# allocate memory & transfer data to GPU 
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_DYNAMIC_DRAW) 

B) i vertici aggiornamento:

draw(): 

    # get new data and update offset 
    data = my_func(n+dx, n+2*dx) 

    # update offset 'n' which is the current absolute value of x. 
    n = n + 2*dx 

    # upload data 
    glBindBuffer(GL_ARRAY_BUFFER, vbo_id) 
    glBufferSubData(GL_ARRAY_BUFFER, n, sizeof(data), data) 

    # translate scene so it looks like line strip has moved to the left. 
    glTranslatef(-local_shift, 0.0, 0.0) 

    # draw all points from offset 
    glVertexPointer(2, GL_FLOAT, 0, n) 
    glDrawArrays(GL_LINE_STRIP, 0, points_per_vbo) 

dove my_func sarebbe fare qualcosa di simile:

my_func(start_x, end_x): 

    # generate the correct x locations. 
    x_values = range(start_x, end_x, STEP_SIZE) 

    # generate the y values. We could be getting these values from a sensor. 
    y_values = [] 
    for j in x_values: 
     y_values.append(random()) 

    data = [] 
    for i, j in zip(x_values, y_values): 
    data.extend([i, j]) 

    return data 

Questo funziona bene, tuttavia se ho diciamo 20 di quelle strisce di linea che si estendono l'intero schermo, quindi le cose rallentano notevolmente. Quindi le mie domande:

1) dovrei usare glMapBuffer per legare il buffer sulla GPU e riempire i dati direttamente (invece di utilizzare glBufferSubData)? O questo non cambierà le prestazioni?

2) dovrei usare uno shader per spostare oggetti (qui striscia di linea) invece di chiamare glTranslatef? Se sì, come sarebbe uno shader simile? (Sospetto che uno shader sia la strada sbagliata, dal momento che la mia striscia di linea NON è una funzione di periodo, ma piuttosto contiene dati casuali).

3) cosa succede se la finestra viene ridimensionata? come posso mantenere le proporzioni e scalare i vertici di conseguenza? glViewport() aiuta solo il ridimensionamento in direzione y, non in direzione x. Se la finestra viene ridimensionata in direzione x, nella mia attuale implementazione dovrei ricalcolare la posizione dell'intera striscia di linea (chiamando my_func per ottenere le nuove coordinate x) e caricarla sulla GPU. Immagino che questo potrebbe essere fatto in modo più elegante? Come potrei farlo?

4) Ho notato che quando uso glTranslatef con un valore non integrale, lo schermo inizia a sfarfallare se la striscia di linea è composta da migliaia di punti. Ciò è probabilmente dovuto al fatto che la risoluzione fine che utilizzo per calcolare la striscia di linea non corrisponde alla risoluzione in pixel dello schermo e quindi a volte alcuni punti appaiono davanti e talvolta dietro altri punti (questo è particolarmente fastidioso quando non si esegue il rendering di un onda sinusoidale ma alcuni dati "casuali"). Come posso evitare che ciò accada (oltre all'ovvia soluzione di tradurre da un multiplo intero di 1 pixel)? Se una finestra viene ridimensionata da diciamo originariamente da 800x800 pixel a 100x100 pixel e voglio ancora visualizzare una striscia di linea di 20 secondi, quindi lo spostamento in direzione x deve funzionare senza sfarfallio in qualche modo con precisione sub pixel, giusto?

5) come potete vedere, chiamo sempre glTranslatef(-local_shift, 0.0, 0.0) - senza mai fare il contrario. Pertanto continuo a spostare l'intera vista a destra. Ed è per questo che ho bisogno di tenere traccia della posizione x assoluta (al fine di inserire nuovi dati nella posizione corretta).Questo problema alla fine porterà a un artefatto, in cui la linea si sovrappone ai bordi della finestra. Immagino che ci debba essere un modo migliore per farlo, giusto? Come mantenere i valori x fissi e semplicemente spostando & aggiornando i valori y?

EDIT Ho rimosso l'esempio di onda sinusoidale e l'ho sostituito con un esempio migliore. La mia domanda è in generale su come spostare le strisce di linea nello spazio nel modo più efficiente (aggiungendo loro nuovi valori). Quindi qualsiasi suggerimento come "precomputare i valori per t -> infinito" non aiuta qui (potrei anche solo disegnare la temperatura attuale misurata davanti a casa mia).

EDIT2 Consideriamo questo esempio giocattolo dove dopo ogni passo, il primo punto viene rimosso e uno nuovo viene aggiunto alla fine:

t = 0

* 
    * * * 
* **** * 

1234567890 

t = 1

* 
* * * * 
    **** * 

2345678901 

t = 2

*  * 
    * * * 
    **** * 

3456789012 

Non penso di poter usare uno shader qui, no?

MODIFICA 3: esempio con due strisce di linea. example showing two line strips

EDIT 4: in base alla risposta di Tim che sto usando ora il seguente codice, che funziona bene, ma spezza la linea in due (dal momento che ho due chiamate di glDrawArrays), puoi anche consultare il seguente due screenshot.

complete line incomplete line

# calculate the difference 
diff_first = x[1] - x[0] 


''' first part of the line ''' 

# push the matrix 
glPushMatrix() 

move_to = -(diff_first * c) 
print 'going to %d ' % (move_to) 
glTranslatef(move_to, 0, 0) 

# format of glVertexPointer: nbr points per vertex, data type, stride, byte offset 
# calculate the offset into the Vertex 
offset_bytes = c * BYTES_PER_POINT 
stride = 0 
glVertexPointer(2, GL_FLOAT, stride, offset_bytes) 

# format of glDrawArrays: mode, Specifies the starting index in the enabled arrays, nbr of points 
nbr_points_to_render = (nbr_points - c) 
starting_point_in_above_selected_Vertex = 0 
glDrawArrays(GL_POINTS, starting_point_in_above_selected_Vertex, nbr_points_to_render) 

# pop the matrix 
glPopMatrix() 


''' second part of the line ''' 

# push the matrix 
glPushMatrix() 

move_to = (nbr_points - c) * diff_first 
print 'moving to %d ' %(move_to) 
glTranslatef(move_to, 0, 0) 


# select the vertex 
offset_bytes = 0 
stride = 0 
glVertexPointer(2, GL_FLOAT, stride, offset_bytes) 

# draw the line 
nbr_points_to_render = c 
starting_point_in_above_selected_Vertex = 0 
glDrawArrays(GL_POINTS, starting_point_in_above_selected_Vertex, nbr_points_to_render) 


# pop the matrix 
glPopMatrix() 

# update counter 
c += 1 
if c == nbr_points: 
    c = 0 

EDIT5 la soluzione risultante deve ovviamente rendering una linea sullo schermo - e non esistono due linee che mancano una connessione. La soluzione di buffer circolare di Tim fornisce una soluzione su come spostare la trama, ma alla fine ho due righe, anziché una.

+0

'glViewport' non riguarda le proporzioni. Piuttosto, le proporzioni vengono impostate utilizzando una matrice di proiezione corrispondente. Vedi [qui] (http://stackoverflow.com/questions/4338729/preserve-aspect-ratio-of-2d-object-on-window-resize) per un esempio. Non è necessario ricalcolare i dati a causa delle modifiche apportate alle finestre. –

+0

"l'onda sinusoidale qui è solo un esempio" Quindi rendi più chiaro (e no, attaccare un commento nel mezzo del tuo post * dopo che l'esempio * non conta). La tua domanda * suona * come se tu parlassi di streaming di dati sui vertici, ma è molto confuso. Rivedi la domanda. –

+0

@NicolBolas grazie per il vostro feedback! Ho provato a chiarire la prima parte della mia domanda. Non so che cosa significhi "dati dei vertici in streaming", ma forse puoi aiutarmi a capirlo. – memyself

risposta

8

Ecco i miei pensieri alla domanda rivisto:

1) dovrei usare glMapBuffer per legare il buffer sulla GPU e riempire direttamente (invece di utilizzare glBufferSubData) i dati ? O questo non renderà le prestazioni della differenza ?

Non sono a conoscenza dell'esistenza di prestazioni significative tra i due, anche se probabilmente preferirei glBufferSubData.

Quello che potrei suggerire nel tuo caso è creare un VBO con N float e quindi usarlo come un buffer circolare. Mantenere un indice localmente dove si trova la 'fine' del buffer, quindi ogni aggiornamento sostituisce il valore sotto 'end' con il nuovo valore e incrementa il puntatore. In questo modo è sufficiente aggiornare un singolo float per ogni ciclo.

Fatto questo, è possibile disegnare usando questo buffer 2x traduce e 2x glDrawArrays/Elementi:

Immaginate che hai un array di 10 elementi, e il puntatore fine del buffer è a elemento 4. Il nuovo array conterrà i seguenti valori 10, dove x è un valore costante, e f (n- d) è il campione casuale da d cicli fa:

0: (0, f(n-4)) 
1: (1, f(n-3)) 
2: (2, f(n-2)) 
3: (3, f(n-1)) 
4: (4, f(n) ) <-- end of buffer 
5: (5, f(n-9)) <-- start of buffer 
6: (6, f(n-8)) 
7: (7, f(n-7)) 
8: (8, f(n-6)) 
9: (9, f(n-5)) 

a disegnare questo (codice pseudo-supposizione, potrebbe non essere esattamente corretto):

glTranslatef(-end, 0, 0); 
glDrawArrays(LINE_STRIP, end+1, (10-end)); //draw elems 5-9 shifted left by 4 
glPopMatrix(); 
glTranslatef(end+1, 0, 0); 
glDrawArrays(LINE_STRIP, 0, end); // draw elems 0-4 shifted right by 5 

Quindi nel ciclo successivo, sostituire il valore più vecchio con il nuovo valore casuale e spostare il puntatore del buffer circolare in avanti.

2) dovrei usare uno shader per spostare oggetti (qui striscia di linea) invece di chiamare glTranslatef? Se sì, come sarebbe uno shader simile? (I sospetto che uno shader sia la strada sbagliata, poiché la mia striscia di linea è NON una funzione di periodo ma contiene piuttosto dati casuali).

Probabilmente facoltativo, se si utilizza il metodo che ho descritto in # 1. Non c'è un vantaggio particolare nell'utilizzarne uno qui.

3) cosa succede se la finestra viene ridimensionata? come mantenere l'aspect ratio e scalare i vertici di conseguenza? glViewport() aiuta solo a ridimensionare in direzione y, non in direzione x. Se la finestra viene ridimensionata in direzione x, nella mia attuale implementazione dovrei ricalcolare la posizione dell'intera striscia di linea (chiamando my_func a ottieni le nuove coordinate x) e caricala sulla GPU. Immagino che questo potrebbe essere fatto in modo più elegante? Come potrei farlo?

Non è necessario ricalcolare alcun dato. È sufficiente definire tutti i dati in un sistema di coordinate fisso che abbia senso per te, quindi utilizzare la matrice di proiezione per mappare questo intervallo alla finestra. Senza altre specifiche è difficile rispondere.

4) ho notato che quando uso glTranslatef con un valore non integrale, lo schermo inizia a lampeggiare se la striscia linea consiste di migliaia di punti. Ciò è probabilmente dovuto al fatto che la risoluzione fine che io uso per calcolare la striscia di linea non corrisponde alla risoluzione in pixel dello schermo e pertanto a volte alcuni punti appaiono davanti e a volte dietro altri punti (questo è particolarmente fastidioso quando si usa 'rendere un'onda sinusoidale ma alcuni dati' casuali '). Come posso evitare che questo problema si verifichi con (oltre all'ovvia soluzione di tradurre da un multiplo intero di di 1 pixel)? Se una finestra viene ridimensionata da diciamo originariamente da 800x800 pixel a 100x100 pixel e voglio ancora visualizzare una striscia di linea di 20 secondi, quindi lo spostamento in direzione x deve funzionare senza sfarfallio in qualche modo con precisione sub pixel, giusto?

La tua ipotesi sembra corretta. Penso che la cosa da fare qui sia di abilitare un qualche tipo di antialiasing (puoi leggere altri post su come farlo), o allargare le linee.

+0

grazie per la tua risposta! Immagino che anche la tua soluzione non soffra del mio problema originale di accumulo di glTranslate, ma stai effettivamente nello stesso posto? (vedi il mio punto n. 5). – memyself

+0

Non penso che questo avrebbe problemi di questo tipo. In sostanza, i dati visualizzati non contengono alcuna informazione sul numero del ciclo o sul tempo in cui sono stati acquisiti, ma visualizza solo gli ultimi 100 punti ricevuti su (x = 0, x = 1, x = 2, x = 3 .... x = 99). – Tim

+0

come puoi vedere nel mio codice originale, tutto quello che faccio sempre è chiamare 'glTranslatef (-local_shift, 0.0, 0.0)' - senza mai fare il contrario. Pertanto continuo a spostare l'intera vista a destra. Ed è per questo che ho bisogno di tenere traccia della posizione x assoluta (al fine di inserire nuovi dati nella posizione corretta). – memyself

4

Ci sono un certo numero di cose che potrebbero essere al lavoro qui.

  • glBindBuffer è uno dei più lenti operazioni OpenGL (insieme con chiamate simile per shader, texture, ecc)
  • glTranslate regola la matrice modelview, quale l'unità di vertice moltiplica tutti i punti. Quindi, semplicemente cambia la matrice che moltiplica per. Se invece dovessi usare un vertex shader, dovresti tradurlo singolarmente per ogni vertice. In breve: glTranslate è più veloce. In pratica, questo non dovrebbe essere troppo importante, però.
  • Se si ricalcola la funzione seno su molti punti ogni volta che si disegna, si avranno problemi di prestazioni (specialmente dal momento che, guardando la fonte, sembra che si stia utilizzando Python).
  • Stai aggiornando il tuo VBO ogni volta che lo disegni, quindi non è più veloce di un array di vertici. Gli array di vertici sono più veloci della modalità intermedia (glVertex, ecc.) Ma non altrettanto velocemente quanto gli elenchi di visualizzazione o VBO statici.
  • Ci potrebbero essere errori di codifica o chiamate ridondanti da qualche parte.

Il mio verdetto:

Stai calcolare un'onda sinusoidale e un offset sulla CPU. Ho il forte sospetto che la maggior parte del sovraccarico provenga dal calcolo e dal caricamento di dati diversi ogni volta che lo si disegna. Ciò è accompagnato da chiamate OpenGL non necessarie e, eventualmente, chiamate locali non necessarie.

La mia raccomandazione:

Questa è un'opportunità per la GPU a brillare. Il calcolo dei valori delle funzioni su dati paralleli è (letteralmente) ciò che la GPU fa meglio.

Ti suggerisco di creare una lista di visualizzazione che rappresenti la tua funzione, ma di impostare tutte le coordinate Y su 0 (quindi è una serie di punti lungo la linea y = 0). Quindi, disegna lo stesso identico elenco di visualizzazione una volta per ogni onda sinusoidale che desideri disegnare. Normalmente, questo produrrebbe solo un grafico piatto, ma, scriverai un vertex shader che trasforma i punti verticalmente nella tua onda sinusoidale. Lo shader prende un'uniforme per l'offset dell'onda sinusoidale ("sin (x-offset)"), e cambia semplicemente ogni y di ogni vertice.

Stimo che questo renderà il codice almeno dieci volte più veloce. Inoltre, poiché le coordinate x dei vertici sono tutte in punti interi (lo shader esegue la "traduzione" nello spazio della funzione calcolando "sin (x-offset)"), non si verificherà alcun jitter quando si compensano con valori in virgola mobile.

+0

grazie per la tua risposta elaborata. Sfortunatamente non penso di poter usare uno shader: l'esempio del seno è stato un po 'sfortunato, la mia striscia di linea non ha a che fare con una funzione periodica. Supponiamo invece di ottenere un nuovo punto casuale dopo ogni passo temporale. Vedi anche il mio Edit2. – memyself

2

Hai molto qui, quindi coprirò quello che posso. Spero che questo ti fornisca alcune aree di ricerca.

1) dovrei usare glMapBuffer per collegare il buffer sulla GPU e riempire i dati direttamente (invece di usare glBufferSubData)? O questo non cambierà le prestazioni?

Mi aspetto che glBufferSubData migliori prestazioni. Se i dati sono memorizzati sulla GPU, la mappatura sarà

  • Copia i dati nella memoria dell'host in modo da poterli modificare e copiarli quando li rimuovi.
  • oppure, fornire un puntatore alla memoria della GPU direttamente a cui la CPU accederà tramite PCI-Express. Questo non è poi così lento come in passato per accedere alla memoria della GPU quando eravamo su AGP o PCI, ma è ancora più lento e non così memorizzato nella cache, ecc. Come memoria dell'host.

glSubBufferData invierà l'aggiornamento del buffer alla GPU e modificherà il buffer. Non copiare il dorso e la fronte. Tutti i dati trasferiti in una raffica. Dovrebbe essere in grado di farlo anche come aggiornamento asincrono del buffer.

Una volta entrati "è più veloce di così?" digita i confronti devi iniziare a misurare quanto tempo ci vuole. Un semplice timer frame è normalmente sufficiente (ma riporta il tempo per fotogramma, non i fotogrammi al secondo - semplifica il confronto dei numeri). Se si va più dettagliati di questo, sii consapevole del fatto che a causa della natura asincrona di OpenGL, si vede spesso il tempo che viene consumato lontano dalla chiamata che ha causato il lavoro. Questo perché dopo aver dato alla GPU un carico di lavoro, è solo quando devi aspettare che finisca qualcosa che ti accorgi di quanto tempo ci vuole. Normalmente ciò accade solo quando stai aspettando che i buffer front/back si scambino.

2) dovrei usare uno shader per spostare oggetti (qui striscia di linea) invece di chiamare glTranslatef? Se sì, come sarebbe uno shader simile?

Nessuna differenza. glTranslate modifica una matrice (normalmente la vista del modello) che viene quindi applicata a tutti i vertici. Se hai uno shader applicherai una matrice di traduzione a tutti i tuoi vertici. In effetti, l'autista probabilmente sta già costruendo un piccolo shader per te.

Si noti che le API precedenti come sono ammortizzate da OpenGL 3.0 in poi e in OpenGL moderno tutto viene eseguito con shader.

3) cosa succede se la finestra viene ridimensionata? come posso mantenere le proporzioni e scalare i vertici di conseguenza? glViewport() aiuta solo il ridimensionamento in direzione y, non in direzione x.

glViewport() imposta la dimensione e la forma dell'area dello schermo su cui viene eseguito il rendering. Molto spesso viene richiamato il ridimensionamento della finestra per impostare la finestra sulla dimensione e sulla forma della finestra. Facendo questo, ogni immagine renderizzata da OpenGL cambierà proporzioni con la finestra. Per mantenere le cose allo stesso modo, devi anche controllare la matrice di proiezione per contrastare l'effetto della modifica della finestra.

Qualcosa sulla falsariga di:

glViewport(0,0, width, height); 
glMatrixMode(GL_PROJECTION_MATRIX); 
glLoadIdentity(); 
glScale2f(1.0f, width/height); // Keeps X scale the same, but scales Y to compensate for aspect ratio 

che è scritto a memoria, e potrei non avere la matematica a destra, ma si spera che si ottiene l'idea.

4) Ho notato che quando uso glTranslatef con un valore non integrale, lo schermo inizia a sfarfallare se la striscia di linea è composta da migliaia di punti.

Penso che stai vedendo una forma di aliasing dovuta alle linee che si muovono sotto la griglia di campionamento dei pixel. Esistono varie tecniche di anti-aliasing che puoi utilizzare per ridurre il problema. OpenGL ha linee anti-alias (glEnable(GL_SMOOTH_LINE)), ma molte schede di consumo non lo supportano o lo sono solo nel software. Puoi provarlo, ma potresti non avere alcun effetto o correre molto lentamente.

In alternativa è possibile esaminare l'anti-aliasing multi-campione (MSAA) o altri tipi che la scheda può supportare tramite estensioni.

Un'altra opzione è il rendering di una texture ad alta risoluzione (tramite oggetti Frame Buffer - FBO) e quindi di filtrarlo quando lo si visualizza sullo schermo come un quad strutturato. Ciò consentirebbe anche di eseguire un trucco in cui si sposta leggermente la texture renderizzata a sinistra ogni volta e si esegue il rendering della nuova striscia a destra di ciascun fotogramma.

1 1 
1 1 1 Frame 1 
    11 

    1 
1 1 1 Frame 1 is copied left, and a new line segment is added to make frame 2 
11 2 

    1 
    1 1 3 Frame 2 is copied left, and a new line segment is added to make frame 3 
11 2 

Non è un semplice cambiamento, ma potrebbe aiutarti a risolvere il problema (5).

+0

'glEnable (GL_SMOOTH_LINE)' funziona alla grande! – memyself

Problemi correlati