Computer graphics/2013-2014/Laboratory 8

Quick links: front; laboratories agenda, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, JOGL template.


MORE ON STATE VARIABLES AND BUFFERS

edit

Quering the status of state variables

edit

(J)OGL allows one to query the status of several state variables such as GL_BLEND, GL_LIGHTING, GL_VIEWPORT, GL_MAX_LIGHTS etc. The methods (functions) which facilitate these operations are: glGetBooleanv, glGetDoublev, glGetFloatv and glGetIntegerv. Each of them receives two variables: the desired state variable and a variable which will hold the result. In addition some state variables have their own methods (functions): glGetLight*(), glGetError(), or glGetPolygonStipple

Links:

Buffers

edit

(J)OGL uses four buffers to handle each window (canvas):

  • Color buffer - specified by GL_COLOR_BUFFER_BIT is responsible for storing the colors of the pixels
    • glColorMask is a nice example of how one can restrict the modification of any of the RGBA color components of the pixels

For instance the following code snippet disables coloring, draws a triangle and enables coloring again:

	public void display(GLAutoDrawable canvas) {
		GL gl = canvas.getGL();

		gl.glClear(GL.GL_COLOR_BUFFER_BIT);

		// Disable operations on the Red, Greed, Blue and Alpha pixel values
		gl.glColorMask(GL.GL_FALSE, GL.GL_FALSE, GL.GL_FALSE, GL.GL_FALSE);
		
		// Draw a colored triangle here

		// Enable coloring
		gl.glColorMask(GL.GL_TRUE, GL.GL_TRUE, GL.GL_TRUE, GL.GL_TRUE);

		gl.glTranslated(10, 0, 0);
		// Draw a colored quad here

		gl.glFlush();
	}
  • Depth buffer - specified by GL_DEPTH_BUFFER_BIT is responsible for storing the distance from each pixel to the observer's position. It is used by the z-buffer algorithm to eliminate the hidden surfaces
    • glDepthFunc can be used to specify the function used to compare each incoming pixel depth value with the depth value present in the depth buffer
    • the operators which can be used to compare the value stored in the buffer with the current pixel value are: GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_GREATER, GLGEQUAL. They can be specified by using the glDepthFunc method (function)
  • Stencil buffer - specified by GL_STENCIL_BUFFER_BIT is used for generating shadows from multiple light sources, restricting drawing to a particular area, etc. One can draw into the stencil planes using GL drawing primitives, then render geometry and images, using the stencil planes to mask out portions of the screen
    • it is activated by using glEnable(GL_STENCIL_TEST)
    • there are four main functions for handling it: glClearStencil, glStencilFunc, glStencilOp and glStencilMask.

The following example shows how the stencil buffer can be used to create a custom shaped viewport:

	public void display(GLAutoDrawable canvas) {
		GL gl = canvas.getGL();

		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT);

		// Enable stencil test
		gl.glEnable(GL.GL_STENCIL_TEST);

		// Specify what action to take when either the stencil test or depth test succeeds or fails
		gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_REPLACE);

		// Prepare to write a single bit into the stencil buffer in the area outside the viewport for every rendered pixel
		gl.glStencilFunc(GL.GL_ALWAYS, 0x1, 0x1); 

		// Draw the triangle here

		// Only render pixels if the corresponding stencil buffer bit is 1 i.e. inside the previously defined triangle
		// If one wishes to draw outside the triangle gl.glStencilFunc(GL.GL_EQUAL, 0x0, 0x1) should be used instead
		gl.glStencilFunc(GL.GL_EQUAL, 0x1, 0x1);

		// Draw some objects here

		gl.glFlush();
	}
  • Accumulation buffer - specified by GL_ACCUMULATION_BUFFER_BIT is the simplest buffer and is used for effects such as anti-aliasing, motion blur, depth of field, etc.
    • it only has one method (function) attached to it: glAccum. Its first parameter can be one of the following:
      • GL_ACCUM - adds (in the buffer) the color from the color buffer multiplied by the second parameter
      • GL_LOAD - replaces (in the buffer) the color from the color buffer multiplied by the second parameter
      • GL_ADD - adds to all the values stored in the buffer the value of the second parameter
      • GL_MULT - multiplies all the values stored in the buffer the value of the second parameter
      • GL_RETURN - copies the values from the buffer to the color buffer

