Computer graphics/2013-2014/Laboratory 6

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


IMPORTANT

edit

Va rog sa completati formularul cu titlul proiectul ales.

La aceste doua linkuri gasiti exemple pentru toate laboratoarele:link1, link2.


3D GRAPHICS BASICS

edit

Note For this lesson you will need to apply textures on your objects. The API from the following link could be useful in this direction. The main class is represented by TextureHandler which you were asked to implement during Laboratory 4. Place the Java classes in your src folder and use them as follows:

[...]

	private TextureHandler texture1, texture2;
	
	public void init(GLAutoDrawable canvas)
	{
		GL gl = canvas.getGL();
		
		[...]

		texture1 = new TextureHandler(gl, glu, "texture1.jpg", true);
		texture2 = new TextureHandler(gl, glu, "texture2.jpg", true);		
		
		[...]
	}

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

		texture1.bind();
		texture1.enable();
		// Draw the object you wish to apply the first texture to.

		texture2.bind();
		texture2.enable();
		// Draw the object you wish to apply the second texture to.

		[...]
	}

[...]
}

Perspective Projection, Depth and Shading

edit

Perspective Projection

edit

3D Graphics offers the possibility to simulate depth by using the Z axis. In order to obtain a realistic scene one should use the perspective projection instead of the orthographic one. The template for the perspective projection can be found at the following link Computer graphics -- 2013-2014 -- info.uvt.ro/JOGL-Template. The perspective projection can set up by using one of the following two methods (functions):

  • glFrustum(left, right, bottom, top, near, far) -- from GL instances;
  • gluPerspective(fovy, aspect, near, far) -- from GLU instances;

In what follows we will use the gluPerspective method (function).

IMPORTANT: The following code is part of the JOGL template for perspective projection. You do not need to paste it in the application.

[...]
	public void reshape(GLAutoDrawable canvas, int left, int top, int width, int height)
	{
		GL gl = canvas.getGL();
		
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity();
		
		double ratio = (double) width / (double) height;
		glu.gluPerspective (38, ratio, 0.1, 100);
		
		gl.glViewport(0, 0, width, height);

		gl.glMatrixMode(GL.GL_MODELVIEW);
	}
[...]

Depth and Shading

edit

Depth (objects should obey the projection laws and cover (hide) one another -- based on the position relative to the viewer) can be enabled by enabling the GL_DEPTH_TEST state variable and selecting a depth function with the aid of the glDepthFunc() method (function). In addition the shading model can also be set to further refine the 3D view:

[...]
	public void init(GLAutoDrawable canvas)
	{
		GL gl = canvas.getGL();
		
		[...]
		
		// Choose the shading model.
		gl.glShadeModel(GL.GL_SMOOTH);
		
		// Activate the depth test and set the depth function.
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glDepthFunc(GL.GL_LESS);
		
		[...]
	}
[...]

Additionally when the depth test is enabled we must also clear the GL_DEPTH_BUFFER each time the scene is redrawn:

[...]
	public void display(GLAutoDrawable canvas)
	{
		GL gl = canvas.getGL();
		
		gl.glClear(GL.GL_COLOR_BUFFER_BIT);
		// Clear the depth buffer.
		gl.glClear(GL.GL_DEPTH_BUFFER_BIT);
		
		[...]
	}
[...]

Links:

Looking at the scene from an arbitrary point of view

edit

There are cases when one needs to look at the scene from a particular point and towards another one. For this case there is the gluLookAt method (function) which allows the user to orientate the viewport. The arguments for the gluLookAt method are:

  • eyeX, eyeY, eyeZ -- specifies the position of the eye point (camera position)
  • centerX, centerY, centerZ -- specifies the position of the reference point (center of the scene)
  • upX, upY, upZ -- specifies the direction of the up vector (camera orientation)

NOTE: the gluLookAt method should be placed right after the gl.glLoadIdentity() call in the display method.

Drawing simple 3D objects

edit

OGL does not offer methods (functions) for rendering (3D) complex objects such as spheres, cubes, pyramids, human faces, buildings, landscapes, etc. In order to simulate them one needs to implement its own library or use already existing ones such as GLU:

[...]
	private void initializeJogl()
	{
		[...]
		
		this.glu = new GLU();
	}
	
	[...]
	
	private GLU glu;
}

The following example shows how one can draw a sphere by using the GLU library:

[...]
	public void display(GLAutoDrawable canvas)
	{
		
		[...]
		
		GLUquadric sun = glu.gluNewQuadric ();
		glu.gluSphere (sun, 0.3, 32, 32);
		glu.gluDeleteQuadric (sun);
		
		[...]
	}
[...]

Applying textures on 3D objects

edit

