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 Rmy 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 */ 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. 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 */ 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 */ 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 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 306 public void initialize(RenderManager rm, ViewPort vp) { 307 renderManager = rm; 308 viewPort = vp; 309 } 310 311 public boolean isInitialized() { 312 return viewPort != null; 313 } 314 315 /** 316 * returns the light direction used by the processor 317 * @return 318 */ 319 public Vector3f getDirection() { 320 return direction; 321 } 322 323 /** 324 * Sets the light direction to use to compute shadows 325 * @param direction 326 */ 327 public void setDirection(Vector3f direction) { 328 this.direction.set(direction).normalizeLocal(); 329 } 330 331 @SuppressWarnings("fallthrough") 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 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 */ 426 public void displayDebug() { 427 debug = true; 428 } 429 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 450 public void preFrame(float tpf) { 451 } 452 453 public void cleanup() { 454 } 455 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 551 public void setFlushQueues(boolean flushQueues) { 552 this.flushQueues = flushQueues; 553 } 554 } 555