Package episode_36

Source Code of episode_36.ShadowMappingFBO

/*
* Copyright (c) 2013, Oskar Veerhoek
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the FreeBSD Project.
*/

package episode_36;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
import utility.BufferTools;
import utility.EulerCamera;
import utility.OBJLoader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.FloatBuffer;

import static org.lwjgl.opengl.ARBFramebufferObject.*;
import static org.lwjgl.opengl.ARBShadowAmbient.GL_TEXTURE_COMPARE_FAIL_VALUE_ARB;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.GL_CLAMP_TO_EDGE;
import static org.lwjgl.opengl.GL14.*;
import static org.lwjgl.util.glu.GLU.*;

/**
* Shows how to get shadows working in OpenGL. Ported from the OpenGLSuperBible. Some code was modified by Oskar
* Veerhoek.
*
* @author Sam K.
* @author Daniel W.
*/
public class ShadowMappingFBO {

    /** The position of the omnidirectional shadow-casting light. */
    private static final FloatBuffer lightPosition = BufferTools.asFlippedFloatBuffer(200, 250, 200, 1);
    private static final FloatBuffer textureBuffer = BufferUtils.createFloatBuffer(16);
    private static final Matrix4f depthModelViewProjection = new Matrix4f();
    private static final DisplayMode DISPLAY_MODE = new DisplayMode(800, 600);
    private static final EulerCamera camera = new EulerCamera.Builder().setAspectRatio((float) DISPLAY_MODE.getWidth
            () / DISPLAY_MODE.getHeight()).setPosition(23, 34, 87).setRotation(22, 341,
            0).setNearClippingPane(2).setFarClippingPane(300).setFieldOfView(60).build();
    /**
     * The width of the depth texture that is known as the shadow map. The higher the width, the more detailed the
     * shadows.
     */
    private static int shadowMapWidth;
    /**
     * The height of the depth texture that is known as the shadow map. The higher the height, the more detailed the
     * shadows.
     */
    private static int shadowMapHeight;
    /**
     * The frame buffer holding the shadow map. This frame buffer is an off-screen rendering location to which we will
     * draw the shadow map.
     */
    private static int frameBuffer;
    /** The render buffer holding the shadow map in the form of a depth texture. */
    private static int renderBuffer;
    /** The display list that holds the Stanford bunny model. */
    private static int bunnyDisplayList;

    public static void main(String[] args) {
        setUpDisplay();
        setUpStates();
        setUpFrameBufferObject();
        setUpCamera();
        setUpModel();
        drawShadowMap();
        while (!Display.isCloseRequested()) {
            render();
            logic();
            input();
            Display.update();
            Display.sync(60);
        }
        cleanUp();
        System.exit(0);
    }

    /** Sets up a display. */
    private static void setUpDisplay() {
        try {
            Display.setDisplayMode(DISPLAY_MODE);
            Display.setVSyncEnabled(true);
            Display.setTitle("Shadow Mapping Demo");
            Display.create();
        } catch (LWJGLException e) {
            System.err.println("Couldn't set up the display");
            Display.destroy();
            System.exit(1);
        }
        if (!GLContext.getCapabilities().OpenGL14 && !GLContext.getCapabilities().GL_ARB_shadow) {
            System.out.println("Can't create shadows at all. Requires OpenGL 1.4 or the GL_ARB_shadow extension");
            Display.destroy();
            System.exit(1);
        }
        if (!GLContext.getCapabilities().GL_ARB_shadow_ambient) {
            System.err.println("GL_ARB_shadow_ambient extension not available.");
            Display.destroy();
            System.exit(1);
        }
    }

