Home | History | Annotate | Download | only in shadow
      1 /*
      2  * Copyright (c) 2009-2012 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.shadow;
     33 
     34 import com.jme3.bounding.BoundingBox;
     35 import com.jme3.bounding.BoundingVolume;
     36 import com.jme3.math.Matrix4f;
     37 import com.jme3.math.Transform;
     38 import com.jme3.math.Vector2f;
     39 import com.jme3.math.Vector3f;
     40 import com.jme3.renderer.Camera;
     41 import com.jme3.renderer.queue.GeometryList;
     42 import com.jme3.scene.Geometry;
     43 import static java.lang.Math.max;
     44 import static java.lang.Math.min;
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 /**
     49  * Includes various useful shadow mapping functions.
     50  *
     51  * @see
     52  * <ul>
     53  * <li><a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/">http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/</a></li>
     54  * <li><a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a></li>
     55  * </ul>
     56  * for more info.
     57  */
     58 public class ShadowUtil {
     59 
     60     /**
     61      * Updates a points arrays with the frustum corners of the provided camera.
     62      * @param viewCam
     63      * @param points
     64      */
     65     public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {
     66         int w = viewCam.getWidth();
     67         int h = viewCam.getHeight();
     68         float n = viewCam.getFrustumNear();
     69         float f = viewCam.getFrustumFar();
     70 
     71         points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), n));
     72         points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), n));
     73         points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), n));
     74         points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), n));
     75 
     76         points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), f));
     77         points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), f));
     78         points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), f));
     79         points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), f));
     80     }
     81 
     82     /**
     83      * Updates the points array to contain the frustum corners of the given
     84      * camera. The nearOverride and farOverride variables can be used
     85      * to override the camera's near/far values with own values.
     86      *
     87      * TODO: Reduce creation of new vectors
     88      *
     89      * @param viewCam
     90      * @param nearOverride
     91      * @param farOverride
     92      */
     93     public static void updateFrustumPoints(Camera viewCam,
     94             float nearOverride,
     95             float farOverride,
     96             float scale,
     97             Vector3f[] points) {
     98 
     99         Vector3f pos = viewCam.getLocation();
    100         Vector3f dir = viewCam.getDirection();
    101         Vector3f up = viewCam.getUp();
    102 
    103         float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear();
    104         float near = nearOverride;
    105         float far = farOverride;
    106         float ftop = viewCam.getFrustumTop();
    107         float fright = viewCam.getFrustumRight();
    108         float ratio = fright / ftop;
    109 
    110         float near_height;
    111         float near_width;
    112         float far_height;
    113         float far_width;
    114 
    115         if (viewCam.isParallelProjection()) {
    116             near_height = ftop;
    117             near_width = near_height * ratio;
    118             far_height = ftop;
    119             far_width = far_height * ratio;
    120         } else {
    121             near_height = depthHeightRatio * near;
    122             near_width = near_height * ratio;
    123             far_height = depthHeightRatio * far;
    124             far_width = far_height * ratio;
    125         }
    126 
    127         Vector3f right = dir.cross(up).normalizeLocal();
    128 
    129         Vector3f temp = new Vector3f();
    130         temp.set(dir).multLocal(far).addLocal(pos);
    131         Vector3f farCenter = temp.clone();
    132         temp.set(dir).multLocal(near).addLocal(pos);
    133         Vector3f nearCenter = temp.clone();
    134 
    135         Vector3f nearUp = temp.set(up).multLocal(near_height).clone();
    136         Vector3f farUp = temp.set(up).multLocal(far_height).clone();
    137         Vector3f nearRight = temp.set(right).multLocal(near_width).clone();
    138         Vector3f farRight = temp.set(right).multLocal(far_width).clone();
    139 
    140         points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight);
    141         points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight);
    142         points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight);
    143         points[3].set(nearCenter).subtractLocal(nearUp).addLocal(nearRight);
    144 
    145         points[4].set(farCenter).subtractLocal(farUp).subtractLocal(farRight);
    146         points[5].set(farCenter).addLocal(farUp).subtractLocal(farRight);
    147         points[6].set(farCenter).addLocal(farUp).addLocal(farRight);
    148         points[7].set(farCenter).subtractLocal(farUp).addLocal(farRight);
    149 
    150         if (scale != 1.0f) {
    151             // find center of frustum
    152             Vector3f center = new Vector3f();
    153             for (int i = 0; i < 8; i++) {
    154                 center.addLocal(points[i]);
    155             }
    156             center.divideLocal(8f);
    157 
    158             Vector3f cDir = new Vector3f();
    159             for (int i = 0; i < 8; i++) {
    160                 cDir.set(points[i]).subtractLocal(center);
    161                 cDir.multLocal(scale - 1.0f);
    162                 points[i].addLocal(cDir);
    163             }
    164         }
    165     }
    166 
    167     /**
    168      * Compute bounds of a geomList
    169      * @param list
    170      * @param transform
    171      * @return
    172      */
    173     public static BoundingBox computeUnionBound(GeometryList list, Transform transform) {
    174         BoundingBox bbox = new BoundingBox();
    175         for (int i = 0; i < list.size(); i++) {
    176             BoundingVolume vol = list.get(i).getWorldBound();
    177             BoundingVolume newVol = vol.transform(transform);
    178             //Nehon : prevent NaN and infinity values to screw the final bounding box
    179             if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) {
    180                 bbox.mergeLocal(newVol);
    181             }
    182         }
    183         return bbox;
    184     }
    185 
    186     /**
    187      * Compute bounds of a geomList
    188      * @param list
    189      * @param mat
    190      * @return
    191      */
    192     public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) {
    193         BoundingBox bbox = new BoundingBox();
    194         BoundingVolume store = null;
    195         for (int i = 0; i < list.size(); i++) {
    196             BoundingVolume vol = list.get(i).getWorldBound();
    197             store = vol.clone().transform(mat, null);
    198             //Nehon : prevent NaN and infinity values to screw the final bounding box
    199             if (!Float.isNaN(store.getCenter().x) && !Float.isInfinite(store.getCenter().x)) {
    200                 bbox.mergeLocal(store);
    201             }
    202         }
    203         return bbox;
    204     }
    205 
    206     /**
    207      * Computes the bounds of multiple bounding volumes
    208      * @param bv
    209      * @return
    210      */
    211     public static BoundingBox computeUnionBound(List<BoundingVolume> bv) {
    212         BoundingBox bbox = new BoundingBox();
    213         for (int i = 0; i < bv.size(); i++) {
    214             BoundingVolume vol = bv.get(i);
    215             bbox.mergeLocal(vol);
    216         }
    217         return bbox;
    218     }
    219 
    220     /**
    221      * Compute bounds from an array of points
    222      * @param pts
    223      * @param transform
    224      * @return
    225      */
    226     public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) {
    227         Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
    228         Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
    229         Vector3f temp = new Vector3f();
    230         for (int i = 0; i < pts.length; i++) {
    231             transform.transformVector(pts[i], temp);
    232 
    233             min.minLocal(temp);
    234             max.maxLocal(temp);
    235         }
    236         Vector3f center = min.add(max).multLocal(0.5f);
    237         Vector3f extent = max.subtract(min).multLocal(0.5f);
    238         return new BoundingBox(center, extent.x, extent.y, extent.z);
    239     }
    240 
    241     /**
    242      * Compute bounds from an array of points
    243      * @param pts
    244      * @param mat
    245      * @return
    246      */
    247     public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) {
    248         Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
    249         Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
    250         Vector3f temp = new Vector3f();
    251 
    252         for (int i = 0; i < pts.length; i++) {
    253             float w = mat.multProj(pts[i], temp);
    254 
    255             temp.x /= w;
    256             temp.y /= w;
    257             // Why was this commented out?
    258             temp.z /= w;
    259 
    260             min.minLocal(temp);
    261             max.maxLocal(temp);
    262         }
    263 
    264         Vector3f center = min.add(max).multLocal(0.5f);
    265         Vector3f extent = max.subtract(min).multLocal(0.5f);
    266         //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned
    267         return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f);
    268     }
    269 
    270     /**
    271      * Updates the shadow camera to properly contain the given
    272      * points (which contain the eye camera frustum corners)
    273      *
    274      * @param shadowCam
    275      * @param points
    276      */
    277     public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) {
    278         boolean ortho = shadowCam.isParallelProjection();
    279         shadowCam.setProjectionMatrix(null);
    280 
    281         if (ortho) {
    282             shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
    283         } else {
    284             shadowCam.setFrustumPerspective(45, 1, 1, 150);
    285         }
    286 
    287         Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
    288         Matrix4f projMatrix = shadowCam.getProjectionMatrix();
    289 
    290         BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
    291 
    292         Vector3f splitMin = splitBB.getMin(null);
    293         Vector3f splitMax = splitBB.getMax(null);
    294 
    295 //        splitMin.z = 0;
    296 
    297         // Create the crop matrix.
    298         float scaleX, scaleY, scaleZ;
    299         float offsetX, offsetY, offsetZ;
    300 
    301         scaleX = 2.0f / (splitMax.x - splitMin.x);
    302         scaleY = 2.0f / (splitMax.y - splitMin.y);
    303         offsetX = -0.5f * (splitMax.x + splitMin.x) * scaleX;
    304         offsetY = -0.5f * (splitMax.y + splitMin.y) * scaleY;
    305         scaleZ = 1.0f / (splitMax.z - splitMin.z);
    306         offsetZ = -splitMin.z * scaleZ;
    307 
    308         Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
    309                 0f, scaleY, 0f, offsetY,
    310                 0f, 0f, scaleZ, offsetZ,
    311                 0f, 0f, 0f, 1f);
    312 
    313 
    314         Matrix4f result = new Matrix4f();
    315         result.set(cropMatrix);
    316         result.multLocal(projMatrix);
    317 
    318         shadowCam.setProjectionMatrix(result);
    319     }
    320 
    321     /**
    322      * Updates the shadow camera to properly contain the given
    323      * points (which contain the eye camera frustum corners) and the
    324      * shadow occluder objects.
    325      *
    326      * @param occluders
    327      * @param receivers
    328      * @param shadowCam
    329      * @param points
    330      */
    331     public static void updateShadowCamera(GeometryList occluders,
    332             GeometryList receivers,
    333             Camera shadowCam,
    334             Vector3f[] points) {
    335         updateShadowCamera(occluders, receivers, shadowCam, points, null);
    336     }
    337 
    338     /**
    339      * Updates the shadow camera to properly contain the given
    340      * points (which contain the eye camera frustum corners) and the
    341      * shadow occluder objects.
    342      *
    343      * @param occluders
    344      * @param shadowCam
    345      * @param points
    346      */
    347     public static void updateShadowCamera(GeometryList occluders,
    348             GeometryList receivers,
    349             Camera shadowCam,
    350             Vector3f[] points,
    351             GeometryList splitOccluders) {
    352 
    353         boolean ortho = shadowCam.isParallelProjection();
    354 
    355         shadowCam.setProjectionMatrix(null);
    356 
    357         if (ortho) {
    358             shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
    359         } else {
    360             shadowCam.setFrustumPerspective(45, 1, 1, 150);
    361         }
    362 
    363         // create transform to rotate points to viewspace
    364         Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
    365 
    366         BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
    367 
    368         ArrayList<BoundingVolume> visRecvList = new ArrayList<BoundingVolume>();
    369         for (int i = 0; i < receivers.size(); i++) {
    370             // convert bounding box to light's viewproj space
    371             Geometry receiver = receivers.get(i);
    372             BoundingVolume bv = receiver.getWorldBound();
    373             BoundingVolume recvBox = bv.transform(viewProjMatrix, null);
    374 
    375             if (splitBB.intersects(recvBox)) {
    376                 visRecvList.add(recvBox);
    377             }
    378         }
    379 
    380         ArrayList<BoundingVolume> visOccList = new ArrayList<BoundingVolume>();
    381         for (int i = 0; i < occluders.size(); i++) {
    382             // convert bounding box to light's viewproj space
    383             Geometry occluder = occluders.get(i);
    384             BoundingVolume bv = occluder.getWorldBound();
    385             BoundingVolume occBox = bv.transform(viewProjMatrix, null);
    386 
    387             boolean intersects = splitBB.intersects(occBox);
    388             if (!intersects && occBox instanceof BoundingBox) {
    389                 BoundingBox occBB = (BoundingBox) occBox;
    390                 //Kirill 01/10/2011
    391                 // Extend the occluder further into the frustum
    392                 // This fixes shadow dissapearing issues when
    393                 // the caster itself is not in the view camera
    394                 // but its shadow is in the camera
    395                 //      The number is in world units
    396                 occBB.setZExtent(occBB.getZExtent() + 50);
    397                 occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25));
    398                 if (splitBB.intersects(occBB)) {
    399                     // To prevent extending the depth range too much
    400                     // We return the bound to its former shape
    401                     // Before adding it
    402                     occBB.setZExtent(occBB.getZExtent() - 50);
    403                     occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25));
    404                     visOccList.add(occBox);
    405                     if (splitOccluders != null) {
    406                         splitOccluders.add(occluder);
    407                     }
    408                 }
    409             } else if (intersects) {
    410                 visOccList.add(occBox);
    411                 if (splitOccluders != null) {
    412                     splitOccluders.add(occluder);
    413                 }
    414             }
    415         }
    416 
    417         BoundingBox casterBB = computeUnionBound(visOccList);
    418         BoundingBox receiverBB = computeUnionBound(visRecvList);
    419 
    420         //Nehon 08/18/2010 this is to avoid shadow bleeding when the ground is set to only receive shadows
    421         if (visOccList.size() != visRecvList.size()) {
    422             casterBB.setXExtent(casterBB.getXExtent() + 2.0f);
    423             casterBB.setYExtent(casterBB.getYExtent() + 2.0f);
    424             casterBB.setZExtent(casterBB.getZExtent() + 2.0f);
    425         }
    426 
    427         Vector3f casterMin = casterBB.getMin(null);
    428         Vector3f casterMax = casterBB.getMax(null);
    429 
    430         Vector3f receiverMin = receiverBB.getMin(null);
    431         Vector3f receiverMax = receiverBB.getMax(null);
    432 
    433         Vector3f splitMin = splitBB.getMin(null);
    434         Vector3f splitMax = splitBB.getMax(null);
    435 
    436         splitMin.z = 0;
    437 
    438         if (!ortho) {
    439             shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
    440         }
    441 
    442         Matrix4f projMatrix = shadowCam.getProjectionMatrix();
    443 
    444         Vector3f cropMin = new Vector3f();
    445         Vector3f cropMax = new Vector3f();
    446 
    447         // IMPORTANT: Special handling for Z values
    448         cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x);
    449         cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x);
    450 
    451         cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y);
    452         cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y);
    453 
    454         cropMin.z = min(casterMin.z, splitMin.z);
    455         cropMax.z = min(receiverMax.z, splitMax.z);
    456 
    457 
    458         // Create the crop matrix.
    459         float scaleX, scaleY, scaleZ;
    460         float offsetX, offsetY, offsetZ;
    461 
    462         scaleX = (2.0f) / (cropMax.x - cropMin.x);
    463         scaleY = (2.0f) / (cropMax.y - cropMin.y);
    464 
    465         offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX;
    466         offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY;
    467 
    468         scaleZ = 1.0f / (cropMax.z - cropMin.z);
    469         offsetZ = -cropMin.z * scaleZ;
    470 
    471 
    472 
    473         Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
    474                 0f, scaleY, 0f, offsetY,
    475                 0f, 0f, scaleZ, offsetZ,
    476                 0f, 0f, 0f, 1f);
    477 
    478 
    479         Matrix4f result = new Matrix4f();
    480         result.set(cropMatrix);
    481         result.multLocal(projMatrix);
    482 
    483         shadowCam.setProjectionMatrix(result);
    484 
    485     }
    486 }
    487