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 HDRRenderer(AssetManager manager, Renderer renderer)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 isEnabled()114 public boolean isEnabled() { 115 return enabled; 116 } 117 setSamples(int samples)118 public void setSamples(int samples){ 119 this.numSamples = samples; 120 } 121 setExposure(float exp)122 public void setExposure(float exp){ 123 this.exposure = exp; 124 } 125 setWhiteLevel(float whiteLevel)126 public void setWhiteLevel(float whiteLevel){ 127 this.whiteLevel = whiteLevel; 128 } 129 setMaxIterations(int maxIterations)130 public void setMaxIterations(int maxIterations){ 131 this.maxIterations = maxIterations; 132 133 // regenerate shaders if needed 134 if (hdr64 != null) 135 createLumShaders(); 136 } 137 setThrottle(float throttle)138 public void setThrottle(float throttle){ 139 this.throttle = throttle; 140 } 141 setUseFastFilter(boolean fastFilter)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 createDisplayQuad( )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 createLumShader(int srcW, int srcH, int bufW, int bufH, int mode, int iters, Texture tex)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 createLumShaders()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 opposite(int i)213 private int opposite(int i){ 214 return i == 1 ? 0 : 1; 215 } 216 renderProcessing(Renderer r, FrameBuffer dst, Material mat)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 renderToneMap(Renderer r, FrameBuffer out)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 updateAverageLuminance(Renderer r)245 private void updateAverageLuminance(Renderer r){ 246 renderProcessing(r, scene64FB, hdr64); 247 renderProcessing(r, scene8FB, hdr8); 248 renderProcessing(r, scene1FB[curSrc], hdr1); 249 } 250 isInitialized()251 public boolean isInitialized(){ 252 return viewPort != null; 253 } 254 reshape(ViewPort vp, int w, int h)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 initialize(RenderManager rm, ViewPort vp)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 preFrame(float tpf)334 public void preFrame(float tpf) { 335 if (!enabled) 336 return; 337 338 time += tpf; 339 blendFactor = (time / throttle); 340 } 341 postQueue(RenderQueue rq)342 public void postQueue(RenderQueue rq) { 343 } 344 postFrame(FrameBuffer out)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 cleanup()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