    private static void setUpStates() {
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glEnable(GL_NORMALIZE);
        glEnable(GL_COLOR_MATERIAL);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
        glPolygonOffset(2.5F, 0.0F);
        glClearColor(0, 0.75f, 1, 1);
        glLight(GL_LIGHT0, GL_POSITION, lightPosition);
        glLightModel(GL_LIGHT_MODEL_AMBIENT, BufferTools.asFlippedFloatBuffer(0, 0, 0, 1));
        glLight(GL_LIGHT0, GL_AMBIENT, BufferTools.asFlippedFloatBuffer(0, 0, 0, 1));
        glLight(GL_LIGHT0, GL_DIFFUSE, BufferTools.asFlippedFloatBuffer(1.7F, 1.7F, 1.7F, 1));
        // Clamp texture coordinates (e.g.: (2,0) becomes (1,0)) because we only want one shadow.
        // Use 'TO_EDGE' to prevent the texture borders to affect the shadow map through linear texture filtering.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // [x,y,z,w] -> [s,t,r,q]
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        // Enable bilinear texture filtering. This means that the colour will be
        // a 'weighted value of the four texture elements that are closest to the center of the pixel being textured'
        // (from OpenGL 2.1 References Pages).
        // The alternative to linear texture filtering is nearest-neighbour texture filtering. Here the colour of
        // the closest texel to the given texture coordinate (texture pixel) is taken. This, while being fast, may
        // produce a pixelated image.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // State that the texture holds nondescript 'intensity' data.
        glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
        // If the intensity of a given texel is lower than 0.5f, then the texture should not be sampled. In practice,
        // the higher the value, the less of the shadow is visible, and the other way around.
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FAIL_VALUE_ARB, 0.5F);
        // Set the automatic texture coordinate generation mode to eye linear. The texture coordinate is calculated
        // with the inverse of the model-view matrix and a plane that we will specify later on.
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    }

    /** Sets up the OpenGL states. */
    private static void setUpFrameBufferObject() {
        final int MAX_RENDERBUFFER_SIZE = glGetInteger(GL_MAX_RENDERBUFFER_SIZE);
        final int MAX_TEXTURE_SIZE = glGetInteger(GL_MAX_TEXTURE_SIZE);
        /**
         * Cap the maximum shadow map size at 1024x1024 pixels or at the maximum render buffer size. If you have a good
         * graphics card, feel free to increase this value. The program will lag
         * if I record and run the program at the same time with higher values.
         */
        if (MAX_TEXTURE_SIZE > 1024) {
            if (MAX_RENDERBUFFER_SIZE < MAX_TEXTURE_SIZE) {
                shadowMapWidth = shadowMapHeight = MAX_RENDERBUFFER_SIZE;
            } else {
                shadowMapWidth = shadowMapHeight = 1024;
            }
        } else {
            shadowMapWidth = shadowMapHeight = MAX_TEXTURE_SIZE;
        }
        // Generate and bind a frame buffer.
        frameBuffer = glGenFramebuffers();
        glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
        // Generate and bind a render buffer.
        renderBuffer = glGenRenderbuffers();
        glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
        // Set the internal storage format of the render buffer to a depth component of 32 bits (4 bytes).
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, shadowMapWidth, shadowMapHeight);
        // Attach the render buffer to the frame buffer as a depth attachment. This means that, if the frame buffer is
        // bound, any depth texture values will be copied to the render buffer object.
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBuffer);
        // OpenGL shall make no amendment to the colour or multisample buffer.
        glDrawBuffer(GL_NONE);
        // Disable the colour buffer for pixel read operations (such as glReadPixels or glCopyTexImage2D).
        glReadBuffer(GL_NONE);
        // Check for frame buffer errors.
        int FBOStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if (FBOStatus != GL_FRAMEBUFFER_COMPLETE) {
            System.err.println("Framebuffer error: " + gluErrorString(glGetError()));
        }
        // Bind the default frame buffer, which is used for ordinary drawing.
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    private static void setUpCamera() {
        camera.applyPerspectiveMatrix();
        camera.applyOptimalStates();
    }

    private static void setUpModel() {
        try {
            bunnyDisplayList = OBJLoader.createDisplayList(OBJLoader.loadModel(new File("res/models/bunny.obj")));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            cleanUp();
        } catch (IOException e) {
            e.printStackTrace();
            cleanUp();
        }
    }

    private static void render() {
        // Reset any changes made to the model-view matrix.
        glLoadIdentity();
        // Apply the camera position and orientation to the model-view matrix.
        camera.applyTranslations();
        // Clear the screen.
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        // Store the current attribute state.
        glPushAttrib(GL_ALL_ATTRIB_BITS);
        {
            generateTextureCoordinates();
            drawGround();
            drawScene();
            drawShadowMap();
        }
        // Restore the previous attribute state.
        glPopAttrib();
    }

    private static void generateTextureCoordinates() {
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        // Compare the texture coordinate 'r' (the distance from the light to the surface of the object) to the
        // value in the depth buffer.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
        // Enable 's' texture coordinate generation.
        glEnable(GL_TEXTURE_GEN_S);
        // Enable 't' texture coordinate generation.
        glEnable(GL_TEXTURE_GEN_T);
        // Enable 'r' texture coordinate generation.
        glEnable(GL_TEXTURE_GEN_R);
        // Enable 'q' texture coordinate generation.
        glEnable(GL_TEXTURE_GEN_Q);
        textureBuffer.clear();
        textureBuffer.put(0, depthModelViewProjection.m00);
        textureBuffer.put(1, depthModelViewProjection.m01);
        textureBuffer.put(2, depthModelViewProjection.m02);
        textureBuffer.put(3, depthModelViewProjection.m03);

        glTexGen(GL_S, GL_EYE_PLANE, textureBuffer);

        textureBuffer.put(0, depthModelViewProjection.m10);
        textureBuffer.put(1, depthModelViewProjection.m11);
        textureBuffer.put(2, depthModelViewProjection.m12);
        textureBuffer.put(3, depthModelViewProjection.m13);

        glTexGen(GL_T, GL_EYE_PLANE, textureBuffer);

        textureBuffer.put(0, depthModelViewProjection.m20);
        textureBuffer.put(1, depthModelViewProjection.m21);
        textureBuffer.put(2, depthModelViewProjection.m22);
        textureBuffer.put(3, depthModelViewProjection.m23);

        glTexGen(GL_R, GL_EYE_PLANE, textureBuffer);

        textureBuffer.put(0, depthModelViewProjection.m30);
        textureBuffer.put(1, depthModelViewProjection.m31);
        textureBuffer.put(2, depthModelViewProjection.m32);
        textureBuffer.put(3, depthModelViewProjection.m33);

        glTexGen(GL_Q, GL_EYE_PLANE, textureBuffer);
    }

    private static void drawGround() {
        glPushAttrib(GL_LIGHTING_BIT);
        {
            glDisable(GL_LIGHTING);
            glBegin(GL_QUADS);
            glColor3f(0.3F, 0.6F, 0.3F);
            glVertex3f(-120, -19, -120);
            glVertex3f(-120, -19, +120);
            glVertex3f(+120, -19, +120);
            glVertex3f(+120, -19, -120);
            glEnd();
        }
        glPopAttrib();
    }

    /** Generate the shadow map. */
    private static void drawShadowMap() {
        /**
         * The model-view matrix of the light.
         */
        FloatBuffer lightModelView = BufferUtils.createFloatBuffer(16);
        /**
         * The projection matrix of the light.
         */
        FloatBuffer lightProjection = BufferUtils.createFloatBuffer(16);
        Matrix4f lightProjectionTemp = new Matrix4f();
        Matrix4f lightModelViewTemp = new Matrix4f();
        /**
         * The radius that encompasses all the objects that cast shadows in the scene. There should
         * be no object farther away than 50 units from [0, 0, 0] in any direction.
         * If an object exceeds the radius, the object may cast shadows wrongly.
         */
        float sceneBoundingRadius = 50;
        /**
         * The distance from the light to the scene, assuming that the scene is located
         * at [0, 0, 0]. Using the Pythagorean theorem, the distance is calculated by taking the square-root of the
         * sum of each of the components of the light position squared.
         */
        float lightToSceneDistance = (float) Math.sqrt(lightPosition.get(0) * lightPosition.get(0) +
                lightPosition.get(1) * lightPosition.get(1) +
                lightPosition.get(2) * lightPosition.get(2));
        /**
         * The distance to the object that is nearest to the camera. This excludes objects that do not cast shadows.
         * This will be used as the zNear parameter in gluPerspective.
         */
        float nearPlane = lightToSceneDistance - sceneBoundingRadius;
        if (nearPlane < 0) {
            System.err.println("Camera is too close to scene. A valid shadow map cannot be generated.");
        }
        /**
         * The field-of-view of the shadow frustum in degrees. Formula taken from the OpenGL SuperBible.
         */
        float fieldOfView = (float) Math.toDegrees(2.0F * Math.atan(sceneBoundingRadius / lightToSceneDistance));
        glMatrixMode(GL_PROJECTION);
        // Store the current projection matrix.
        glPushMatrix();
        glLoadIdentity();
        // Generate the 'shadow frustum', a perspective projection matrix that shows all the objects in the scene.
        gluPerspective(fieldOfView, 1, nearPlane, nearPlane + sceneBoundingRadius * 2);
        // Store the shadow frustum in 'lightProjection'.
        glGetFloat(GL_PROJECTION_MATRIX, lightProjection);
        glMatrixMode(GL_MODELVIEW);
        // Store the current model-view matrix.
        glPushMatrix();
        glLoadIdentity();
        // Have the 'shadow camera' look toward [0, 0, 0] and be location at the light's position.
        gluLookAt(lightPosition.get(0), lightPosition.get(1), lightPosition.get(2), 0, 0, 0, 0, 1, 0);
        glGetFloat(GL_MODELVIEW_MATRIX, lightModelView);
        // Set the view port to the shadow map dimensions so no part of the shadow is cut off.
        glViewport(0, 0, shadowMapWidth, shadowMapHeight);
        // Bind the extra frame buffer in which to store the shadow map in the form a depth texture.
        glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
        // Clear only the depth buffer bit. Clearing the colour buffer is unnecessary, because it is disabled (we
        // only need depth components).
        glClear(GL_DEPTH_BUFFER_BIT);
        // Store the current attribute state.
        glPushAttrib(GL_ALL_ATTRIB_BITS);
        {
            // Disable smooth shading, because the shading in a shadow map is irrelevant. It only matters where the
            // shape
            // vertices are positioned, and not what colour they have.
            glShadeModel(GL_FLAT);
            // Enabling all these lighting states is unnecessary for reasons listed above.
            glDisable(GL_LIGHTING);
            glDisable(GL_COLOR_MATERIAL);
            glDisable(GL_NORMALIZE);
            // Disable the writing of the red, green, blue, and alpha colour components,
            // because we only need the depth component.
            glColorMask(false, false, false, false);
            // An offset is given to every depth value of every polygon fragment to prevent a visual quirk called
            // 'shadow
            // acne'.
            glEnable(GL_POLYGON_OFFSET_FILL);
            // Draw the objects that cast shadows.
            drawScene();
            /**
             * Copy the pixels of the shadow map to the frame buffer object depth attachment.
             *  int target -> GL_TEXTURE_2D
             *  int level  -> 0, has to do with mip-mapping, which is not applicable to shadow maps
             *  int internalformat -> GL_DEPTH_COMPONENT
             *  int x, y -> 0, 0
             *  int width, height -> shadowMapWidth, shadowMapHeight
             *  int border -> 0
             */
            glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, shadowMapWidth, shadowMapHeight, 0);
            // Restore the previous model-view matrix.
            glPopMatrix();
            glMatrixMode(GL_PROJECTION);
            // Restore the previous projection matrix.
            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
        }// Restore the previous attribute state.
        glPopAttrib();
        // Restore the view port.
        glViewport(0, 0, Display.getWidth(), Display.getHeight());
        lightProjectionTemp.load(lightProjection);
        lightModelViewTemp.load(lightModelView);
        lightProjection.flip();
        lightModelView.flip();
        depthModelViewProjection.setIdentity();
        // [-1,1] -> [-0.5,0.5] -> [0,1]
        depthModelViewProjection.translate(new Vector3f(0.5F, 0.5F, 0.5F));
        depthModelViewProjection.scale(new Vector3f(0.5F, 0.5F, 0.5F));
        // Multiply the texture matrix by the projection and model-view matrices of the light.
        Matrix4f.mul(depthModelViewProjection, lightProjectionTemp, depthModelViewProjection);
        Matrix4f.mul(depthModelViewProjection, lightModelViewTemp, depthModelViewProjection);
        // Transpose the texture matrix.
        Matrix4f.transpose(depthModelViewProjection, depthModelViewProjection);
    }

    /** This is where anything you want rendered into your world should go. */
    private static void drawScene() {
        glPushMatrix();
        glScalef(5, 5, 5);
        glTranslatef(0, -2, 0);
        glCallList(bunnyDisplayList);
        glPopMatrix();
    }

    private static void logic() {
        glLight(GL_LIGHT0, GL_POSITION, lightPosition);
    }

    /** Handles the keyboard and mouse input. */
    private static void input() {
        while (Keyboard.next()) {
            if (Keyboard.getEventKeyState()) {
                if (Keyboard.isKeyDown(Keyboard.KEY_Q)) {
                    lightPosition.flip();
                    lightPosition.clear();
                    lightPosition.put(new float[]{camera.x(), camera.y(), camera.z(), 1});
                    lightPosition.flip();
                }
            }
        }
        if (Mouse.isGrabbed()) {
            camera.processMouse(1.0f, 80, -80);
        }
        camera.processKeyboard(16.0f, 10);
        if (Mouse.isButtonDown(0)) {
            Mouse.setGrabbed(true);
        } else if (Mouse.isButtonDown(1)) {
            Mouse.setGrabbed(false);
        }
    }

    /** Cleanup after the program. */
    private static void cleanUp() {
        glDeleteFramebuffers(frameBuffer);
        glDeleteLists(bunnyDisplayList, 1);
        glDeleteRenderbuffers(renderBuffer);
        Display.destroy();
    }
}
TOP

Related Classes of episode_36.ShadowMappingFBO

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.