Applying textures on 3D objects is basically the same as for 2D ones. One notable difference arises in the case of spheres.

Mapping textures on spheres

edit

More details on how to programmatic build a sphere and how texture coordinates are binded can be found here: [Parametric Equation of a Sphere and Texture Mapping]

[...]

	this.texture.bind();
	this.texture.enable();

	GLUquadric sphere = glu.gluNewQuadric();
        // Enabling texturing on the quadric.
	glu.gluQuadricTexture(sphere, true);
	glu.gluSphere(sphere, 0.5, 64, 64);
	glu.gluDeleteQuadric(sphere);
[...]

While this technique is general and can be used to load all sorts of textures, there are cases in which it is not sufficient. One example is related to sphere mapping. Let's say we have a sphere representing a planet on which we want to apply a texture representing the planet's surface.

However when applying a texture on a sphere in this way, you will notice that the sphere is lighted in the same way anywhere. In order to produce the correct light effects one needs to enable sphere mapping by using GL_SPHERE_MAP:

[...]
	public void init(GLAutoDrawable canvas)
	{
                [...]
                // Set The Texture Generation Mode For S To Sphere Mapping (NEW)
                gl.glTexGeni(GL.GL_S, GL.GL_TEXTURE_GEN_MODE, GL.GL_SPHERE_MAP);                
                // Set The Texture Generation Mode For T To Sphere Mapping (NEW) 
                gl.glTexGeni(GL.GL_T, GL.GL_TEXTURE_GEN_MODE, GL.GL_SPHERE_MAP);
        }

	public void display(GLAutoDrawable canvas)
	{
                [...]
                // Enable Texture Coord Generation For S (NEW)
	        gl.glEnable(GL.GL_TEXTURE_GEN_S); 		                
	        // Enable Texture Coord Generation For T (NEW)        
	        gl.glEnable(GL.GL_TEXTURE_GEN_T);
                //draw the object
        }

Note: When applying sphere mapping, however, the texture will be drawn so that its center will always face the camera, prohibiting the rotation of the texture on the sphere.

Lighting and materials

edit

3D Graphics also relies on lighting and materials to simulate surface features.

Lighting

edit

Light is what makes our scene (the objects) visible and has an important role in creating the illusion of 3D through the way in which vertices are lit. For example in the case of a sphere we might have a lighter area, corresponding to the point where the intensity of the light is greatest, and a diminishing effect towards the outside of that area. Of course this is also influenced by the materials used when drawing the objects (more on materials will come bellow). Without lights our objects will look like plain 2D objects!

The light that reaches your eye from the polygon surface arrives by four different mechanisms -- thus different light types:

  • ambiental light -- the light that comes from all directions equally and is scattered in all directions equally by the polygons in our scene
  • diffuse light -- the light that comes from a particular (distant) point source (a star or candle) and hits surfaces with an intensity that depends on whether they face towards the light or away from it; this light is responsible for defining the 3D shapes of the objects
  • specular light -- the light that comes from a point source, but it is reflected in the manner of a mirror where most of the light bounces off in a particular direction defined by the surface's shape; it is best used to describe whether a surface is shinny or dull
  • emission light -- the light actually emitted by the surface equally in all directions

Also in order to use lights first we have to be enable the feature with glEnable(GL_LIGHTING). However, this is not enough as we must still define and enable each particular light used inside a scene (at least 8 are supported on all video cards). This can be done using glLightfv which allows us to specify:

  • the light involved
  • the light type -- there are 10 different possible values, and you can find them at: [1]
  • the values for the light RGB channels

Important: When lighting is enabled, OpenGL suddenly needs to know the orientation of the surface at each polygon vertex. One needs to call glNormal3f for each vertex to define the normals of the vertices.

NOTE: By default the position of the light as specified when using GL_POSITION is {x, y, z, w} = {0, 0, 1, 0}.

NOTE: Light position is specified in homogeneous coodinates. This also effects the way a light is being treated.

  • If w=0 than the light is seen treated as directional (as in the case of the default behavior). When directional light is being used one can also define the spot direction and spot cut-off (angle of the light cone).
[...]

	gl.glLightf( GL.GL_LIGHT1, GL.GL_SPOT_CUTOFF, 60.0F );
	gl.glLightfv( GL.GL_LIGHT1, GL.GL_SPOT_DIRECTION, spotDirection );

[...]
  • In case w=1 than the light is treated as a point light with light being emitted in all directions from the specified position.
[...]
	
	public void init(GLAutoDrawable canvas)
	{
		GL gl = canvas.getGL();
		
		[...]
		
		gl.glEnable(GL.GL_LIGHTING);
		gl.glEnable(GL.GL_LIGHT0);
		gl.glEnable(GL.GL_LIGHT1);
		
		[...]
	}
	