The following example shows a simple use of the accumulation buffer:

	public void display(GLAutoDrawable canvas) {
		GL gl = canvas.getGL();

		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_ACCUM_BUFFER_BIT);

		for (int i=0; i<4; i++)
		{
			gl.glPushMatrix();
				gl.glRotated(i * 10., 0, 1, 0);
				// Draw the triangle here
			gl.glPopMatrix();

			// Acumulate the values in the buffer
			gl.glAccum(GL.GL_ACCUM, 1.0/i /*0.25*/);

			// Next we just want to see how the images get accumulated by using the front and back buffers

			// Switch the current draw buffer to the front buffer
			gl.glDrawBuffer(GL.GL_FRONT);
			// Transfer the accumulation buffer contents into it
			glAccum(GL.GL_RETURN, 1.0/(i+1));
			// Swap the draw buffer back
			gl.glDrawBuffer(GL.GL_BACK);
		}

		gl.glAccum(GL.GL_RETURN, 1.0);

		gl.glFlush();
	}

Links:

OBJECT PICKING

edit

Picking is used in interactive scenes where the user can select an object or click on it to obtain more information.

Rendering modes

edit

Before proceeding it is good to know that there (J)OGL has three possible rendering states. Until now we have only used GL_RENDER (implicitly):

  • GL_RENDER - used for rendering the objects in the frame buffer (on the screen)
  • GL_SELECT - used for rendering objects in the select buffer - this is what will use for picking
  • GL_FEEDBACK - used for displaying a lot of information without actually rendering anything

Changing the rendering mode is accomplished by using the glRenderMode(mode) method (function). It takes as arguments one of the above listed parameters.

Picking

edit

The glRenderMode method (function) returns a value which depends on the previously used rendering mode. The value contains the number of visible objects drawn in the buffer. So for example if you restrict drawing in GL_SELECT mode to a window of NxN pixels it will count the number of objects drawn in that particular area. Now if you only use an area of one pixel around your mouse pointer and switch to GL_SELECT mode each time you click a button the previously mentioned method (function) will return to you the number of objects which were drawn (and thus visible) in that region. You therefore can count in this way how many objects you've selected.

The previous is not enough as you need to know which object you clicked on. For this (J)OGL provides us with methods (functions) (glPushName(name) and glPopName()) for naming objects and pushing them in the name stack. Before you draw the object you need to push its name on the stack. In the same way you pop it after drawing is complete:

public class MainFrame implements MouseListener [...]
{
	[...]

	public void display(GLAutoDrawable canvas) {
		GL2 gl = canvas.getGL().getGL2();

		[...]

		// Push on the name stack the name (id) of the sphere.
		gl.glPushName(1);
		// Then draw it.
		this.drawSphere(gl, glu, radius);
		// We are done so pop the name.
		gl.glPopName();

		[...]
	}

	[...]
}

NOTE: the name stack is only used in selection mode (GL_SELECT). It will have no effect in the default GL_RENDER mode.

Important: be sure to initialize the name stack by using the glInitNames() method (function) right after entering selection mode.

One should add the following it the code in order to handle picking:

  • define a drawing function which draws the objects. Before drawing each object you define it's name by calling glLoadName(name)
  • define an event handling function which can handle mouse clicks:
    • call a selection handling method (function) each time a click happens
  • define a selection method (function) where you need to:
    • initialize the selection buffer. It will contain data about selected objects
    • save the info about the current view-port
    • switch to GL_SELECT mode
    • initialize the name stack
    • restrict the drawing area around the mouse position (a 1x1 pixels or 5x5 should suffice). Is accomplished by reseting the view-volume (from the Projection Matrix). It can be done by using the gluPickMatrix method (function).
    • draw the objects with their names
    • get the number of hits, handle them and retrieve the picked object

The following fragment of code exemplifies all of the previous by drawing a sphere and allowing the user to pick it.

public class Main
{
	[...]

	// Variables for storing the mouse coordinates when a click event occurs.
	private int mouseX, mouseY;
	// Default mode is GL_RENDER;
	private int mode = GL2.GL_RENDER;

	public void display(GLAutoDrawable canvas) {
		GL2 gl = canvas.getGL().getGL2();

		if (mode == GL2.GL_RENDER) 
		{           
			// only clear the buffers when in GL_RENDER mode. Avoids flickering
			gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

			this.drawScene(gl);

		}
		else
		{
			this.pickHandle (gl, this.mouseX, this.mouseY);
		}
	}

