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 package com.jme3.post; 33 34 import com.jme3.asset.AssetManager; 35 import com.jme3.export.*; 36 import com.jme3.material.Material; 37 import com.jme3.renderer.*; 38 import com.jme3.renderer.queue.RenderQueue; 39 import com.jme3.texture.FrameBuffer; 40 import com.jme3.texture.Image.Format; 41 import com.jme3.texture.Texture2D; 42 import com.jme3.ui.Picture; 43 import java.io.IOException; 44 import java.util.ArrayList; 45 import java.util.Collection; 46 import java.util.Iterator; 47 import java.util.List; 48 49 /** 50 * A FilterPostProcessor is a processor that can apply several {@link Filter}s to a rendered scene<br> 51 * It manages a list of filters that will be applied in the order in which they've been added to the list 52 * @author Rémy Bouquet aka Nehon 53 */ 54 public class FilterPostProcessor implements SceneProcessor, Savable { 55 56 private RenderManager renderManager; 57 private Renderer renderer; 58 private ViewPort viewPort; 59 private FrameBuffer renderFrameBufferMS; 60 private int numSamples = 1; 61 private FrameBuffer renderFrameBuffer; 62 private Texture2D filterTexture; 63 private Texture2D depthTexture; 64 private List<Filter> filters = new ArrayList<Filter>(); 65 private AssetManager assetManager; 66 private Camera filterCam = new Camera(1, 1); 67 private Picture fsQuad; 68 private boolean computeDepth = false; 69 private FrameBuffer outputBuffer; 70 private int width; 71 private int height; 72 private float bottom; 73 private float left; 74 private float right; 75 private float top; 76 private int originalWidth; 77 private int originalHeight; 78 private int lastFilterIndex = -1; 79 private boolean cameraInit = false; 80 81 /** 82 * Create a FilterProcessor 83 * @param assetManager the assetManager 84 */ FilterPostProcessor(AssetManager assetManager)85 public FilterPostProcessor(AssetManager assetManager) { 86 this.assetManager = assetManager; 87 } 88 89 /** 90 * Don't use this constructor use {@link FilterPostProcessor(AssetManager assetManager)}<br> 91 * This constructor is used for serialization only 92 */ FilterPostProcessor()93 public FilterPostProcessor() { 94 } 95 96 /** 97 * Adds a filter to the filters list<br> 98 * @param filter the filter to add 99 */ addFilter(Filter filter)100 public void addFilter(Filter filter) { 101 filters.add(filter); 102 103 if (isInitialized()) { 104 initFilter(filter, viewPort); 105 } 106 107 setFilterState(filter, filter.isEnabled()); 108 109 } 110 111 /** 112 * removes this filters from the filters list 113 * @param filter 114 */ removeFilter(Filter filter)115 public void removeFilter(Filter filter) { 116 filters.remove(filter); 117 filter.cleanup(renderer); 118 updateLastFilterIndex(); 119 } 120 getFilterIterator()121 public Iterator<Filter> getFilterIterator() { 122 return filters.iterator(); 123 } 124 initialize(RenderManager rm, ViewPort vp)125 public void initialize(RenderManager rm, ViewPort vp) { 126 renderManager = rm; 127 renderer = rm.getRenderer(); 128 viewPort = vp; 129 fsQuad = new Picture("filter full screen quad"); 130 131 Camera cam = vp.getCamera(); 132 133 //save view port diensions 134 left = cam.getViewPortLeft(); 135 right = cam.getViewPortRight(); 136 top = cam.getViewPortTop(); 137 bottom = cam.getViewPortBottom(); 138 originalWidth = cam.getWidth(); 139 originalHeight = cam.getHeight(); 140 //first call to reshape 141 reshape(vp, cam.getWidth(), cam.getHeight()); 142 } 143 144 /** 145 * init the given filter 146 * @param filter 147 * @param vp 148 */ initFilter(Filter filter, ViewPort vp)149 private void initFilter(Filter filter, ViewPort vp) { 150 filter.setProcessor(this); 151 if (filter.isRequiresDepthTexture()) { 152 if (!computeDepth && renderFrameBuffer != null) { 153 depthTexture = new Texture2D(width, height, Format.Depth24); 154 renderFrameBuffer.setDepthTexture(depthTexture); 155 } 156 computeDepth = true; 157 filter.init(assetManager, renderManager, vp, width, height); 158 filter.setDepthTexture(depthTexture); 159 } else { 160 filter.init(assetManager, renderManager, vp, width, height); 161 } 162 } 163 164 /** 165 * renders a filter on a fullscreen quad 166 * @param r 167 * @param buff 168 * @param mat 169 */ renderProcessing(Renderer r, FrameBuffer buff, Material mat)170 private void renderProcessing(Renderer r, FrameBuffer buff, Material mat) { 171 if (buff == outputBuffer) { 172 fsQuad.setWidth(width); 173 fsQuad.setHeight(height); 174 filterCam.resize(originalWidth, originalHeight, true); 175 fsQuad.setPosition(left * originalWidth, bottom * originalHeight); 176 } else { 177 fsQuad.setWidth(buff.getWidth()); 178 fsQuad.setHeight(buff.getHeight()); 179 filterCam.resize(buff.getWidth(), buff.getHeight(), true); 180 fsQuad.setPosition(0, 0); 181 } 182 183 if (mat.getAdditionalRenderState().isDepthWrite()) { 184 mat.getAdditionalRenderState().setDepthTest(false); 185 mat.getAdditionalRenderState().setDepthWrite(false); 186 } 187 188 fsQuad.setMaterial(mat); 189 fsQuad.updateGeometricState(); 190 191 renderManager.setCamera(filterCam, true); 192 r.setFrameBuffer(buff); 193 r.clearBuffers(false, true, true); 194 renderManager.renderGeometry(fsQuad); 195 196 } 197 isInitialized()198 public boolean isInitialized() { 199 return viewPort != null; 200 } 201 postQueue(RenderQueue rq)202 public void postQueue(RenderQueue rq) { 203 204 for (Iterator<Filter> it = filters.iterator(); it.hasNext();) { 205 Filter filter = it.next(); 206 if (filter.isEnabled()) { 207 filter.postQueue(renderManager, viewPort); 208 } 209 } 210 211 } 212 Picture pic = new Picture("debug"); 213 214 /** 215 * iterate through the filter list and renders filters 216 * @param r 217 * @param sceneFb 218 */ renderFilterChain(Renderer r, FrameBuffer sceneFb)219 private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { 220 Texture2D tex = filterTexture; 221 FrameBuffer buff = sceneFb; 222 boolean msDepth = depthTexture != null && depthTexture.getImage().getMultiSamples() > 1; 223 for (int i = 0; i < filters.size(); i++) { 224 Filter filter = filters.get(i); 225 if (filter.isEnabled()) { 226 if (filter.getPostRenderPasses() != null) { 227 for (Iterator<Filter.Pass> it1 = filter.getPostRenderPasses().iterator(); it1.hasNext();) { 228 Filter.Pass pass = it1.next(); 229 pass.beforeRender(); 230 if (pass.requiresSceneAsTexture()) { 231 pass.getPassMaterial().setTexture("Texture", tex); 232 if (tex.getImage().getMultiSamples() > 1) { 233 pass.getPassMaterial().setInt("NumSamples", tex.getImage().getMultiSamples()); 234 } else { 235 pass.getPassMaterial().clearParam("NumSamples"); 236 237 } 238 } 239 if (pass.requiresDepthAsTexture()) { 240 pass.getPassMaterial().setTexture("DepthTexture", depthTexture); 241 if (msDepth) { 242 pass.getPassMaterial().setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples()); 243 } else { 244 pass.getPassMaterial().clearParam("NumSamplesDepth"); 245 } 246 } 247 renderProcessing(r, pass.getRenderFrameBuffer(), pass.getPassMaterial()); 248 } 249 } 250 251 filter.postFrame(renderManager, viewPort, buff, sceneFb); 252 253 Material mat = filter.getMaterial(); 254 if (msDepth && filter.isRequiresDepthTexture()) { 255 mat.setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples()); 256 } 257 258 if (filter.isRequiresSceneTexture()) { 259 mat.setTexture("Texture", tex); 260 if (tex.getImage().getMultiSamples() > 1) { 261 mat.setInt("NumSamples", tex.getImage().getMultiSamples()); 262 } else { 263 mat.clearParam("NumSamples"); 264 } 265 } 266 267 buff = outputBuffer; 268 if (i != lastFilterIndex) { 269 buff = filter.getRenderFrameBuffer(); 270 tex = filter.getRenderedTexture(); 271 272 } 273 renderProcessing(r, buff, mat); 274 } 275 } 276 } 277 postFrame(FrameBuffer out)278 public void postFrame(FrameBuffer out) { 279 280 FrameBuffer sceneBuffer = renderFrameBuffer; 281 if (renderFrameBufferMS != null && !renderer.getCaps().contains(Caps.OpenGL31)) { 282 renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer); 283 } else if (renderFrameBufferMS != null) { 284 sceneBuffer = renderFrameBufferMS; 285 } 286 renderFilterChain(renderer, sceneBuffer); 287 renderer.setFrameBuffer(outputBuffer); 288 289 //viewport can be null if no filters are enabled 290 if (viewPort != null) { 291 renderManager.setCamera(viewPort.getCamera(), false); 292 } 293 294 } 295 preFrame(float tpf)296 public void preFrame(float tpf) { 297 if (filters.isEmpty() || lastFilterIndex == -1) { 298 //If the camera is initialized and there are no filter to render, the camera viewport is restored as it was 299 if (cameraInit) { 300 viewPort.getCamera().resize(originalWidth, originalHeight, true); 301 viewPort.getCamera().setViewPort(left, right, bottom, top); 302 viewPort.setOutputFrameBuffer(outputBuffer); 303 cameraInit = false; 304 } 305 306 } else { 307 if (renderFrameBufferMS != null) { 308 viewPort.setOutputFrameBuffer(renderFrameBufferMS); 309 } else { 310 viewPort.setOutputFrameBuffer(renderFrameBuffer); 311 } 312 //init of the camera if it wasn't already 313 if (!cameraInit) { 314 viewPort.getCamera().resize(width, height, true); 315 viewPort.getCamera().setViewPort(0, 1, 0, 1); 316 } 317 } 318 319 for (Iterator<Filter> it = filters.iterator(); it.hasNext();) { 320 Filter filter = it.next(); 321 if (filter.isEnabled()) { 322 filter.preFrame(tpf); 323 } 324 } 325 326 } 327 328 /** 329 * sets the filter to enabled or disabled 330 * @param filter 331 * @param enabled 332 */ setFilterState(Filter filter, boolean enabled)333 protected void setFilterState(Filter filter, boolean enabled) { 334 if (filters.contains(filter)) { 335 filter.enabled = enabled; 336 updateLastFilterIndex(); 337 } 338 } 339 340 /** 341 * compute the index of the last filter to render 342 */ updateLastFilterIndex()343 private void updateLastFilterIndex() { 344 lastFilterIndex = -1; 345 for (int i = filters.size() - 1; i >= 0 && lastFilterIndex == -1; i--) { 346 if (filters.get(i).isEnabled()) { 347 lastFilterIndex = i; 348 return; 349 } 350 } 351 if (lastFilterIndex == -1) { 352 cleanup(); 353 } 354 } 355 cleanup()356 public void cleanup() { 357 if (viewPort != null) { 358 //reseting the viewport camera viewport to its initial value 359 viewPort.getCamera().resize(originalWidth, originalHeight, true); 360 viewPort.getCamera().setViewPort(left, right, bottom, top); 361 viewPort.setOutputFrameBuffer(outputBuffer); 362 viewPort = null; 363 for (Filter filter : filters) { 364 filter.cleanup(renderer); 365 } 366 } 367 368 } 369 reshape(ViewPort vp, int w, int h)370 public void reshape(ViewPort vp, int w, int h) { 371 //this has no effect at first init but is useful when resizing the canvas with multi views 372 Camera cam = vp.getCamera(); 373 cam.setViewPort(left, right, bottom, top); 374 //resizing the camera to fit the new viewport and saving original dimensions 375 cam.resize(w, h, false); 376 left = cam.getViewPortLeft(); 377 right = cam.getViewPortRight(); 378 top = cam.getViewPortTop(); 379 bottom = cam.getViewPortBottom(); 380 originalWidth = w; 381 originalHeight = h; 382 cam.setViewPort(0, 1, 0, 1); 383 384 //computing real dimension of the viewport and resizing he camera 385 width = (int) (w * (Math.abs(right - left))); 386 height = (int) (h * (Math.abs(bottom - top))); 387 width = Math.max(1, width); 388 height = Math.max(1, height); 389 cam.resize(width, height, false); 390 cameraInit = true; 391 computeDepth = false; 392 393 if (renderFrameBuffer == null) { 394 outputBuffer = viewPort.getOutputFrameBuffer(); 395 } 396 397 Collection<Caps> caps = renderer.getCaps(); 398 399 //antialiasing on filters only supported in opengl 3 due to depth read problem 400 if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)) { 401 renderFrameBufferMS = new FrameBuffer(width, height, numSamples); 402 if (caps.contains(Caps.OpenGL31)) { 403 Texture2D msColor = new Texture2D(width, height, numSamples, Format.RGBA8); 404 Texture2D msDepth = new Texture2D(width, height, numSamples, Format.Depth); 405 renderFrameBufferMS.setDepthTexture(msDepth); 406 renderFrameBufferMS.setColorTexture(msColor); 407 filterTexture = msColor; 408 depthTexture = msDepth; 409 } else { 410 renderFrameBufferMS.setDepthBuffer(Format.Depth); 411 renderFrameBufferMS.setColorBuffer(Format.RGBA8); 412 } 413 } 414 415 if (numSamples <= 1 || !caps.contains(Caps.OpenGL31)) { 416 renderFrameBuffer = new FrameBuffer(width, height, 1); 417 renderFrameBuffer.setDepthBuffer(Format.Depth); 418 filterTexture = new Texture2D(width, height, Format.RGBA8); 419 renderFrameBuffer.setColorTexture(filterTexture); 420 } 421 422 for (Iterator<Filter> it = filters.iterator(); it.hasNext();) { 423 Filter filter = it.next(); 424 initFilter(filter, vp); 425 } 426 427 if (renderFrameBufferMS != null) { 428 viewPort.setOutputFrameBuffer(renderFrameBufferMS); 429 } else { 430 viewPort.setOutputFrameBuffer(renderFrameBuffer); 431 } 432 } 433 434 /** 435 * return the number of samples for antialiasing 436 * @return numSamples 437 */ getNumSamples()438 public int getNumSamples() { 439 return numSamples; 440 } 441 442 /** 443 * 444 * Removes all the filters from this processor 445 */ removeAllFilters()446 public void removeAllFilters() { 447 filters.clear(); 448 updateLastFilterIndex(); 449 } 450 451 /** 452 * Sets the number of samples for antialiasing 453 * @param numSamples the number of Samples 454 */ setNumSamples(int numSamples)455 public void setNumSamples(int numSamples) { 456 if (numSamples <= 0) { 457 throw new IllegalArgumentException("numSamples must be > 0"); 458 } 459 460 this.numSamples = numSamples; 461 } 462 463 /** 464 * Sets the asset manager for this processor 465 * @param assetManager 466 */ setAssetManager(AssetManager assetManager)467 public void setAssetManager(AssetManager assetManager) { 468 this.assetManager = assetManager; 469 } 470 write(JmeExporter ex)471 public void write(JmeExporter ex) throws IOException { 472 OutputCapsule oc = ex.getCapsule(this); 473 oc.write(numSamples, "numSamples", 0); 474 oc.writeSavableArrayList((ArrayList) filters, "filters", null); 475 } 476 read(JmeImporter im)477 public void read(JmeImporter im) throws IOException { 478 InputCapsule ic = im.getCapsule(this); 479 numSamples = ic.readInt("numSamples", 0); 480 filters = ic.readSavableArrayList("filters", null); 481 for (Filter filter : filters) { 482 filter.setProcessor(this); 483 setFilterState(filter, filter.isEnabled()); 484 } 485 assetManager = im.getAssetManager(); 486 } 487 488 /** 489 * For internal use only<br> 490 * returns the depth texture of the scene 491 * @return 492 */ getDepthTexture()493 public Texture2D getDepthTexture() { 494 return depthTexture; 495 } 496 497 /** 498 * For internal use only<br> 499 * returns the rendered texture of the scene 500 * @return 501 */ getFilterTexture()502 public Texture2D getFilterTexture() { 503 return filterTexture; 504 } 505 } 506