import java.nio.*; // Native IO buffers: LWJGL uses these to efficiently exchange data with system memory import java.io.*; import org.lwjgl.opengl.*; import org.lwjgl.util.glu.*; import org.lwjgl.input.Keyboard; import glmodel.*; /** * Render a model and cast a shadow on the floor. * Uses the stencil buffer to restrict the shadow to just one surface. * * Requires the glmodel package to load model formats into a GL_Object class. * Requires GLMaterial.java * Requires Vector3D.java * */ public class GLART_11_Shadow { private boolean done = false; private final String windowTitle = "Shadow Demo"; private DisplayMode displayMode; private float rotateModelY = 0; private GL_Mesh obj; int texture; // shadow matrix (see initOpenGL() and setShadowMatrix()) FloatBuffer fShadowMatrix; // equation for the plane the shadow will fall on (the normal for the plane, 0,1,0) float[] floorPlane = new float[] {0f,1f,0f,0f}; /** * Main function just creates and runs the application. */ public static void main(String args[]) { GLART_11_Shadow app = new GLART_11_Shadow(); app.run(); } /** * Initialize the app, then sit in a render loop until done==true. */ public void run() { try { init(); while (!done) { mainloop(); render(); Display.update(); } cleanup(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } } /** * Initialize the environment * @throws Exception */ private void init() throws Exception { GL_OBJ_Importer importOBJ; GLImage textureImg; InputStream in; initDisplay(); initGL(); // load model importOBJ = new GL_OBJ_Importer(); in = getInputStream("models/cow.obj"); obj = importOBJ.importFromStream(in); obj.regenerateNormals(); // prepare a texture textureImg = new GLImage("images/Flag_of_the_United_States.png"); texture = makeTexture(textureImg); makeTextureMipMap(textureImg); } /** * Create an OpenGL display, in this case a fullscreen window. * @throws Exception */ private void initDisplay() throws Exception { // set to full screen, no chrome Display.setFullscreen(false); // get all possible display resolutions DisplayMode d[] = Display.getAvailableDisplayModes(); // find a resolution we like for (int i = 0; i < d.length; i++) { if (d[i].getWidth() == 800 && d[i].getHeight() == 600 && d[i].getBitsPerPixel() == 32) { displayMode = d[i]; break; } } // set the display to the resolution we picked Display.setDisplayMode(displayMode); Display.setTitle(windowTitle); // create the window // specify Pixel Format: // 0 -- default bits per pixel for alpha values // 24 -- 24 bits per pixel for depth buffer // 8 -- set bits per pixel for stencil buffer // NOTE: Need to set bits for Stencil buffer or shadows won't work Display.create(new PixelFormat(0,24,8)); } /** * Initialize OpenGL * */ private void initGL() { // color of overall scene lighting float ambient[] = { .1f, .1f, .1f, 1f }; // color of light source float lightDiffuse[] = { 1f, 1f, .9f, 1f }; // direct light float lightSpecular[] = { 1f, 1f, .9f, 1f }; // highlight float lightAmbient[] = { .3f, .3f, .4f, 1f }; // scattered light // light position: if last value is 0, then this describes light direction. float lightPosition[] = { -5f, 5f, 5, 0f }; // setup material GLMaterial material = new GLMaterial(); material = new GLMaterial(); material.setDiffuse(new float[] {1f,1f,1f,1f}); material.setAmbient(new float[] {.8f,.8f,.8f,1f}); material.apply(); // Select the Projection Matrix (controls perspective) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // Define perspective GLU.gluPerspective( 30.0f, // Field Of View (float)displayMode.getWidth() / (float)displayMode.getHeight(), // aspect ratio 0.01f, // near Z clipping plane 1000.0f); // far Z clipping plane // Select The Modelview Matrix (controls model orientation) GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); // Reset The Modelview Matrix // make sure OpenGL correctly layers objects GL11.glEnable(GL11.GL_DEPTH_TEST); // turn on texturing GL11.glEnable(GL11.GL_TEXTURE_2D); // don't draw backward facing triangles ("back faces") GL11.glEnable(GL11.GL_CULL_FACE); // turn lighting on GL11.glEnable(GL11.GL_LIGHTING); // overall scene lighting setAmbientLight(ambient); // Create a light setLight( GL11.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition ); ////////////////////////////////////////////////////////////// // For Shadows: define a matrix based on light position and shadow surface float[] fShadowMatrixArray = new float[16]; makeShadowMatrix(fShadowMatrixArray, lightPosition, floorPlane); // convert the float array to a FloatBuffer fShadowMatrix = allocFloats(fShadowMatrixArray); /////////////////////////////////////////////////////////////// // set the background color GL11.glClearColor(.5f, .5f, .8f, 1); } /** * Handle keyboard input. Just check for escape key or user * clicking to close the window. */ private void mainloop() { // Check for Escape key if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) { done = true; } // Has Window been closed if(Display.isCloseRequested()) { done = true; } } /** * Cleanup all the resources. * */ private void cleanup() { Display.destroy(); } //---------------------------------------------------------------- // Rendering //---------------------------------------------------------------- /** * Render the scene. */ private void render() { rotateModelY += .2; // Clear screen and depth buffer GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // reset the coordinate system to center of screen GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); // Place the 'camera' GLU.gluLookAt( 0, 5, 10, // camera position 0, 2, 0, // look at a point directly in front of camera 0f, 1f, 0f); // which way is up // draw shadow cast on floor plane drawShadow(); // draw the object normally drawObject(); } /** * draw the object that will be shadowed */ public void drawObject() { GL11.glPushMatrix(); { GL11.glTranslatef(0,1.8f,0); // move up a litte (off the floor) GL11.glScalef(.5f,.5f,.5f); // scale model down a little GL11.glRotatef(rotateModelY, 0, 1, 0); // rotate around vertical axis renderMesh(obj, texture); } GL11.glPopMatrix(); } /** * draw the shadowed object from the lights point of view. this will * create the right shape for a shadow. To flatten out the object * we turn off lighting and texture, and just draw with a flat gray. */ public void drawShadow() { // draw the shadow onto floor GL11.glPushMatrix(); { // no texture GL11.glDisable(GL11.GL_TEXTURE_2D); // no light GL11.glDisable(GL11.GL_LIGHTING); // no depth GL11.glDisable(GL11.GL_DEPTH_TEST); // dark gray color GL11.glColor4f(.3f, .3f, .35f, 1); // multiply the current modelview matrix with the shadow matrix GL11.glMultMatrix(fShadowMatrix); // draw the object to be shadowed (as if viewed from light position) drawObject(); // re-enable settings GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glEnable(GL11.GL_LIGHTING); } GL11.glPopMatrix(); } /** * Make a matrix that will transform the geometry as if it is * projected onto the given plane, from the viewpoint of the given light position. */ void makeShadowMatrix(float[] fDestMat, float[] fLightPos, float[] fPlane) { float dot; // dot product of plane and light position dot = fPlane[0] * fLightPos[0] + fPlane[1] * fLightPos[1] + fPlane[2] * fLightPos[2] + fPlane[3] * fLightPos[3]; // first column fDestMat[0] = dot - fLightPos[0] * fPlane[0]; fDestMat[4] = 0.0f - fLightPos[0] * fPlane[1]; fDestMat[8] = 0.0f - fLightPos[0] * fPlane[2]; fDestMat[12] = 0.0f - fLightPos[0] * fPlane[3]; // second column fDestMat[1] = 0.0f - fLightPos[1] * fPlane[0]; fDestMat[5] = dot - fLightPos[1] * fPlane[1]; fDestMat[9] = 0.0f - fLightPos[1] * fPlane[2]; fDestMat[13] = 0.0f - fLightPos[1] * fPlane[3]; // third column fDestMat[2] = 0.0f - fLightPos[2] * fPlane[0]; fDestMat[6] = 0.0f - fLightPos[2] * fPlane[1]; fDestMat[10] = dot - fLightPos[2] * fPlane[2]; fDestMat[14] = 0.0f - fLightPos[2] * fPlane[3]; // fourth column fDestMat[3] = 0.0f - fLightPos[3] * fPlane[0]; fDestMat[7] = 0.0f - fLightPos[3] * fPlane[1]; fDestMat[11] = 0.0f - fLightPos[3] * fPlane[2]; fDestMat[15] = dot - fLightPos[3] * fPlane[3]; } //---------------------------------------------------------------- // render mesh //---------------------------------------------------------------- /** * Render mesh with normals and texture coordinates. Loops through * all triangles in the mesh object. * * Several triangles may refer to the same vertex, but each face * can have different normals for that vertex. This allows for * sharp edges between faces. * * @param o mesh object to render */ public void renderMesh(GL_Mesh o, int textureHandle) { GL_Triangle t; GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); GL11.glBegin(GL11.GL_TRIANGLES); for (int j = 0; j < o.triangles.length; j++) { // draw all triangles in object t = o.triangles[j]; GL11.glTexCoord2f(t.uvw1.x, t.uvw1.y); GL11.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z); GL11.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z); GL11.glTexCoord2f(t.uvw2.x, t.uvw2.y); GL11.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z); GL11.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z); GL11.glTexCoord2f(t.uvw3.x, t.uvw3.y); GL11.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z); GL11.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z); } GL11.glEnd(); } //---------------------------------------------------------------- // Lighting //---------------------------------------------------------------- /** * Simple way to setup a light. Uses same color for direct light (diffuse), * reflected highlight (specular) and scattered light (ambient). Ambient * color is darkened to 1/4 of the light color. * * @param GLLightHandle * @param color * @param position */ public static void setLight(int GLLightHandle, float[] color, float[] position ) { float[] ambientLight = {color[0]/4f, color[1]/4f, color[2]/4f, color[3]/4f}; FloatBuffer lightColor = allocFloats(color); FloatBuffer ambientColor = allocFloats(ambientLight); FloatBuffer ltPosition = allocFloats(position); GL11.glLight(GLLightHandle, GL11.GL_DIFFUSE, lightColor); // color of the direct illumination GL11.glLight(GLLightHandle, GL11.GL_SPECULAR, lightColor); // color of the highlight (same as direct light) GL11.glLight(GLLightHandle, GL11.GL_AMBIENT, ambientColor); // color of the scattered light (darker) GL11.glLight(GLLightHandle, GL11.GL_POSITION, ltPosition); GL11.glEnable(GLLightHandle); // Enable the light (GL_LIGHT1 - 7) } /** * Set the color of a 'positional' light (a light that has a specific * position within the scene).
*
* Params:
* an OpenGL light number (GL11.GL_LIGHT1),
* 'Diffuse': color of direct light from this source,
* 'Ambient': color of scattered light from this source
* 'Specular': color of this light reflected off a surface,
* position.
*/ public static void setLight( int GLLightHandle, float[] diffuseLightColor, float[] ambientLightColor, float[] specularLightColor, float[] position ) { FloatBuffer ltDiffuse = allocFloats(diffuseLightColor); FloatBuffer ltAmbient = allocFloats(ambientLightColor); FloatBuffer ltSpecular = allocFloats(specularLightColor); FloatBuffer ltPosition = allocFloats(position); GL11.glLight(GLLightHandle, GL11.GL_DIFFUSE, ltDiffuse); // color of the direct illumination GL11.glLight(GLLightHandle, GL11.GL_AMBIENT, ltAmbient); // color of the reflected light GL11.glLight(GLLightHandle, GL11.GL_SPECULAR, ltSpecular); // color of the highlight (same as direct light) GL11.glLight(GLLightHandle, GL11.GL_POSITION, ltPosition); GL11.glEnable(GLLightHandle); // Enable the light (GL_LIGHT1 - 7) //GL11.glLightf(GLLightHandle, GL11.GL_QUADRATIC_ATTENUATION, .005F); // how light beam drops off } /** * Set the position of a light to the given xyz. NOTE: Positional light only, * not directional. */ public static void setLightPos(int GLLightHandle, float x, float y, float z) { float[] position = new float[] {x,y,z,1}; GL11.glLight(GLLightHandle, GL11.GL_POSITION, allocFloats(position)); } /** * Set the color of the Global Ambient Light. Affects all objects in * scene regardless of their placement. */ public static void setAmbientLight(float[] ambientLightColor) { FloatBuffer ltAmbient = allocFloats(ambientLightColor); GL11.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, ltAmbient); } /** * Open a file InputStream and trap errors. * @param filenameOrURL * @return */ public static InputStream getInputStream(String filename) { InputStream in = null; try { in = new FileInputStream(filename); } catch (IOException ioe) { System.out.println("getInputStream (" + filename + "): " + ioe); if (in != null) { try { in.close(); in = null; } catch (Exception e) {} } } catch (Exception e) { System.out.println("getInputStream (" + filename + "): " + e); } return in; } /** * Create a texture from the given image. */ public static int makeTexture(GLImage textureImg) { if ( textureImg == null ) { return 0; } else { return makeTexture(textureImg.pixelBuffer, textureImg.w, textureImg.h); } } /** * Create a texture from the given pixels in RGBA format. Set the texture * to repeat in both directions and use LINEAR for magnification. * @return the texture handle */ public static int makeTexture(ByteBuffer pixels, int w, int h) { // get a new empty texture int textureHandle = allocateTexture(); // 'select' the new texture by it's handle GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle); // set texture parameters: // how to wrap texture GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); // how to scale up texture GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); //GL11.GL_NEAREST); // Create the texture from pixels GL11.glTexImage2D(GL11.GL_TEXTURE_2D, // type of texture we're creating 0, // level-of-detail: use 0 GL11.GL_RGBA, // texture pixel format w, h, // width and height of texture image (powers of 2) 0, // widtt of the border (either 0 or 1, use 0) GL11.GL_RGBA, // image pixel format GL11.GL_UNSIGNED_BYTE, // image pixel data type pixels // image pixel data ); return textureHandle; } /** * Build "Mipmap" for currently selected texture. Builds different * versions of the texture image at lower levels of detail, so texture * can scale down gracefully at longer distances. * * @param textureImg the texture image * @return error code of buildMipMap call */ public static int makeTextureMipMap(GLImage textureImg) { int ret = GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, 4, textureImg.w, textureImg.h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, textureImg.getPixelsRGBA()); if (ret != 0) { System.out.println("GLApp.makeTextureMipMap(): Error occured while building mip map, ret=" + ret); } //Assign the mip map levels and texture info GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_NEAREST); GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); return ret; } /** * Allocate a texture (glGenTextures) and return the handle to it. */ public static int allocateTexture() { IntBuffer textureHandle = allocInts(1); GL11.glGenTextures(textureHandle); return textureHandle.get(0); } //---------------------------------------------------------------- // Native IO buffer functions //---------------------------------------------------------------- public static final int SIZE_FLOAT = 4; public static final int SIZE_INT = 4; public static FloatBuffer allocFloats(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer(); } public static FloatBuffer allocFloats(float[] floatarray) { FloatBuffer fb = ByteBuffer.allocateDirect(floatarray.length * SIZE_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer(); fb.put(floatarray).flip(); return fb; } public static IntBuffer allocInts(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_INT).order(ByteOrder.nativeOrder()).asIntBuffer(); } }