1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved. 3 * <p/> 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are met: 6 * 7 * * Redistributions of source code must retain the above copyright notice, 8 * this list of conditions and the following disclaimer. 9 * <p/> 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * <p/> 14 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 15 * may be used to endorse or promote products derived from this software 16 * without specific prior written permission. 17 * <p/> 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 package com.jme3.shadow; 31 32 import com.jme3.asset.AssetManager; 33 import com.jme3.material.Material; 34 import com.jme3.math.ColorRGBA; 35 import com.jme3.math.Matrix4f; 36 import com.jme3.math.Vector3f; 37 import com.jme3.post.SceneProcessor; 38 import com.jme3.renderer.Camera; 39 import com.jme3.renderer.RenderManager; 40 import com.jme3.renderer.Renderer; 41 import com.jme3.renderer.ViewPort; 42 import com.jme3.renderer.queue.GeometryList; 43 import com.jme3.renderer.queue.OpaqueComparator; 44 import com.jme3.renderer.queue.RenderQueue; 45 import com.jme3.renderer.queue.RenderQueue.ShadowMode; 46 import com.jme3.scene.Geometry; 47 import com.jme3.scene.Spatial; 48 import com.jme3.scene.debug.WireFrustum; 49 import com.jme3.texture.FrameBuffer; 50 import com.jme3.texture.Image.Format; 51 import com.jme3.texture.Texture.MagFilter; 52 import com.jme3.texture.Texture.MinFilter; 53 import com.jme3.texture.Texture.ShadowCompareMode; 54 import com.jme3.texture.Texture2D; 55 import com.jme3.ui.Picture; 56 57 /** 58 * PssmShadow renderer use Parrallel Split Shadow Mapping technique (pssm)<br> 59 * It splits the view frustum in several parts and compute a shadow map for each 60 * one.<br> splits are distributed so that the closer they are from the camera, 61 * the smaller they are to maximize the resolution used of the shadow map.<br> 62 * This result in a better quality shadow than standard shadow mapping.<br> for 63 * more informations on this read this 64 * <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a><br> 65 * <p/> 66 * @author Rémy Bouquet aka Nehon 67 */ 68 public class PssmShadowRenderer implements SceneProcessor { 69 70 /** 71 * <code>FilterMode</code> specifies how shadows are filtered 72 */ 73 public enum FilterMode { 74 75 /** 76 * Shadows are not filtered. Nearest sample is used, causing in blocky 77 * shadows. 78 */ 79 Nearest, 80 /** 81 * Bilinear filtering is used. Has the potential of being hardware 82 * accelerated on some GPUs 83 */ 84 Bilinear, 85 /** 86 * Dither-based sampling is used, very cheap but can look bad 87 * at low resolutions. 88 */ 89 Dither, 90 /** 91 * 4x4 percentage-closer filtering is used. Shadows will be smoother 92 * at the cost of performance 93 */ 94 PCF4, 95 /** 96 * 8x8 percentage-closer filtering is used. Shadows will be smoother 97 * at the cost of performance 98 */ 99 PCF8 100 } 101 102 /** 103 * Specifies the shadow comparison mode 104 */ 105 public enum CompareMode { 106 107 /** 108 * Shadow depth comparisons are done by using shader code 109 */ 110 Software, 111 /** 112 * Shadow depth comparisons are done by using the GPU's dedicated 113 * shadowing pipeline. 114 */ 115 Hardware; 116 } 117 private int nbSplits = 3; 118 private float lambda = 0.65f; 119 private float shadowIntensity = 0.7f; 120 private float zFarOverride = 0; 121 private RenderManager renderManager; 122 private ViewPort viewPort; 123 private FrameBuffer[] shadowFB; 124 private Texture2D[] shadowMaps; 125 private Texture2D dummyTex; 126 private Camera shadowCam; 127 private Material preshadowMat; 128 private Material postshadowMat; 129 private GeometryList splitOccluders = new GeometryList(new OpaqueComparator()); 130 private Matrix4f[] lightViewProjectionsMatrices; 131 private ColorRGBA splits; 132 private float[] splitsArray; 133 private boolean noOccluders = false; 134 private Vector3f direction = new Vector3f(); 135 private AssetManager assetManager; 136 private boolean debug = false; 137 private float edgesThickness = 1.0f; 138 private FilterMode filterMode; 139 private CompareMode compareMode; 140 private Picture[] dispPic; 141 private Vector3f[] points = new Vector3f[8]; 142 private boolean flushQueues = true; 143 144 /** 145 * Create a PSSM Shadow Renderer 146 * More info on the technique at <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a> 147 * @param manager the application asset manager 148 * @param size the size of the rendered shadowmaps (512,1024,2048, etc...) 149 * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). 150 * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). 151 */ PssmShadowRenderer(AssetManager manager, int size, int nbSplits)152 public PssmShadowRenderer(AssetManager manager, int size, int nbSplits) { 153 this(manager, size, nbSplits, new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md")); 154 155 } 156 157 /** 158 * Create a PSSM Shadow Renderer 159 * More info on the technique at <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a> 160 * @param manager the application asset manager 161 * @param size the size of the rendered shadowmaps (512,1024,2048, etc...) 162 * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). 163 * @param postShadowMat the material used for post shadows if you need to override it * 164 */ 165 //TODO remove the postShadowMat when we have shader injection....or remove this todo if we are in 2020. PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Material postShadowMat)166 public PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Material postShadowMat) { 167 assetManager = manager; 168 nbSplits = Math.max(Math.min(nbSplits, 4), 1); 169 this.nbSplits = nbSplits; 170 171 shadowFB = new FrameBuffer[nbSplits]; 172 shadowMaps = new Texture2D[nbSplits]; 173 dispPic = new Picture[nbSplits]; 174 lightViewProjectionsMatrices = new Matrix4f[nbSplits]; 175 splits = new ColorRGBA(); 176 splitsArray = new float[nbSplits + 1]; 177 178 //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) 179 dummyTex = new Texture2D(size, size, Format.RGBA8); 180 181 preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md"); 182 this.postshadowMat = postShadowMat; 183 184 for (int i = 0; i < nbSplits; i++) { 185 lightViewProjectionsMatrices[i] = new Matrix4f(); 186 shadowFB[i] = new FrameBuffer(size, size, 1); 187 shadowMaps[i] = new Texture2D(size, size, Format.Depth); 188 189 shadowFB[i].setDepthTexture(shadowMaps[i]); 190 191 //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) 192 shadowFB[i].setColorTexture(dummyTex); 193 194 postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]); 195 196 //quads for debuging purpose 197 dispPic[i] = new Picture("Picture" + i); 198 dispPic[i].setTexture(manager, shadowMaps[i], false); 199 } 200 201 setCompareMode(CompareMode.Hardware); 202 setFilterMode(FilterMode.Bilinear); 203 setShadowIntensity(0.7f); 204 205 shadowCam = new Camera(size, size); 206 shadowCam.setParallelProjection(true); 207 208 for (int i = 0; i < points.length; i++) { 209 points[i] = new Vector3f(); 210 } 211 } 212 213 /** 214 * Sets the filtering mode for shadow edges see {@link FilterMode} for more info 215 * @param filterMode 216 */ setFilterMode(FilterMode filterMode)217 public void setFilterMode(FilterMode filterMode) { 218 if (filterMode == null) { 219 throw new NullPointerException(); 220 } 221 222 if (this.filterMode == filterMode) { 223 return; 224 } 225 226 this.filterMode = filterMode; 227 postshadowMat.setInt("FilterMode", filterMode.ordinal()); 228 postshadowMat.setFloat("PCFEdge", edgesThickness); 229 if (compareMode == CompareMode.Hardware) { 230 for (Texture2D shadowMap : shadowMaps) { 231 if (filterMode == FilterMode.Bilinear) { 232 shadowMap.setMagFilter(MagFilter.Bilinear); 233 shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); 234 } else { 235 shadowMap.setMagFilter(MagFilter.Nearest); 236 shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); 237 } 238 } 239 } 240 } 241 242 /** 243 * sets the shadow compare mode see {@link CompareMode} for more info 244 * @param compareMode 245 */ setCompareMode(CompareMode compareMode)246 public void setCompareMode(CompareMode compareMode) { 247 if (compareMode == null) { 248 throw new NullPointerException(); 249 } 250 251 if (this.compareMode == compareMode) { 252 return; 253 } 254 255 this.compareMode = compareMode; 256 for (Texture2D shadowMap : shadowMaps) { 257 if (compareMode == CompareMode.Hardware) { 258 shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual); 259 if (filterMode == FilterMode.Bilinear) { 260 shadowMap.setMagFilter(MagFilter.Bilinear); 261 shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); 262 } else { 263 shadowMap.setMagFilter(MagFilter.Nearest); 264 shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); 265 } 266 } else { 267 shadowMap.setShadowCompareMode(ShadowCompareMode.Off); 268 shadowMap.setMagFilter(MagFilter.Nearest); 269 shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); 270 } 271 } 272 postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware); 273 } 274 275 //debug function that create a displayable frustrum createFrustum(Vector3f[] pts, int i)276 private Geometry createFrustum(Vector3f[] pts, int i) { 277 WireFrustum frustum = new WireFrustum(pts); 278 Geometry frustumMdl = new Geometry("f", frustum); 279 frustumMdl.setCullHint(Spatial.CullHint.Never); 280 frustumMdl.setShadowMode(ShadowMode.Off); 281 Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 282 mat.getAdditionalRenderState().setWireframe(true); 283 frustumMdl.setMaterial(mat); 284 switch (i) { 285 case 0: 286 frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink); 287 break; 288 case 1: 289 frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); 290 break; 291 case 2: 292 frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green); 293 break; 294 case 3: 295 frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue); 296 break; 297 default: 298 frustumMdl.getMaterial().setColor("Color", ColorRGBA.White); 299 break; 300 } 301 302 frustumMdl.updateGeometricState(); 303 return frustumMdl; 304 } 305 initialize(RenderManager rm, ViewPort vp)306 public void initialize(RenderManager rm, ViewPort vp) { 307 renderManager = rm; 308 viewPort = vp; 309 } 310 isInitialized()311 public boolean isInitialized() { 312 return viewPort != null; 313 } 314 315 /** 316 * returns the light direction used by the processor 317 * @return 318 */ getDirection()319 public Vector3f getDirection() { 320 return direction; 321 } 322 323 /** 324 * Sets the light direction to use to compute shadows 325 * @param direction 326 */ setDirection(Vector3f direction)327 public void setDirection(Vector3f direction) { 328 this.direction.set(direction).normalizeLocal(); 329 } 330 331 @SuppressWarnings("fallthrough") postQueue(RenderQueue rq)332 public void postQueue(RenderQueue rq) { 333 GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); 334 if (occluders.size() == 0) { 335 return; 336 } 337 338 GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive); 339 if (receivers.size() == 0) { 340 return; 341 } 342 343 Camera viewCam = viewPort.getCamera(); 344 345 float zFar = zFarOverride; 346 if (zFar == 0) { 347 zFar = viewCam.getFrustumFar(); 348 } 349 350 //We prevent computing the frustum points and splits with zeroed or negative near clip value 351 float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f); 352 ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points); 353 354 //shadowCam.setDirection(direction); 355 shadowCam.getRotation().lookAt(direction, shadowCam.getUp()); 356 shadowCam.update(); 357 shadowCam.updateViewProjection(); 358 359 PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda); 360 361 362 switch (splitsArray.length) { 363 case 5: 364 splits.a = splitsArray[4]; 365 case 4: 366 splits.b = splitsArray[3]; 367 case 3: 368 splits.g = splitsArray[2]; 369 case 2: 370 case 1: 371 splits.r = splitsArray[1]; 372 break; 373 } 374 375 Renderer r = renderManager.getRenderer(); 376 renderManager.setForcedMaterial(preshadowMat); 377 renderManager.setForcedTechnique("PreShadow"); 378 379 for (int i = 0; i < nbSplits; i++) { 380 381 // update frustum points based on current camera and split 382 ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points); 383 384 //Updating shadow cam with curent split frustra 385 ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders); 386 387 //saving light view projection matrix for this split 388 lightViewProjectionsMatrices[i] = shadowCam.getViewProjectionMatrix().clone(); 389 renderManager.setCamera(shadowCam, false); 390 391 r.setFrameBuffer(shadowFB[i]); 392 r.clearBuffers(false, true, false); 393 394 // render shadow casters to shadow map 395 viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true); 396 } 397 if (flushQueues) { 398 occluders.clear(); 399 } 400 //restore setting for future rendering 401 r.setFrameBuffer(viewPort.getOutputFrameBuffer()); 402 renderManager.setForcedMaterial(null); 403 renderManager.setForcedTechnique(null); 404 renderManager.setCamera(viewCam, false); 405 406 } 407 408 //debug only : displays depth shadow maps displayShadowMap(Renderer r)409 private void displayShadowMap(Renderer r) { 410 Camera cam = viewPort.getCamera(); 411 renderManager.setCamera(cam, true); 412 int h = cam.getHeight(); 413 for (int i = 0; i < dispPic.length; i++) { 414 dispPic[i].setPosition(64 * (i + 1) + 128 * i, h / 20f); 415 dispPic[i].setWidth(128); 416 dispPic[i].setHeight(128); 417 dispPic[i].updateGeometricState(); 418 renderManager.renderGeometry(dispPic[i]); 419 } 420 renderManager.setCamera(cam, false); 421 } 422 423 /**For dubuging purpose 424 * Allow to "snapshot" the current frustrum to the scene 425 */ displayDebug()426 public void displayDebug() { 427 debug = true; 428 } 429 postFrame(FrameBuffer out)430 public void postFrame(FrameBuffer out) { 431 Camera cam = viewPort.getCamera(); 432 if (!noOccluders) { 433 postshadowMat.setColor("Splits", splits); 434 for (int i = 0; i < nbSplits; i++) { 435 postshadowMat.setMatrix4("LightViewProjectionMatrix" + i, lightViewProjectionsMatrices[i]); 436 } 437 renderManager.setForcedMaterial(postshadowMat); 438 439 viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, cam, flushQueues); 440 441 renderManager.setForcedMaterial(null); 442 renderManager.setCamera(cam, false); 443 444 } 445 if (debug) { 446 displayShadowMap(renderManager.getRenderer()); 447 } 448 } 449 preFrame(float tpf)450 public void preFrame(float tpf) { 451 } 452 cleanup()453 public void cleanup() { 454 } 455 reshape(ViewPort vp, int w, int h)456 public void reshape(ViewPort vp, int w, int h) { 457 } 458 459 /** 460 * returns the labda parameter<br> 461 * see {@link setLambda(float lambda)} 462 * @return lambda 463 */ getLambda()464 public float getLambda() { 465 return lambda; 466 } 467 468 /* 469 * Adjust the repartition of the different shadow maps in the shadow extend 470 * usualy goes from 0.0 to 1.0 471 * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged 472 * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend. 473 * the default value is set to 0.65f (theoric optimal value). 474 * @param lambda the lambda value. 475 */ setLambda(float lambda)476 public void setLambda(float lambda) { 477 this.lambda = lambda; 478 } 479 480 /** 481 * How far the shadows are rendered in the view 482 * see {@link setShadowZExtend(float zFar)} 483 * @return shadowZExtend 484 */ getShadowZExtend()485 public float getShadowZExtend() { 486 return zFarOverride; 487 } 488 489 /** 490 * Set the distance from the eye where the shadows will be rendered 491 * default value is dynamicaly computed to the shadow casters/receivers union bound zFar, capped to view frustum far value. 492 * @param zFar the zFar values that override the computed one 493 */ setShadowZExtend(float zFar)494 public void setShadowZExtend(float zFar) { 495 this.zFarOverride = zFar; 496 } 497 498 /** 499 * returns the shdaow intensity<br> 500 * see {@link setShadowIntensity(float shadowIntensity)} 501 * @return shadowIntensity 502 */ getShadowIntensity()503 public float getShadowIntensity() { 504 return shadowIntensity; 505 } 506 507 /** 508 * Set the shadowIntensity, the value should be between 0 and 1, 509 * a 0 value gives a bright and invisilble shadow, 510 * a 1 value gives a pitch black shadow, 511 * default is 0.7 512 * @param shadowIntensity the darkness of the shadow 513 */ setShadowIntensity(float shadowIntensity)514 public void setShadowIntensity(float shadowIntensity) { 515 this.shadowIntensity = shadowIntensity; 516 postshadowMat.setFloat("ShadowIntensity", shadowIntensity); 517 } 518 519 /** 520 * returns the edges thickness <br> 521 * see {@link setEdgesThickness(int edgesThickness)} 522 * @return edgesThickness 523 */ getEdgesThickness()524 public int getEdgesThickness() { 525 return (int) (edgesThickness * 10); 526 } 527 528 /** 529 * Sets the shadow edges thickness. default is 1, setting it to lower values can help to reduce the jagged effect of the shadow edges 530 * @param edgesThickness 531 */ setEdgesThickness(int edgesThickness)532 public void setEdgesThickness(int edgesThickness) { 533 this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); 534 this.edgesThickness *= 0.1f; 535 postshadowMat.setFloat("PCFEdge", edgesThickness); 536 } 537 538 /** 539 * returns true if the PssmRenderer flushed the shadow queues 540 * @return flushQueues 541 */ isFlushQueues()542 public boolean isFlushQueues() { 543 return flushQueues; 544 } 545 546 /** 547 * Set this to false if you want to use several PssmRederers to have multiple shadows cast by multiple light sources. 548 * Make sure the last PssmRenderer in the stack DO flush the queues, but not the others 549 * @param flushQueues 550 */ setFlushQueues(boolean flushQueues)551 public void setFlushQueues(boolean flushQueues) { 552 this.flushQueues = flushQueues; 553 } 554 } 555