Sprzężenie zwrotne (ang. feedback) jest jednym z trzech trybów renderingu dostępnych w bibliotece OpenGL. W trybie tym, podobnie jak w trybie selekcji, piksele nie są kopiowane do bufora ramki obrazu. W zamian za to w buforze sprzężenia zwrotnego tworzone są dane o współrzędnych wierzchołków prymitywów, składowych kolorów oraz współrzędnych tekstur.
Swoją budową bufor sprzężenia zwrotnego najbardziej przypomina bufor selekcji, z tą jednak różnicą, że zamiast tablicy liczb całkowitych mamy do czynienia z tablicą liczb zmiennoprzecinkowych typu GLfloat.
Sprzężenie zwrotne jest często wykorzystywane łącznie z opisaną wcześniej techniką selekcji obiektów do tworzenia grafiki interaktywnej.
Zmiana trybu renderowania
Aby korzystać ze sprzężenia zwrotnego w bibliotece OpenGL trzeba zmienić tryb renderingu, wywołując opisywaną już funkcję glRenderMode z parametrem
GL_FEEDBACK. W trybie sprzężenia zwrotnego funkcja zwraca ilość wartości zapisanych w buforze sprzężenia zwrotnego. Jeżeli ilość przekazywanych informacji do bufora sprzężenia zwrotnego jest większa niż wielkość tego bufora, funkcja glRenderMode zwróci wartość -1.
Bufor sprzężenia zwrotnego
Utworzenie bufora sprzężenia zwrotnego wymaga wywołania funkcji:
void glFeedbackBuffer( GLsizei size, GLenum type, GLfloat * buffer )
której parametr size określa wielkość tablicy wskazywanej w parametrze buffer, a rodzaj danych przekazywanych do bufora zwrotnego określa parametr type. Tabela 1 zawiera zestawienie rodzaju informacji zwracanych w buforze sprzężenia zwrotnego w zależności od wybranego parametru type w przypadku, gdy OpenGL pracuje w trybie RGB(A). Jeżeli biblioteka pracuje w trybie indeksowym w buforze sprzężenia zwrotnego umieszczana jest zamiast składowych (R, G, B, A) koloru jedna liczba - numer indeksu koloru. Współrzędne x, y i z położenia wierzchołków podawane są w odniesieniu do współrzędnych okna renderingu. W przypadku użycia techniki wieloteksturowania bufor sprzężenia zwrotnego zwraca wyłącznie informacje o pierwszej teksturze (
GL_TEXTURE0).
Tabela 1: Rodzaje informacji zwracanych w buforze sprzężenia zwrotnego
Przetwarzanie bufora sprzężenia zwrotnego
Dane umieszczone w buforze sprzężenia zwrotnego oddzielone są specjalnymi znacznikami. Znaczniki identyfikowane są przez następujące stałe:
Po znaczniku w buforze umieszczone są dane o wierzchołkach, których ilość określa parametr type funkcji glFeedbackBuffer. W przypadku znaczników:
GL_POINT_TOKEN,
GL_BITMAP_TOKEN,
GL_DRAW_PIXEL_TOKEN i
GL_COPY_PIXEL_TOKEN będą to dane pojedynczego wierzchołka (lub współrzędnych położenia rastra). Analogicznie po znacznikach
GL_LINE_TOKEN i
GL_LINE_RESET_TOKEN w buforze znajdują się informacje o dwóch wierzchołkach. Jedynie po znaczniku
GL_POLYGON_TOKEN znajduje się liczba określająca ilość wierzchołków wielokąta, a po niej dane kolejnych wierzchołków.
Wystąpienie w buforze sprzężenia zwrotnego znacznika zdefiniowanego przez użytkownika wymaga wywołania funkcji:
void glPassThrough( GLfloat token )
Wartość parametru token jest umieszczana w buforze zaraz po samym znaczniku
GL_PASS_THROUGH_TOKEN.
Program przykładowy
Program przykładowy (plik sprzezenie_zwrotne.cpp) prezentuje tworzenie bufora sprzężenia zwrotnego i pobierania z niego informacji. Trudno nazwać go specjalnie odkrywczym, ale Czytelnik powinien zwrócić szczególną uwagę na to jakie dane są przekazywane do bufora sprzężenia zwrotnego i porównać je z tym co jest rysowane na ekranie. Przykładowe efekty działania programu przedstawiają rysunki 1, 2 i 3.
Warto jeszcze zwrócić uwagę, że program działając w trybie sprzężenia zwrotnego wyświetla wyłącznie testowe prymitywy graficzne. Sprawdzenie bieżącego trybu renderowania umożliwia zmienna stanu
GL_RENDER_MODE.
Plik sprzezenie_zwrotne.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <string>
#include "colors.h"
enum
{
POINTS,
LINES,
POLYGON,
BITMAPS,
PIXMAPS,
PASS_THROUGH,
EXIT
};
std::vector < std::string > strings;
int test = POINTS;
const GLint MAX_FEEDBACK_BUFFER = 128;
GLfloat feedback_buf[ MAX_FEEDBACK_BUFFER ];
void DrawStrings()
{
glColor3fv( Black );
for( int str = 0; str < strings.size(); str++ )
{
glRasterPos2i( 4, 4 + str * 16 );
int len = strlen( strings[ str ].c_str() );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, strings[ str ][ i ] );
}
}
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
switch( test )
{
case PASS_THROUGH:
glPassThrough( 10.0 );
case POINTS:
glPointSize( 40 );
glBegin( GL_POINTS );
glColor3fv( Red );
glVertex2i( 100, 400 );
glColor3fv( Green );
glVertex2i( 250, 400 );
glColor3fv( Blue );
glVertex2i( 400, 400 );
glEnd();
break;
case LINES:
glLineWidth( 40 );
glBegin( GL_LINES );
glColor3fv( Red );
glVertex2i( 100, 350 );
glColor3fv( Green );
glVertex2i( 400, 350 );
glColor3fv( Blue );
glVertex2i( 100, 420 );
glColor3fv( Red );
glVertex2i( 400, 420 );
glEnd();
break;
case POLYGON:
glLineWidth( 40 );
glBegin( GL_TRIANGLES );
glColor3fv( Red );
glVertex2i( 100, 400 );
glColor3fv( Green );
glVertex2i( 400, 400 );
glColor3fv( Blue );
glVertex2i( 250, 250 );
glEnd();
break;
case BITMAPS:
glColor3fv( Blue );
glRasterPos2i( 230, 400 );
glutBitmapCharacter( GLUT_BITMAP_HELVETICA_18, 'A' );
glutBitmapCharacter( GLUT_BITMAP_HELVETICA_18, 'B' );
glutBitmapCharacter( GLUT_BITMAP_HELVETICA_18, 'C' );
break;
case PIXMAPS:
unsigned char pixmap[ 4 * 4 * 3 ] =
{
0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00
};
glRasterPos2i( 220, 400 );
glPixelZoom( 6.0, 6.0 );
glDrawPixels( 4, 4, GL_RGB, GL_UNSIGNED_BYTE, pixmap );
glPixelZoom( 1.0, 1.0 );
glRasterPos2i( 260, 400 );
glCopyPixels( 220, 400, 24, 24, GL_COLOR );
break;
}
GLint render_mode;
glGetIntegerv( GL_RENDER_MODE, & render_mode );
if( render_mode == GL_RENDER )
DrawStrings();
glFlush();
glutSwapBuffers();
}
void VertexInfo( GLint buf_pos )
{
char str[ 128 ];
sprintf( str, "(x,y,z)=(%f,%f,%f)", feedback_buf[ buf_pos + 0 ],
feedback_buf[ buf_pos + 1 ], feedback_buf[ buf_pos + 2 ] );
strings.insert( strings.end(), str );
sprintf( str, "(R,G,B,A)=(%f,%f,%f,%f)", feedback_buf[ buf_pos + 3 ],
feedback_buf[ buf_pos + 4 ], feedback_buf[ buf_pos + 5 ],
feedback_buf[ buf_pos + 6 ] );
strings.insert( strings.end(), str );
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0, width, 0, height );
glFeedbackBuffer( MAX_FEEDBACK_BUFFER, GL_3D_COLOR, feedback_buf );
glRenderMode( GL_FEEDBACK );
Display();
GLint buf_size = glRenderMode( GL_RENDER );
strings.erase( strings.begin(), strings.end() );
char str[ 128 ];
GLint buf_pos = 0;
while( buf_pos < buf_size )
switch(( int ) feedback_buf[ buf_pos ] )
{
case GL_POINT_TOKEN:
strings.insert( strings.end(), "GL_POINT_TOKEN" );
VertexInfo( buf_pos + 1 );
buf_pos += 8;
break;
case GL_LINE_TOKEN:
strings.insert( strings.end(), "GL_LINE_TOKEN" );
VertexInfo( buf_pos + 1 );
buf_pos += 8;
VertexInfo( buf_pos );
buf_pos += 7;
break;
case GL_LINE_RESET_TOKEN:
strings.insert( strings.end(), "GL_LINE_RESET_TOKEN" );
VertexInfo( buf_pos + 1 );
buf_pos += 8;
VertexInfo( buf_pos );
buf_pos += 7;
break;
case GL_POLYGON_TOKEN:
strings.insert( strings.end(), "GL_POLYGON_TOKEN" );
for( int i = 0; i < feedback_buf[ buf_pos + 1 ]; i++ )
VertexInfo( buf_pos + 1 + i * 7 );
buf_pos += 7 * feedback_buf[ buf_pos + 1 ] + 2;
break;
case GL_BITMAP_TOKEN:
strings.insert( strings.end(), "GL_BITMAP_TOKEN" );
VertexInfo( buf_pos + 1 );
buf_pos += 8;
break;
case GL_DRAW_PIXEL_TOKEN:
strings.insert( strings.end(), "GL_DRAW_PIXEL_TOKEN" );
VertexInfo( buf_pos + 1 );
buf_pos += 8;
break;
case GL_COPY_PIXEL_TOKEN:
strings.insert( strings.end(), "GL_COPY_PIXEL_TOKEN" );
VertexInfo( buf_pos + 1 );
buf_pos += 8;
break;
case GL_PASS_THROUGH_TOKEN:
strings.insert( strings.end(), "GL_PASS_THROUGH_TOKEN" );
sprintf( str, "TOKEN=%f", feedback_buf[ buf_pos + 1 ] );
strings.insert( strings.end(), str );
buf_pos += 2;
break;
}
Display();
}
void Menu( int value )
{
switch( value )
{
case POINTS:
case LINES:
case POLYGON:
case BITMAPS:
case PIXMAPS:
case PASS_THROUGH:
test = value;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_WIDTH ) );
break;
case EXIT:
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Sprzężenie zwrotne" );
#else
glutCreateWindow( "Sprzezenie zwrotne" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
int MenuFeedback = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Punkty", POINTS );
glutAddMenuEntry( "Linie", LINES );
glutAddMenuEntry( "Wielokąt", POLYGON );
glutAddMenuEntry( "Mapy bitowe", BITMAPS );
glutAddMenuEntry( "Mapy pikselowe", PIXMAPS );
glutAddMenuEntry( "Znacznik użytkownika", PASS_THROUGH );
#else
glutAddMenuEntry( "Punkty", POINTS );
glutAddMenuEntry( "Linie", LINES );
glutAddMenuEntry( "Wielokat", POLYGON );
glutAddMenuEntry( "Mapy bitowe", BITMAPS );
glutAddMenuEntry( "Mapy pikselowe", PIXMAPS );
glutAddMenuEntry( "Znacznik uzytkownika", PASS_THROUGH );
#endif
glutCreateMenu( Menu );
#ifdef WIN32
glutAddSubMenu( "Sprzężenie zwrotne", MenuFeedback );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddSubMenu( "Sprzezenie zwrotne", MenuFeedback );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}