package classwork; import javax.media.opengl.*; import javax.media.opengl.glu.*; import java.awt.event.*; import jocode.*; import jomodel.*; /** * GLART_5_transparent_material_solution.java * * Draw a row of transparent textured spheres. * * This demo fixes the front-to-back drawing problem that transparent * shapes have. We need to draw the objects behind transparent objects * first, then draw the transparent objects in front, so that we can * see the background objects through the transparent objects. * See GLART_sphere_lit_alpha.java for an example of the Z order * issue when rendering with alpha. * * In this case, we'll find the current Z depth of each transparent * sphere by "projecting" the sphere position through the modelview, * projection and viewport matrices. In effect we're reproducing * what OpenGL does when it transforms vertices before drawing * them on screen. This will give us the Z depth of each sphere * as it appears on screen. * * Once we know the Z depth we can draw the transparent spheres in * the correct back-to-front order. The Z depth furthest away from * the eye will be 1 and the Z depth closest to the eye will be 0. * We will to draw objects in order from the high Z depth to the low * Z depth. */ public class GLART_5_transparent_material_solution extends JOApp { float rotation = 0; // Material objects will hold surface color values JOMaterial clearMtl = new JOMaterial(); JOMaterial opaqueMtl = new JOMaterial(); // texture handle (a number that refers to an allocated texture) int marbleTextureHandle = 0; // An array holds world positions of the spheres // The ObjectPosition class is defined at the end of this file ObjectPosition spherePositions[]; /** * Main function just creates and runs the application. */ public static void main(String args[]) { GLART_5_transparent_material_solution app = new GLART_5_transparent_material_solution(); displayWidth = 800; displayHeight = 600; app.run(); } /** * Initialize OpenGL */ public void setup() { // Select the Projection Matrix (controls perspective) gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); // Reset The Projection Matrix // Define perspective glu.gluPerspective( 45.0f, // Field Of View (float)getWidth() / (float)getHeight(), // aspect ratio 0.1f, // near Z clipping plane 100.0f); // far Z clipping plane // Select The Modelview Matrix (controls model orientation) gl.glMatrixMode(GL.GL_MODELVIEW); // make sure OpenGL correctly layers objects gl.glEnable(GL.GL_DEPTH_TEST); // OpenGL won't draw backward facing triangles ("back faces") gl.glEnable(GL.GL_CULL_FACE); // turn lighting on (does not create a light) gl.glEnable(GL.GL_LIGHTING); // Create a light // diffuse is the color of direct light from this light source // specular is the hightlight color // ambient is the color of scattered light from this source // position is where the light is, or it's direction float lightDiffuse[] = { .9f, .9f, .8f, 1f }; // direct light float lightSpecular[] = { .9f, .9f, .8f, 1f }; // highlight float lightAmbient[] = { .1f, .1f, .0f, 1f }; // scattered light float lightPosition[] = { -4f, 4f, 6, 1f }; // Last value is 1: this is light position. setLight( GL.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition ); // overall scene lighting setAmbientLight( new float[] { .7f, .7f, .7f, 1f } ); // make the transparent material clearMtl.setDiffuse( new float[] { .9f, .95f, .9f, .5f }); // white/green .5f in the fourth position is ALPHA value clearMtl.setAmbient( new float[] { .7f, .75f, .7f, 1f }); // light gray greenish clearMtl.setSpecular( new float[] { .9f, .9f, .9f, 1f }); // almost white: very reflective clearMtl.setShininess(127f); // 0=no shine, 127=max shine // make the opaque material opaqueMtl.setDiffuse(new float[] { 1f, .3f, .3f, 1f }); // reddish opaqueMtl.setAmbient(new float[] { .8f, .2f, .2f, 1f }); // reddish // set the background color gl.glClearColor(.1f, .1f, .23f, 1); // blending gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); // Force normals to length 1 gl.glEnable(GL.GL_NORMALIZE); // Draw specular highlghts on top of textures (GL12.GL_SINGLE_COLOR to reset) gl.glLightModeli(GL.GL_LIGHT_MODEL_COLOR_CONTROL, GL.GL_SEPARATE_SPECULAR_COLOR ); // Create a texture from the image marbleTextureHandle = makeTexture("images/marble.jpg"); // positions of 3 transparent spheres spherePositions = new ObjectPosition[3]; spherePositions[0] = new ObjectPosition( 0f, 0f, -2f); spherePositions[1] = new ObjectPosition( 0f, 0f, 0f); spherePositions[2] = new ObjectPosition( 0f, 0f, 2f); } /** * Render the scene. */ public void draw() { // reset the coordinate system to center of screen gl.glLoadIdentity(); // Where is the 'eye' glu.gluLookAt( -3f, 0f, 6f, // eye position 0f, 0f, 0f, // target to look at 0f, 1f, 0f); // which way is up // rotate scene gl.glRotatef(rotation, 0,1,0); // Clear screen and depth buffer gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); //----------------------------------------------------------------------------- // 1) Sort the transparent objects by their Z depth // // I do this with a simple but somewhat kludgey approach. I draw the spheres // exactly as they appear in the scene and check their screen Z positions (in // other words the Z depth). Then I sort the spheres by that Z depth, and // clear the screen. //----------------------------------------------------------------------------- // get the Z depths of the three spheres for (int i=0; i < spherePositions.length; i++) { spherePositions[i].Zdepth = drawSphereAt(spherePositions[i].x, spherePositions[i].y, spherePositions[i].z); } // sort the sphere positions by Zdepth (back to front) sortSpheresByZ(spherePositions); // Clear screen and depth buffer again gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); //----------------------------------------------------------------------------- // 2) Draw opaque stuff first // Depth test is ON, so these objects will be written into depth buffer //----------------------------------------------------------------------------- // activate opaque material opaqueMtl.apply(); // draw sphere at one end of line drawSphereAt(0f,0f,-4f); // draw sphere at other end of line drawSphereAt(0f,0f,4f); //----------------------------------------------------------------------------- // 3) Draw transparent stuff second // Depth test is ON, so depth test will take place for the opaque objects that // we'we drawn so far. We make the depth buffer read-only: the transparent // objects will not be written to the depth buffer, so they will not overwrite // what is behind them. //----------------------------------------------------------------------------- // activate transparent material clearMtl.apply(); // make depth buffer read-only before drawing transparent objects gl.glDepthMask(false); // draw the transparent spheres from highest Z (farthest from the eye) to lowest Z (closest to the eye) for (int i=0; i < spherePositions.length; i++) { drawSphereAt(spherePositions[i].x, spherePositions[i].y, spherePositions[i].z); } // make depth buffer writable again gl.glDepthMask(true); } /** * Translate to the given world position and draw a sphere. * Return the Z depth of that position. */ public float drawSphereAt(float x, float y, float z) { JOVector projectedPoint; gl.glPushMatrix(); { // shift to sphere position gl.glTranslatef(x,y,z); // draw double-sided sphere draw2SidedSphere(); // "project" the sphere position onto the screen so we can get it's Z depth. // The projectPoint() function returns the screen position of the sphere // after it has been transformed by the gluLookAt(), glRotate(), glTranslate() // and gluPerspective() commands. We project 0,0,0 because the sphere is // drawn at the origin (it is translated and rotated afterwards) projectedPoint = projectPoint(0,0,0); } gl.glPopMatrix(); // Return the Z depth of the sphere return projectedPoint.z; } /** * Draw a sphere with another slightly smaller sphere inside. The inner sphere faces * inside, creating the effect that the sphere is a glass shell and can reflect * light both on the outer surface and the inner surface. */ public void draw2SidedSphere() { gl.glPushMatrix(); { // draw inside-facing surface a little smaller gl.glScalef(.99f,.99f,.99f); renderSphereInside(); } gl.glPopMatrix(); gl.glPushMatrix(); { // draw outside-facing sphere a little bigger gl.glScalef(1.01f, 1.01f, 1.01f); renderSphere(); } gl.glPopMatrix(); } /** * call glu functions to create geometry for a 1 unit sphere, with normals. * NOTE: this is not optimized at all. */ public static void renderSphereInside() { GLUquadric s = glu.gluNewQuadric(); glu.gluQuadricOrientation(s, GLU.GLU_INSIDE); // normals point inwards glu.gluQuadricTexture(s, true); glu.gluSphere(s, 1, 48, 48); // run GL commands to draw sphere } public static void renderSphere() { GLUquadric s = glu.gluNewQuadric(); glu.gluQuadricOrientation(s, GLU.GLU_OUTSIDE); // normals point outwards glu.gluQuadricTexture(s, true); glu.gluSphere(s, 1, 48, 48); // run GL commands to draw sphere } /** * Return the screen position of a world position. * * "Project" a point from world space into screen space. This replicates the "transformation * pipeline", the process OpenGL goes through to place geometry onto the screen. We call * glu.gluProject(), a utility function that transforms a world xyz coordinate into * screen space, based on the current modelview matrix, projection matrix, and viewport values. * * Remember: in screen space, Z is a number from 0-1 (not the same as world space Z) */ public JOVector projectPoint(float x, float y, float z) { float[] resultf = new float[4]; project(x, y, z, resultf); return new JOVector(resultf[0],resultf[1],resultf[2]); } /** * Uses a basic bubble sort to sort the sphere positions by Z depth. When * we're done the positions array will be ordered from highest Z depth * to lowest. Higher Z depths are further away from the eye (background). */ public void sortSpheresByZ(ObjectPosition[] worldPositions) { int n = worldPositions.length; for (int pass=1; pass < n; pass++) { for (int i=0; i < n-pass; i++) { if (worldPositions[i].Zdepth < worldPositions[i+1].Zdepth) { ObjectPosition temp = worldPositions[i+1]; worldPositions[i+1] = worldPositions[i]; worldPositions[i] = temp; } } } } /** * rotate scene if right or left arrow keys are pressed */ public void keyDown(int keycode) { if(keycode == KeyEvent.VK_RIGHT) { rotation -= 2f; } else if(keycode == KeyEvent.VK_LEFT) { rotation += 2f; } } // --------------------------------------------------------------- // A class to keep track of one object's Z depth, and its position // in the world. This information is used to draw the objects in // order by Z depth (necessary when rendering transparent objects). // --------------------------------------------------------------- class ObjectPosition { public float Zdepth; public float x; public float y; public float z; public ObjectPosition(float x_, float y_, float z_) { x = x_; y = y_; z = z_; } } }