Home | History | Annotate | Download | only in post
      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