2012年7月17日 星期二

Picking Tutorial

from:http://www.lighthouse3d.com/opengl/picking/index.php?color1


Color Coding


The Red Book describes an interesting approach to picking based on color coding. This is a very simple approach that can be used instead of using the OpenGL API described in the previous sections.
The color coding scheme does not require any perspective changes and therefore it is simpler in theory. Just define a rendering function where the relevant objects (pickable and occluders) are assigned each a different color. When the user clicks the mouse over the scene, render the scene on the back buffer, read back the selected pixel from the back buffer and check its color. The process is completely transparent to the user because the buffers are not swapped, so the color coding rendering is never seen.
For instance, consider the following scene with 4 snowmen. What you're seeing is the result of the normal rendering function.

The next figure shows the result produced in the back buffer by the color coding rendering function. As you can see each snowman has a different color.

The required steps when the mouse is clicked are:
  • 1. Call the color coding rendering function
  • 2. Read the pixel where the mouse was clicked from the back buffer
  • 3. Process the color information to find out which item was clicked
  • 4. Do NOT swap buffers, otherwise the user will see the color coding
  • Bellow the normal rendering function is presented. It contains the code to render both the ground as well as the snowmen (contained in thedisplay list). 


        
    void draw() {
    
    // Draw ground
    	glColor3f(0.9f, 0.9f, 0.9f);
    	glBegin(GL_QUADS);
    		glVertex3f(-100.0f, 0.0f, -100.0f);
    		glVertex3f(-100.0f, 0.0f,  100.0f);
    		glVertex3f( 100.0f, 0.0f,  100.0f);
    		glVertex3f( 100.0f, 0.0f, -100.0f);
    	glEnd();
    
    // Draw 4 Snowmen
    
    	glColor3f(1.0f, 1.0f, 1.0f);
    
    	for(int i = 0; i < 2; i++)
    		for(int j = 0; j < 2; j++) {
    			glPushMatrix();
    			glTranslatef(i*3.0,0,-j * 3.0);
    			glColor3f(1.0f, 1.0f, 1.0f);
    			glCallList(snowman_display_list);
    			glPopMatrix();
    		}
    
    }
    
    


    The following function is for color coding rendering. As you can see the ground was left out since it is neither a pickable object, nor an occluding one. Also the code for the snowmen was altered 


        
    void drawPickingMode() {
    
    // Draw 4 SnowMen
    
    
    	glDisable(GL_DITHER);
    	for(int i = 0; i < 2; i++)
               for(int j = 0; j < 2; j++) {
    	 	glPushMatrix();
    		
    // A different color for each snowman
    
    		switch (i*2+j) {	
    			case 0: glColor3ub(255,0,0);break;
    			case 1: glColor3ub(0,255,0);break;
    			case 2: glColor3ub(0,0,255);break;
    			case 3: glColor3ub(250,0,250);break;
    		}
    
    		glTranslatef(i*3.0,0,-j * 3.0);
    		glCallList(snowman_display_list);
    		glPopMatrix();
    	   }
    	glEnable(GL_DITHER);
    }
    

    Cheking the Color

    Checking the color of the pixel where the mouse was clicked involves reading that same pixel from the back buffer. This can be accomplished using the following function: 
    void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);  Parameters:
    x,y : the bottom left corner
    width, length: the size of the area to be read
    format: The type of data to be read. In here it is assumed to GL_RGB.
    type: the data type of the elements of the pixel. In here we'll use GL_UNSIGNED_BYTE.
    pixels: An array where the pixels will be stored. This is the result of the function

    Both format and type have a lot of other possibilities, check the Red or Blue books for more information.
    Just as when using the OpenGL API it is necessary to invert the y coordinate. Reading the viewport information is therefore also required when using this approach. After getting the viewport info, it is possible to call glReadPixels as shown in the code bellow. The x and y parameters represent the cursor position. Width and height are both set to one since only a single pixel is being read. Afterwards it is just a matter of checking the values of the pixel. 
        
    void processPick ()
    {
    	GLint viewport[4];
    	GLubyte pixel[3];
    
    	glGetIntegerv(GL_VIEWPORT,viewport);
    
    	glReadPixels(cursorX,viewport[3]-cursorY,1,1,
    		GL_RGB,GL_UNSIGNED_BYTE,(void *)pixel);
    
    	printf("%d %d %d\n",pixel[0],pixel[1],pixel[2]);
    	if (pixel[0] == 255)
    	  printf ("You picked the 1st snowman on the 1st row");
    	else if (pixel[1] == 255)
    	  printf ("You picked the 1st snowman on the 2nd row");
    	else if (pixel[2] == 255)
    	  printf ("You picked the 2nd snowman on the 1st row");
    	else if (pixel[0] == 250)
    	  printf ("You picked the 2nd snowman on the 2nd row");
       	else
    	   printf("You didn't click a snowman!");
      printf ("\n");
       
    }
    
    

    To get this approach running without problems there are a few things that you should be aware. First set your colors using unsigned bytes as opposed to floats. When in RGB mode there are only 256 possible values for each component, so when you set a color using floats OpenGL will pick the nearest color possible. Secondly make sure your monitor is set to True Color. Otherwise the color presented on screen is only an approximation to the required color and therefore when reading back the pixels there is a possibility that the values wont match with the set values. Finally make sure you disable dithering, lighting and texturing for the color coded rendering function, since any of these operations may change the original color.
    If you really want to work with floats to set the color, or you can't guarantee that the monitor will be set to True Color then you can always to a test to check what the specified colors will look like when rendered.
    This approach requires that before your application starts you must render blocks of the requested colors and read them back using glReadPixelsto get the real rendered colors. Store those colors in an array and use them instead of the colors you specified when rendering.
    Be careful when choosing the colors though. If the values for each component are not set sufficiently apart then the may turn out to be the same color when rendered. For instance when setting the red component to 250, using unsigned bytes, with the monitor set to High Color instead of True Color, you actually get a red component of 255. This is because there are less possible values for each component when using only 16 bits to specify a color instead of 32 or 24. Check out the pixel packing values in the Red Book to find out about the possible combinations of bits for each component with 16 bits.

    沒有留言:

    張貼留言