• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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