	public void mousePressed(MouseEvent me) {
		mouseX = me.getX();
		mouseY = me.getY();
		mode = GL2.GL_SELECT;
	}

	private void pickHandle(GL gl, int x, int y) {
		//Calculate the select buffer capacity and allocate data if necessary
		final int bufferSize  = 10; 		
		final int capacity = BufferUtil.SIZEOF_INT * bufferSize;
		IntBuffer selectBuffer = BufferUtil.newIntBuffer(capacity);

		// Send the select buffer to (J)OGL and use select mode to track object hits.
		gl.glSelectBuffer(selectBuffer.capacity(), selectBuffer);
		gl.glRenderMode(GL2.GL_SELECT);

		// Initialize the name stack.
		gl.glInitNames();

		// Get the viewport.
		int[] viewport = new int[4];
		gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0);
		// Get the projection matrix.
		float[] projection = new float[16];
		gl.glGetFloatv(GL2.GL_PROJECTION_MATRIX, projection, 0);

		// Switch to the projection matrix mode.
		gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
		// Save the current projection matrix.
		gl.glPushMatrix();
		// Reset the projection matrix.
		gl.glLoadIdentity();
   
		// Restrict region to pick object only in this region.
		// Remember to adjust the y value correctly by taking into consideration the different coordinate systems used by AWT and (J)OGL.
		glu.gluPickMatrix(x, viewport[3] - y, 1, 1, viewport, 0);

		//Load the projection matrix
		gl.glMultMatrixf(projection, 0);

		// Or redefine the perspective again.
		// glu.gluPerspective ( 38.0, (float) screenWidth / (float) screenHeight, 0.1, z_far );

		//Go back to modelview matrix for rendering.
		gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);

		// Render the scene. Note we are in GL_SELECT mode now.
		this.drawScene(gl);

		gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
		// Restore the saved projection matrix.  
		gl.glPopMatrix();
        
		// Select the modelview matrix.
		gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);

		// Return to GL_RENDER mode.
		final int hits = gl.glRenderMode(GL.GL_RENDER);        
		mode = GL2.GL_RENDER;
		
		// Process the hits.
		processHits(hits, selectBuffer);
	}

	private void drawScene(GL gl)
	{

		gl.glLoadIdentity();

		// Remember to either add these methods or use gluLookAt(...) here.
		// aimCamera(gl, glu);
		// moveCamera();


		if (mode == GL2.GL_SELECT) 
		{ 
			// Push on the name stack the name (id) of the sphere.
			gl.glPushName(1);
		}

		// Then draw it.
		this.drawSphere(gl, glu, 5);
	
		if (mode == GL2.GL_SELECT) 
		{ 
			// We are done so pop the name.
			gl.glPopName();
		}
	}

	// Retrieved from: http://user.cs.tu-berlin.de/~schabby/PickingExample.java
	// It extracts the data in the selection buffer and writes it on the console.
	private void processHits(int hits, IntBuffer buffer) {
		int offset = 0;
		int names;
		float z1, z2;

		System.out.println("---------------------------------");
		System.out.println(" HITS: " + hits);

		for (int i = 0; i < hits; i++) {
			System.out.println("- - - - - - - - - - - -");
			System.out.println(" hit: " + (i + 1));
			names = buffer.get(offset);
			offset++;
			z1 = (float) buffer.get(offset) / 0x7fffffff;
			offset++;
			z2 = (float) buffer.get(offset) / 0x7fffffff;
			offset++;
			System.out.println(" number of names: " + names);
			System.out.println(" z1: " + z1);
			System.out.println(" z2: " + z2);
			System.out.println(" names: ");

			for (int j = 0; j < names; j++) {
				System.out.print("  " + buffer.get(offset));
				if (j == (names - 1)) {
					System.out.println("<-");
				} else {
					System.out.println();
				}
				offset++;
			}
			System.out.println("- - - - - - - - - - - -");
		}
		System.out.println("--------------------------------- ");
	}

	[...]
}

Links:

Exercise

edit
  • Create a hexagon shaped viewport. Make it rotate.
  • Starting from the exercise in the previous laboratory add support for picking the three spheres present in the scene.