I'm trying to highlight ("color #00FFFF") specific individual pixels in an image (already displayed in the background) using OpenGL/C++. The pixel coordinates and the image exist in exact 2D space, but all the OpenGL code I'm seeing in the project so far - glTranslatef(), glScalef() - is 3D and float-based, and the positioning of the object appears to happen separately from the time it's drawn.
I'm used to Java's Graphics2D package, where I can call something to the effect of
width = 1; height = 1;
buffer.drawRect(width, height, xPosition, yPosition);
and it'll fill in a pixel at the specified location. Is there anything similar to that syntax - where I can set size, set position, and draw all in one line - in OpenGL? If not, how would I go about adapting my 2D+pixel input to OpenGL's float and 3D structure?
I currently have this:
glPushMatrix();
glTranslatef(0.0f, 0.0f, -5.0f);
glColor3f(0, 1, 1);
glPointSize(5.0f);
glBegin(GL_POINTS);
glVertex3f(1.0f, 1.0f, 1.0f);
glPopMatrix();
which I pieced together from some Google searches and other parts of my code, but I don't see anything being draw. I have no idea as to the units for the translate, vertex, or pointsize commands. It'd be awesome if I could replace all of that with something like the Java command up above. If not, is there some way I can guarantee whatever I draw here will be "on top" of everything else, but still not behind the camera.
Is there anything similar to that syntax - where I can set size, set position, and draw all in one line - in OpenGL?
glRect():
#include <GL/glut.h>
void display()
{
glEnable( GL_CULL_FACE );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE );
double w = glutGet( GLUT_WINDOW_WIDTH );
double h = glutGet( GLUT_WINDOW_HEIGHT );
glDepthMask( GL_TRUE );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// draw teapot
glEnable( GL_DEPTH_TEST );
glDepthMask( GL_TRUE );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 60, w / h, 1, 100 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -5 );
glColor4ub( 255, 0, 0, 255 );
glPushMatrix();
float angle = 60.0f * ( glutGet(GLUT_ELAPSED_TIME) / 1000.0f );
glRotatef( angle, 0.1, 0.95, 0.05 );
glutSolidTeapot( 1.0 );
glPopMatrix();
// draw rectangle
glDisable( GL_DEPTH_TEST );
glDepthMask( GL_FALSE );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 0, w, 0, h, -1, 1);
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glColor4ub( 0, 255, 0, 128 );
glRecti( 0 + 50, 0 + 50, w - 50, h - 50 );
glutSwapBuffers();
}
void timer( int extra )
{
glutPostRedisplay();
glutTimerFunc( 16, timer, 0 );
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE );
glutInitWindowSize( 640, 480 );
glutCreateWindow( "Rect" );
glutDisplayFunc( display );
glutTimerFunc( 0, timer, 0 );
glutMainLoop();
return 0;
}
Related
I am trying to look at a cone in 3D, for which I used gluLookAt. I draw a cone in the middle of the screen at pos (250, 250, 0) and then look at it with a camera above the origin looking at the middle. The output is a circle, contrary to the cone I expected to see. It seems the camera is instead at point (250,250,0) also, but it is specified in gluLookAt it should be above the origin. What am I overlooking here?
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(250, 250, 0);
glutSolidCone(30, 10, 20, 20);
glTranslatef(-250, -250, 0);
gluLookAt(0, 0, 100, 250, 250, 0, 0, 0, 1);
glFlush();
glutSwapBuffers();
}
int main(int argc, char **argv)
{
float x, y;
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(500, 500);
glutCreateWindow("Cone");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMotionFunc(drag);
glutMouseFunc(click);
glutSpecialFunc(catchKey);
glEnable(GL_DEPTH_TEST);
glutMainLoop(); //calls do_one_simulation_step, keyboard, display, drag, reshape
return 0;
}
A few issues:
Set your camera matrix, then draw. The way you have it now your gluLookAt() does nothing useful.
The default identity projection matrix probably isn't what you want. Set a real perspective transform using gluPerspective() or glFrustum().
Your gluLookAt() eye/center coords were a bit wonky; I've put the eye at (50, 50, 50) and set it to look at the origin.
All together:
#include <GL/glut.h>
#include <GL/GLU.h>
void display( void )
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
double w = glutGet( GLUT_WINDOW_WIDTH );
double h = glutGet( GLUT_WINDOW_HEIGHT );
gluPerspective( 60.0, w / h, 1.0, 1000.0 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
gluLookAt( 50, 50, 50, 0, 0, 0, 0, 0, 1 );
glutWireCone( 10, 30, 20, 20 );
glutSwapBuffers();
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "Cone" );
glutDisplayFunc( display );
glEnable( GL_DEPTH_TEST );
glutMainLoop();
return 0;
}
I have a 3D labyrinth with a 3d model that the user controls to exit the labyrinth. I want to draw a rectangle on top of the models head, in which the idea is to show his "energy". The rectangle is to be above his head at all times.
I have this code so far:
glMatrixMode(GL_MODELVIEW); //Switch to the drawing perspective
glLoadIdentity(); //Reset the drawing perspective
glColor3f(1, 0, 0);
glTranslatef(modelo.objecto.pos.x, modelo.objecto.pos.y+1, modelo.objecto.pos.z);
glBegin(GL_QUADS); //Begin quadrilateral coordinates
//Trapezoid
glVertex2f(0, 0);
glVertex2f(2, 0);
glVertex2f(2, .5);
glVertex2f(0, .5);
glEnd(); //End quadrilateral coordinates
This is the result: Result
It doensn't appear neither red, nor at the right position..
Project the world-space coordinate of the top of the model's head into window space using gluProject() or similar.
Swap your projection matrix over to an ortho one
Draw quad centered on window space coords acquired in #1.
All together:
#include <GL/glut.h>
float angle = 0;
void timer( int extra )
{
angle += 0.5;
glutPostRedisplay();
glutTimerFunc( 16, timer, 0 );
}
void display(void)
{
const double w = glutGet( GLUT_WINDOW_WIDTH );
const double h = glutGet( GLUT_WINDOW_HEIGHT );
const double ar = w / h;
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective( 60.0, ar, 0.1, 100.0 );
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef( 0, 0, -4 );
glRotatef( angle, 0.1, 0.5, 0.3 );
glColor3ub( 255, 255, 255 );
glutWireCube( 2.0 );
GLdouble modelview[16];
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
GLdouble projection[16];
glGetDoublev( GL_PROJECTION_MATRIX, projection );
GLint viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );
double x, y, z;
gluProject( 1, 1, 1, modelview, projection, viewport, &x, &y, &z );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho( 0, w, 0, h, -1, 1 );
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef( x, y, 0 );
glScalef( 10, 10, 1 );
glBegin( GL_QUADS );
glColor3ub( 255, 0, 0 );
glVertex2i( -1, -1 );
glVertex2i( 1, -1 );
glVertex2i( 1, 1 );
glVertex2i( -1, 1 );
glEnd();
glutSwapBuffers();
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
glutInitWindowSize( 800, 600 );
glutCreateWindow( "GLUT" );
glutDisplayFunc( display );
glutTimerFunc( 0, timer, 0 );
glutMainLoop();
return 0;
}
The easiest way to accomplish that would probably be to a have a plane and place it right over the model, then turn the rotation to the camera. The only problem with that might be that the energy bar is affected by perspective in that case and to get rid of that you would need to render in ortho. But if you are fine with the perspective changing the look of it, that should be a good enough solution.
I'm messing around with glutBitmapString from the OpenGlut library. Right now I just want to write a string on the screen to make sure it works, but I cannot get it to appear:
const char* texto = "texto";
glRasterPos2i(100, 120);
glColor3f(0.0f, 0.0f, 1.0f);
glutBitmapString(GLUT_BITMAP_HELVETICA_10, (UCHAR*)texto);
I've also tried with glutBitmapCharacter in a for loop, but with no success. Could someone give me a clear example or tell me what I'm doing wrong?
glRasterPos() positions are transformed by the current projection/modelview matrices. Make sure they're reasonable.
Or use glWindowPos2i() to bypass them.
Example:
#include <GL/freeglut.h>
void display()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
double w = glutGet( GLUT_WINDOW_WIDTH );
double h = glutGet( GLUT_WINDOW_HEIGHT );
glOrtho( 0, w, 0, h, -1, 1 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
const char* texto = "texto";
glRasterPos2i( 100, 120 );
glColor3f( 0.0f, 0.0f, 1.0f );
glutBitmapString(GLUT_BITMAP_HELVETICA_10, (const unsigned char*)texto );
glutSwapBuffers();
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
glutInitWindowSize( 300, 300 );
glutCreateWindow( "GLUT" );
glutDisplayFunc( display );
glutMainLoop();
return 0;
}
I can draw text by specifying the glRasterPos2i(WindowX, WindowY); and it draws the text at the specified position within my window just fine.
For example: glPrint(150, 150, "dgdg"); Where my viewport is 0, 0, 800, 500.
However, doing the same for a square doesn't work. So I tried setting up my view again:
const int W = 800, H = 500;
glViewport(0, 0, W, H);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, W, H, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColor3f(1.0f, 1.0f, 0.0f);
glBegin(GL_QUADS);
glVertex2f(100, 100);
glVertex2f(150, 100);
glVertex2f(150, 150);
glVertex2f(100, 150);
glEnd();
But my square never shows up :l
I disabled DepthTesting and GL_RECTANGLE_2D. Still no go.
What am I doing wrong? How can I get it to draw at window coords instead of specifying 1.0f, etc?
Workin' fine here:
#include <GL/glut.h>
void display()
{
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
double w = glutGet( GLUT_WINDOW_WIDTH );
double h = glutGet( GLUT_WINDOW_HEIGHT );
glOrtho( 0, w, h, 0, -1, 1);
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glColor3f(1.0f, 1.0f, 0.0f);
glBegin(GL_QUADS);
glVertex2f(100, 100);
glVertex2f(150, 100);
glVertex2f(150, 150);
glVertex2f(100, 150);
glEnd();
glutSwapBuffers();
}
int main( int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
glutInitWindowSize( 640, 480 );
glutCreateWindow( "GLUT" );
glutDisplayFunc( display );
glutMainLoop();
return 0;
}
You can add glFlush() after your OpenGL code to require the drawings in the buffer to flush to the window.
For some reference to glFlush(), checkout this link.
#include <iostream>
#include <X11/Xlib.h>
#include <GL/glut.h>
#include <GL/glui.h>
using namespace std;
GLsizei wh = 500;
GLsizei ww = 500;
void MyInit();
void DisplayLine(void);
void DisplaySquare(void );
void MyInit()
{
glClearColor( 1, 1, 1, 0 );
glColor3f( 1, 0, 0 );
glPointSize( 10 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity( );
glOrtho( 0.0, ww, 0.0, wh, -1.0, 1.0 );
}
void DisplayLine()
{
glClear( GL_COLOR_BUFFER_BIT );
glBegin( GL_LINES );
glVertex2i( 100, 100 );
glVertex2i( 170, 170 );
glVertex2i( 30, 370 );
glVertex2i( 160, 50 );
glEnd( );
glFlush( );
}
void DisplaySquare()
{
glClear( GL_COLOR_BUFFER_BIT );
glBegin( GL_LINE_STRIP );
glBegin( GL_POLYGON );
glVertex2f( 150, 150 );
glVertex2f( 350, 150 );
glVertex2f( 350, 350 );
glVertex2f( 150, 350 );
glVertex2f( 150, 150 );
glEnd( );
glFlush( );
}
void DisplayRSquare()
{
glClear( GL_COLOR_BUFFER_BIT );
#if 0
glMatrixMode( GL_MODELVIEW );
glLoadIdentity( );
glBegin( GL_LINE_LOOP );
glVertex2f( 50, 50 );
glVertex2f( 250, 250 );
glVertex2f( 50, 250 );
glEnd( );
#endif
glBegin( GL_LINE_LOOP );
glVertex2f( 0, 0 );
glVertex2f( 500, 0 );
glVertex2f( 500, 500 );
glVertex2f( 0, 500 );
glEnd( );
glTranslatef( 100, 0, 0 );
#if 0
glRotatef( 20, 0, 0, 1 );
glTranslatef( -50, -50, 0 );
glColor3f( 0, 1, 0 );
glBegin( GL_LINE_LOOP );
glVertex2f( 0, 0 );
glVertex2f( 500, 0 );
glVertex2f( 500, 500 );
glVertex2f( 0, 500 );
glEnd( );
glBegin( GL_LINE_LOOP );
glVertex2f( 50, 50 );
glVertex2f( 250, 50 );
glVertex2f( 250, 250 );
glVertex2f( 50, 250 );
glEnd( );
glRotatef( 12, 0, 0, 1 );
glBegin( GL_LINE_LOOP );
glVertex2f( 75, 75 );
glVertex2f( 75, 550 );
glVertex2f( 550, 550 );
glVertex2f( 550, 75 );
glEnd( );
glFlush( );
glBegin( GL_LINES );
glVertex2f( 0, 0 );
glVertex2f( 150, 150 );
glEnd()
#endif
glFlush( );
}
void DisplayCube()
{
glClear( GL_COLOR_BUFFER_BIT );
glBegin( GL_LINE_STRIP );
glBegin( GL_POLYGON );
glVertex2i( 150, 150 );
glVertex2i( 350, 150 );
glVertex2i( 350, 350 );
glVertex2i( 150, 350 );
glVertex2i( 150, 150 );
glEnd( );
glBegin( GL_LINE_STRIP );
glBegin( GL_POLYGON );
glVertex2i( 250, 250 );
glVertex2i( 450, 250 );
glVertex2i( 450, 450 );
glVertex2i( 250, 450 );
glVertex2i( 250, 250 );
glEnd( );
glBegin( GL_LINE_STRIP );
glBegin( GL_LINES );
glVertex2i( 150, 150 );
glVertex2i( 250, 250 );
glVertex2i( 350, 150 );
glVertex2i( 450, 250 );
glVertex2i( 350, 350 );
glVertex2i( 450, 450 );
glVertex2i( 150, 350 );
glVertex2i( 250, 450 );
glEnd( );
glFlush( );
}
void DisplayRotate()
{
glClear( GL_COLOR_BUFFER_BIT );
glColor3f( 0.0, 1.0, 0.0 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity( );
for( int angle = 0; angle < 18; angle += 3 )
{
glRotatef( angle, 1, 0, 0 );
if( angle > 8 )
{
glRotatef( angle * 2, 1, 0.0, 0 );
glColor3f( 1.0, 0.0, 0.0 );
}
glBegin( GL_LINE_LOOP );
glVertex2f( 150, 150 );
glVertex2f( 350, 150 );
glVertex2f( 350, 350 );
glVertex2f( 150, 350 );
glEnd( );
}
glFlush( );
}
void pressMouse(int button, int state, int x, int y )
{
if( state == GLUT_DOWN )
{
glBegin( GL_POINTS );
glPointSize( 50 );
glVertex2i( x, wh - y );
glEnd( );
glFlush( );
}
}
void holdMouse(int x, int y )
{
glBegin( GL_POINTS );
glPointSize( 50 );
glVertex2i( x, wh - y );
glEnd( );
glFlush( );
}
void mouse(int btn, int state, int x, int y )
{
if( state == GLUT_DOWN )
{
mouseState = state;
mouseButton = btn;
mouseX = x;
mouseY = y;
}
else
{
mouseState = 0;
}
}
void motion(int x, int y )
{
if( mouseState == GLUT_DOWN )
{
if( MouseButton == GLUT_LEFT_BUTTON )
{
yrotation -= ( mouseX - x );
xrotation -= ( mouseY - y );
}
}
mouseX = x;
mouseY = y;
glutPostRedisplay( );
}
void DisplayFSquare()
{
glClear( GL_COLOR_BUFFER_BIT );
glColor3f( 0, 0, 1 );
glBegin( GL_LINE_LOOP );
glVertex2f( ( GLdouble ) 0.25, ( GLdouble ) 0.25 );
glVertex2f( ( GLdouble ) 0.75, ( GLdouble ) 0.25 );
glVertex2f( ( GLdouble ) 0.75, ( GLdouble ) .75 );
glVertex2f( ( GLdouble ) 0.25, ( GLdouble ) 0.75 );
glEnd( );
glFlush( );
}
int main(int argc, char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_SINGLE | GLUT_RGB );
glutInitWindowSize( ww, wh );
glutInitWindowPosition( 150, 150 );
glutCreateWindow( "Line" );
MyInit( );
glutDisplayFunc( DisplayRSquare );
glutMouseFunc( pressMouse );
glutMotionFunc( holdMouse );
glutMainLoop( );
}
I have just created several trivial models to test the basic transformation. I have two questions:
DisplayRSquare : in this function I draw a square with the window size and want to move it to (100, 0), but it translated to (200, 0). And if I move it to (50, 0) it will move to (100, 0).
What more weird is, the program sometimes works and sometimes not.
For function glVertex2f(0.3,0.7), we should find a point in the position 0.3*widthofthewindow, 0.7* height of the window, right? But I cannot find it. I have to compute the coordinates and run glVertex2f(0.3*width,0.7*height).
My compiling command is g++ -o line Line.cpp -lGL -lGLU -lglut -lglui
and just run ./line(File name is Line.cpp)
Please speak with me: "OpenGL is not a scene graph! – It doesn't maintain a scene, and all it does is executing drawing commands, drawing single points, lines and triangles, one at a time."
Your code lacks certain basic steps. First you need to setup a proper projection and modelview matrix. You put a projective matrix (ortho) in the modelview matrix at the beginning of your program. And then you just multiply, and multiply and multiply and multiply on the modelview matrix, without ever resetting it to a known (identity) state.
The coarse outline of every fixed function OpenGL display process is about the following:
set clear color and depth, then clear
set viewport and projection matrix
matrix mode projection
load identity
multiply projection (ortho or perspective)
set initial view matrix
matrix mode modelview
load identity
setup view (lookat or whatever)
draw geometry
for each object
matrix mode modelview
push matrix
apply local transformation
draw object
pop matrix
Update 1
First a rewriting of your program, that works in a predictible way and does what you originally indended.
Find answers to your questions after it
#include <GL/glut.h>
void DrawRSquare(
float sz,
float tx,
float ty )
{
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
/*
Order of operations matters.
Drawing commands use the current state
of OpenGL, which includes the transformation
matrix state. So this must be set before
drawing.
*/
glTranslatef(tx, ty, 0);
glColor3f(1, 0, 0);
glBegin( GL_LINE_LOOP );
glVertex2f( 0, 0 );
glVertex2f( sz, 0 );
glVertex2f( sz, sz );
glVertex2f( 0, sz );
glEnd();
glPopMatrix();
}
void Display(void)
{
glClearColor( 1, 1, 1, 0 );
glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT );
const int windim[2] = {
glutGet(GLUT_WINDOW_WIDTH),
glutGet(GLUT_WINDOW_HEIGHT)
};
glViewport(0, 0, windim[0], windim[1]);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, windim[0], 0.0, windim[1], -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
DrawRSquare(50, 100, 0);
DrawRSquare(50, 200, 100);
glutSwapBuffers();
}
int main(
int argc,
char **argv )
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutCreateWindow( "" );
glutDisplayFunc( Display );
glutMainLoop( );
}
Actually the program is running DisplayRSquare, in this function I draw a square same size with the window we created and want to more it with the vector (100,0) in the 2-D space but actually it translated (200,0). And if you change the vector into (50,0), it will move (100,0).
The reason for this is, that your Display function is probably called thrice and then no more. In the first call of DisplayRSquare a translation of (100,0,0) is applied after drawing some lines when the window is created but not yet exposed. Then the window gets exposed and the next redraw applies another (100,0,0) to the already set transformation. But still the window gets exposed another time and now the square will be drawn once again, now with the (100,0,0) + (100,0,0) applied. If you resize your window, the square would futher translate away until out of view with each redraw.
That's why you must reset your OpenGL state into a well known state before beginning any drawing.
What is more wired, the program sometimes works well. Sometimes not. My code is glTranslatef(100,0,0). For the same program ,sometimes it translate 100, sometime 200......Kind of wired...
What happens is, that the amount of redraw events your programm receives varies. This is by moving other windows over it, iconizing and revertig it, etc.
For function glVertex2f(0.3,0.7), we should find a point in the position 0.3*widthofthewindow, 0.7* height of the window, right?
This entirely depends on the set projection and modelview matrices. Without knowing how those are set, and your program never sets them into a well defined state, we don't know.
But I cannot find it.
Quite likely, because your transformation matrices will be way off, of what you indended.
I have to compute the coordinates and run glVertex2f(0.3*width,0.7*height)?
No. Well, at least not in the way you think.
A suggestion: You're using the fixed function pipeline and immediate mode. Both are severely outdated and deprecated. In fact they have been stripped out of recent versions of OpenGL, so I'd not even bother learning them.
If you want to learn modern OpenGL, I stronly suggest you read and work through the tutorial of Nicol Bolas, which is IMHO one of the best you can find at the moment:
http://arcsynthesis.org/gltut