[...]
	
	public void display(GLAutoDrawable canvas)
	{
		GL gl = canvas.getGL();
		
		[...]
		
		// The vector arguments represent the R, G, B, A values.
		gl.glLightfv(GL.GL_LIGHT1, GL.GL_AMBIENT, new float [] {0.2f, 0.0f, 0.0f, 1f}, 0);
		
		gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, new float [] {0.9f, 0.9f, 0.9f, 1f}, 0);
		// The vector arguments represent the x, y, z, w values of the position.
		gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, new float [] {-10, 0, 0, 1f}, 0);
		
		// Place your code starting from here.
		
		[...]
	}
	
[...]

IMPORTANT: Specular light might not work when using a texture on an object. This article discusses this issue and how it can be solved.

Links:

Materials

edit

As said before materials and lights are essential to each other, and there are some important things we need to know before proceeding:

  • the glColor commands are not useful (or usable) when lighting is enabled if certain measures are not taken;
  • if however we want to use this command it should be done after we have enabled GL_COLOR_MATERIAL by using glEnable;
  • the properties of COLOR_MATERIAL can be set by using glColorMaterial(GLenum face, GLenum mode) which causes a material color to track the current color;
  • now we can safely use the glColor commands;
[...]
	public void init(GLAutoDrawable canvas)
	{
		GL gl = canvas.getGL();
		
		[...]
		
		gl.glEnable(GL.GL_COLOR_MATERIAL);
		gl.glColorMaterial(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT_AND_DIFFUSE);
		
		[...]
	}
[...]

On the other hand we can use the other alternative by calling glMaterial* commands before actually drawing the object, and thus being able to setup color and light handling. The next example illustrates this by drawing a yellow glowing sphere

[...]
	public void display(GLAutoDrawable canvas)
	{
		GL gl = canvas.getGL();
		
		[...]
			
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, new float [] {0.8f, 0.8f, 0.0f, 1f}, 0);
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, new float [] {0.8f, 0.8f, 0.0f, 1f}, 0);
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, new float [] {0.8f, 0.8f, 0.0f, 1f}, 0);
		gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, new float [] {0.5f, 0.5f, 0f, 1f}, 0);
		
		GLUquadric sun = glu.gluNewQuadric ();
		glu.gluSphere (sun, 0.3, 32, 32);
		glu.gluDeleteQuadric (sun);
		
		[...]	
	}
[...]

Important: specifying the material properties has a different meaning than in the case of light properties. In the case of materials the RGB components specify the percent of color being reflected while in the case of lights it specifies the percent of color being emitted.

One could also use two new methods called glPushAttrib which takes one parameter: a mask that indicates which groups of state variables to save on the attribute stack, and glPopAttrib. More information here. For example we could store the GL_CURRENT_BIT which contains data about:

  • current RGBA color;
  • current color index;
  • current normal vector;
  • current texture coordinates;
  • current raster position;
  • etc.;
[...]
	public void display(GLAutoDrawable drawable) 
	{
		[...]
		
		float sun_glow[] = {1.7f, 1.5f, 1.0f, 1.0f};
		gl.glPushAttrib(GL.GL_CURRENT_BIT);
			gl.glMaterialfv(GL.GL_FRONT, GL.GL_EMISSION, sun_glow, 0);    
			GLUquadric q = glu.gluNewQuadric();
			glu.gluQuadricDrawStyle(q, GLU.GLU_FILL);
			glu.gluQuadricNormals(q, GLU.GLU_SMOOTH);
			glu.gluSphere(q, 34, 40, 40);
			glu.gluDeleteQuadric(q);
		gl.glPopAttrib();
		
		[...]
	}
[...]

Links:

Exercises

edit
  • Create a a sphere (use GLU) and enable shading, depth and lighting. Apply a texture on it. The scene should contain an ambient light, a diffuse and a specular one.
  • Create a sphere from points by defining your own sphere method.
    • Bonus: create a sphere with texture coordinates and normals. (use GL_TRIANGLES if GL_POINTS doesn't work). Apply a texture on it and compare it with the gluSphere method when texturing and lighting is enabled.
  • Create a cube with materials and lighting enabled. Try changing the properties of emission, specular, diffuse and ambient properties for both of material and lighting elements. Use a different material for each side of the cube.
    • Add a different texture on each of the cube's sides.
  • Create a pyramid and make it transparent. Add normals to each pyramid face. Lighting should be enabled.

NOTE: The objects could be too big for the viewport or might be situated outside of it. Use the glLookAt method (function) to fix the problem.

NOTE: Normals should be added for each face of the cube, otherwise lighting won't work properly. You need to compute them manually and set them via the glNormal3* methods.