1 /* 2 * Copyright (c) 2009-2010 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 33 package com.jme3.post; 34 35 import com.jme3.asset.AssetManager; 36 import com.jme3.material.Material; 37 import com.jme3.math.Vector2f; 38 import com.jme3.renderer.*; 39 import com.jme3.renderer.queue.RenderQueue; 40 import com.jme3.texture.FrameBuffer; 41 import com.jme3.texture.Image; 42 import com.jme3.texture.Image.Format; 43 import com.jme3.texture.Texture; 44 import com.jme3.texture.Texture.MagFilter; 45 import com.jme3.texture.Texture.MinFilter; 46 import com.jme3.texture.Texture2D; 47 import com.jme3.ui.Picture; 48 import java.util.Collection; 49 import java.util.logging.Logger; 50 51 public class HDRRenderer implements SceneProcessor { 52 53 private static final int LUMMODE_NONE = 0x1, 54 LUMMODE_ENCODE_LUM = 0x2, 55 LUMMODE_DECODE_LUM = 0x3; 56 57 private Renderer renderer; 58 private RenderManager renderManager; 59 private ViewPort viewPort; 60 private static final Logger logger = Logger.getLogger(HDRRenderer.class.getName()); 61 62 private Camera fbCam = new Camera(1, 1); 63 64 private FrameBuffer msFB; 65 66 private FrameBuffer mainSceneFB; 67 private Texture2D mainScene; 68 private FrameBuffer scene64FB; 69 private Texture2D scene64; 70 private FrameBuffer scene8FB; 71 private Texture2D scene8; 72 private FrameBuffer scene1FB[] = new FrameBuffer[2]; 73 private Texture2D scene1[] = new Texture2D[2]; 74 75 private Material hdr64; 76 private Material hdr8; 77 private Material hdr1; 78 private Material tone; 79 80 private Picture fsQuad; 81 private float time = 0; 82 private int curSrc = -1; 83 private int oppSrc = -1; 84 private float blendFactor = 0; 85 86 private int numSamples = 0; 87 private float exposure = 0.18f; 88 private float whiteLevel = 100f; 89 private float throttle = -1; 90 private int maxIterations = -1; 91 private Image.Format bufFormat = Format.RGB16F; 92 93 private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps; 94 private MagFilter fbMagFilter = MagFilter.Bilinear; 95 private AssetManager manager; 96 97 private boolean enabled = true; 98 99 public HDRRenderer(AssetManager manager, Renderer renderer){ 100 this.manager = manager; 101 this.renderer = renderer; 102 103 Collection<Caps> caps = renderer.getCaps(); 104 if (caps.contains(Caps.PackedFloatColorBuffer)) 105 bufFormat = Format.RGB111110F; 106 else if (caps.contains(Caps.FloatColorBuffer)) 107 bufFormat = Format.RGB16F; 108 else{ 109 enabled = false; 110 return; 111 } 112 } 113 114 public boolean isEnabled() { 115 return enabled; 116 } 117 118 public void setSamples(int samples){ 119 this.numSamples = samples; 120 } 121 122 public void setExposure(float exp){ 123 this.exposure = exp; 124 } 125 126 public void setWhiteLevel(float whiteLevel){ 127 this.whiteLevel = whiteLevel; 128 } 129 130 public void setMaxIterations(int maxIterations){ 131 this.maxIterations = maxIterations; 132 133 // regenerate shaders if needed 134 if (hdr64 != null) 135 createLumShaders(); 136 } 137 138 public void setThrottle(float throttle){ 139 this.throttle = throttle; 140 } 141 142 public void setUseFastFilter(boolean fastFilter){ 143 if (fastFilter){ 144 fbMagFilter = MagFilter.Nearest; 145 fbMinFilter = MinFilter.NearestNoMipMaps; 146 }else{ 147 fbMagFilter = MagFilter.Bilinear; 148 fbMinFilter = MinFilter.BilinearNoMipMaps; 149 } 150 } 151 152 public Picture createDisplayQuad(/*int mode, Texture tex*/){ 153 if (scene64 == null) 154 return null; 155 156 Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md"); 157 // if (mode == LUMMODE_ENCODE_LUM) 158 // mat.setBoolean("EncodeLum", true); 159 // else if (mode == LUMMODE_DECODE_LUM) 160 mat.setBoolean("DecodeLum", true); 161 mat.setTexture("Texture", scene64); 162 // mat.setTexture("Texture", tex); 163 164 Picture dispQuad = new Picture("Luminance Display"); 165 dispQuad.setMaterial(mat); 166 return dispQuad; 167 } 168 169 private Material createLumShader(int srcW, int srcH, int bufW, int bufH, int mode, 170 int iters, Texture tex){ 171 Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md"); 172 173 Vector2f blockSize = new Vector2f(1f / bufW, 1f / bufH); 174 Vector2f pixelSize = new Vector2f(1f / srcW, 1f / srcH); 175 Vector2f blocks = new Vector2f(); 176 float numPixels = Float.POSITIVE_INFINITY; 177 if (iters != -1){ 178 do { 179 pixelSize.multLocal(2); 180 blocks.set(blockSize.x / pixelSize.x, 181 blockSize.y / pixelSize.y); 182 numPixels = blocks.x * blocks.y; 183 } while (numPixels > iters); 184 }else{ 185 blocks.set(blockSize.x / pixelSize.x, 186 blockSize.y / pixelSize.y); 187 numPixels = blocks.x * blocks.y; 188 } 189 System.out.println(numPixels); 190 191 mat.setBoolean("Blocks", true); 192 if (mode == LUMMODE_ENCODE_LUM) 193 mat.setBoolean("EncodeLum", true); 194 else if (mode == LUMMODE_DECODE_LUM) 195 mat.setBoolean("DecodeLum", true); 196 197 mat.setTexture("Texture", tex); 198 mat.setVector2("BlockSize", blockSize); 199 mat.setVector2("PixelSize", pixelSize); 200 mat.setFloat("NumPixels", numPixels); 201 202 return mat; 203 } 204 205 private void createLumShaders(){ 206 int w = mainSceneFB.getWidth(); 207 int h = mainSceneFB.getHeight(); 208 hdr64 = createLumShader(w, h, 64, 64, LUMMODE_ENCODE_LUM, maxIterations, mainScene); 209 hdr8 = createLumShader(64, 64, 8, 8, LUMMODE_NONE, maxIterations, scene64); 210 hdr1 = createLumShader(8, 8, 1, 1, LUMMODE_NONE, maxIterations, scene8); 211 } 212 213 private int opposite(int i){ 214 return i == 1 ? 0 : 1; 215 } 216 217 private void renderProcessing(Renderer r, FrameBuffer dst, Material mat){ 218 if (dst == null){ 219 fsQuad.setWidth(mainSceneFB.getWidth()); 220 fsQuad.setHeight(mainSceneFB.getHeight()); 221 fbCam.resize(mainSceneFB.getWidth(), mainSceneFB.getHeight(), true); 222 }else{ 223 fsQuad.setWidth(dst.getWidth()); 224 fsQuad.setHeight(dst.getHeight()); 225 fbCam.resize(dst.getWidth(), dst.getHeight(), true); 226 } 227 fsQuad.setMaterial(mat); 228 fsQuad.updateGeometricState(); 229 renderManager.setCamera(fbCam, true); 230 231 r.setFrameBuffer(dst); 232 r.clearBuffers(true, true, true); 233 renderManager.renderGeometry(fsQuad); 234 } 235 236 private void renderToneMap(Renderer r, FrameBuffer out){ 237 tone.setFloat("A", exposure); 238 tone.setFloat("White", whiteLevel); 239 tone.setTexture("Lum", scene1[oppSrc]); 240 tone.setTexture("Lum2", scene1[curSrc]); 241 tone.setFloat("BlendFactor", blendFactor); 242 renderProcessing(r, out, tone); 243 } 244 245 private void updateAverageLuminance(Renderer r){ 246 renderProcessing(r, scene64FB, hdr64); 247 renderProcessing(r, scene8FB, hdr8); 248 renderProcessing(r, scene1FB[curSrc], hdr1); 249 } 250 251 public boolean isInitialized(){ 252 return viewPort != null; 253 } 254 255 public void reshape(ViewPort vp, int w, int h){ 256 if (mainSceneFB != null){ 257 renderer.deleteFrameBuffer(mainSceneFB); 258 } 259 260 mainSceneFB = new FrameBuffer(w, h, 1); 261 mainScene = new Texture2D(w, h, bufFormat); 262 mainSceneFB.setDepthBuffer(Format.Depth); 263 mainSceneFB.setColorTexture(mainScene); 264 mainScene.setMagFilter(fbMagFilter); 265 mainScene.setMinFilter(fbMinFilter); 266 267 if (msFB != null){ 268 renderer.deleteFrameBuffer(msFB); 269 } 270 271 tone.setTexture("Texture", mainScene); 272 273 Collection<Caps> caps = renderer.getCaps(); 274 if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)){ 275 msFB = new FrameBuffer(w, h, numSamples); 276 msFB.setDepthBuffer(Format.Depth); 277 msFB.setColorBuffer(bufFormat); 278 vp.setOutputFrameBuffer(msFB); 279 }else{ 280 if (numSamples > 1) 281 logger.warning("FBO multisampling not supported on this GPU, request ignored."); 282 283 vp.setOutputFrameBuffer(mainSceneFB); 284 } 285 286 createLumShaders(); 287 } 288 289 public void initialize(RenderManager rm, ViewPort vp){ 290 if (!enabled) 291 return; 292 293 renderer = rm.getRenderer(); 294 renderManager = rm; 295 viewPort = vp; 296 297 // loadInitial() 298 fsQuad = new Picture("HDR Fullscreen Quad"); 299 300 Format lumFmt = Format.RGB8; 301 scene64FB = new FrameBuffer(64, 64, 1); 302 scene64 = new Texture2D(64, 64, lumFmt); 303 scene64FB.setColorTexture(scene64); 304 scene64.setMagFilter(fbMagFilter); 305 scene64.setMinFilter(fbMinFilter); 306 307 scene8FB = new FrameBuffer(8, 8, 1); 308 scene8 = new Texture2D(8, 8, lumFmt); 309 scene8FB.setColorTexture(scene8); 310 scene8.setMagFilter(fbMagFilter); 311 scene8.setMinFilter(fbMinFilter); 312 313 scene1FB[0] = new FrameBuffer(1, 1, 1); 314 scene1[0] = new Texture2D(1, 1, lumFmt); 315 scene1FB[0].setColorTexture(scene1[0]); 316 317 scene1FB[1] = new FrameBuffer(1, 1, 1); 318 scene1[1] = new Texture2D(1, 1, lumFmt); 319 scene1FB[1].setColorTexture(scene1[1]); 320 321 // prepare tonemap shader 322 tone = new Material(manager, "Common/MatDefs/Hdr/ToneMap.j3md"); 323 tone.setFloat("A", 0.18f); 324 tone.setFloat("White", 100); 325 326 // load(); 327 int w = vp.getCamera().getWidth(); 328 int h = vp.getCamera().getHeight(); 329 reshape(vp, w, h); 330 331 332 } 333 334 public void preFrame(float tpf) { 335 if (!enabled) 336 return; 337 338 time += tpf; 339 blendFactor = (time / throttle); 340 } 341 342 public void postQueue(RenderQueue rq) { 343 } 344 345 public void postFrame(FrameBuffer out) { 346 if (!enabled) 347 return; 348 349 if (msFB != null){ 350 // first render to multisampled FB 351 // renderer.setFrameBuffer(msFB); 352 // renderer.clearBuffers(true,true,true); 353 // 354 // renderManager.renderViewPortRaw(viewPort); 355 356 // render back to non-multisampled FB 357 renderer.copyFrameBuffer(msFB, mainSceneFB); 358 }else{ 359 // renderer.setFrameBuffer(mainSceneFB); 360 // renderer.clearBuffers(true,true,false); 361 // 362 // renderManager.renderViewPortRaw(viewPort); 363 } 364 365 // should we update avg lum? 366 if (throttle == -1){ 367 // update every frame 368 curSrc = 0; 369 oppSrc = 0; 370 blendFactor = 0; 371 time = 0; 372 updateAverageLuminance(renderer); 373 }else{ 374 if (curSrc == -1){ 375 curSrc = 0; 376 oppSrc = 0; 377 378 // initial update 379 updateAverageLuminance(renderer); 380 381 blendFactor = 0; 382 time = 0; 383 }else if (time > throttle){ 384 385 // time to switch 386 oppSrc = curSrc; 387 curSrc = opposite(curSrc); 388 389 updateAverageLuminance(renderer); 390 391 blendFactor = 0; 392 time = 0; 393 } 394 } 395 396 // since out == mainSceneFB, tonemap into the main screen instead 397 //renderToneMap(renderer, out); 398 renderToneMap(renderer, null); 399 400 renderManager.setCamera(viewPort.getCamera(), false); 401 } 402 403 public void cleanup() { 404 if (!enabled) 405 return; 406 407 if (msFB != null) 408 renderer.deleteFrameBuffer(msFB); 409 if (mainSceneFB != null) 410 renderer.deleteFrameBuffer(mainSceneFB); 411 if (scene64FB != null){ 412 renderer.deleteFrameBuffer(scene64FB); 413 renderer.deleteFrameBuffer(scene8FB); 414 renderer.deleteFrameBuffer(scene1FB[0]); 415 renderer.deleteFrameBuffer(scene1FB[1]); 416 } 417 } 418 419 } 420