public void setFullscreen(boolean state) { if (renderingConfig.isFullscreen() != state) { renderingConfig.setFullscreen(state); DisplayDevice display = CoreRegistry.get(DisplayDevice.class); display.setFullscreen(state); } }
private void initConfig() { if (Files.isRegularFile(Config.getConfigFile())) { try { config = Config.load(Config.getConfigFile()); } catch (IOException e) { logger.error("Failed to load config", e); config = new Config(); } } else { config = new Config(); } if (!config.getDefaultModSelection().hasModule(TerasologyConstants.CORE_GAMEPLAY_MODULE)) { config.getDefaultModSelection().addModule(TerasologyConstants.CORE_GAMEPLAY_MODULE); } if (!validateServerIdentity()) { CertificateGenerator generator = new CertificateGenerator(); CertificatePair serverIdentity = generator.generateSelfSigned(); config .getSecurity() .setServerCredentials(serverIdentity.getPublicCert(), serverIdentity.getPrivateCert()); config.save(); } renderingConfig = config.getRendering(); logger.info("Video Settings: " + renderingConfig.toString()); CoreRegistry.putPermanently(Config.class, config); }
/** * If blur is enabled through the rendering settings, this method generates the images used by the * Blur effect when underwater and for the Depth of Field effect when above water. * * <p>For more information on blur: http://en.wikipedia.org/wiki/Defocus_aberration For more * information on DoF: http://en.wikipedia.org/wiki/Depth_of_field */ public void generateBlurPasses() { if (renderingConfig.getBlurIntensity() != 0) { PerformanceMonitor.startActivity("Generating Blur Passes"); generateBlur(buffers.sceneBlur0); generateBlur(buffers.sceneBlur1); PerformanceMonitor.endActivity(); } }
// TODO: verify if this can be achieved entirely in the GPU, during tone mapping perhaps? public void downsampleSceneAndUpdateExposure() { if (renderingConfig.isEyeAdaptation()) { PerformanceMonitor.startActivity("Updating exposure"); downsampleSceneInto1x1pixelsBuffer(); renderingProcess .getCurrentReadbackPBO() .copyFromFBO( buffers.downSampledScene[0].fboId, 1, 1, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE); renderingProcess.swapReadbackPBOs(); ByteBuffer pixels = renderingProcess.getCurrentReadbackPBO().readBackPixels(); if (pixels.limit() < 3) { logger.error("Failed to auto-update the exposure value."); return; } // TODO: make this line more readable by breaking it in smaller pieces currentSceneLuminance = 0.2126f * (pixels.get(2) & 0xFF) / 255.f + 0.7152f * (pixels.get(1) & 0xFF) / 255.f + 0.0722f * (pixels.get(0) & 0xFF) / 255.f; float targetExposure = hdrMaxExposure; if (currentSceneLuminance > 0) { targetExposure = hdrTargetLuminance / currentSceneLuminance; } float maxExposure = hdrMaxExposure; if (CoreRegistry.get(BackdropProvider.class).getDaylight() == 0.0) { // TODO: fetch the backdropProvider earlier and only once maxExposure = hdrMaxExposureNight; } if (targetExposure > maxExposure) { targetExposure = maxExposure; } else if (targetExposure < hdrMinExposure) { targetExposure = hdrMinExposure; } currentExposure = TeraMath.lerp(currentExposure, targetExposure, hdrExposureAdjustmentSpeed); } else { if (CoreRegistry.get(BackdropProvider.class).getDaylight() == 0.0) { currentExposure = hdrMaxExposureNight; } else { currentExposure = hdrExposureDefault; } } PerformanceMonitor.endActivity(); }
/** * If bloom is enabled via the rendering settings, this method generates the images needed for the * bloom shader effect and stores them in their own frame buffers. * * <p>This effects renders adds fringes (or "feathers") of light to areas of intense brightness. * This in turn give the impression of those areas partially overwhelming the camera or the eye. * * <p>For more information see: http://en.wikipedia.org/wiki/Bloom_(shader_effect) */ public void generateBloomPasses() { if (renderingConfig.isBloom()) { PerformanceMonitor.startActivity("Generating Bloom Passes"); generateHighPass(); generateBloom(buffers.sceneBloom0); generateBloom(buffers.sceneBloom1); generateBloom(buffers.sceneBloom2); PerformanceMonitor.endActivity(); } }
/** * Enabled by the "outline" option in the render settings, this method generates landscape/objects * outlines and stores them into a buffer in its own FBO. The stored image is eventually combined * with others. * * <p>The outlines visually separate a given object (including the landscape) or parts of it from * sufficiently distant objects it overlaps. It is effectively a depth-based edge detection * technique and internally uses a Sobel operator. * * <p>For further information see: http://en.wikipedia.org/wiki/Sobel_operator */ public void generateOutline() { if (renderingConfig.isOutline()) { materials.outline.enable(); // TODO: verify inputs: shouldn't there be a texture binding here? buffers.outline.bind(); setViewportTo(buffers.outline.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary } }
/** * If each is enabled through the rendering settings, this method adds depth-of-field blur, motion * blur and film grain to the rendering obtained so far. If OculusVR support is enabled, it * composes (over two calls) the images for each eye into a single image, and applies a distortion * pattern to each, to match the optics in the OculusVR headset. * * <p>Finally, it either sends the image to the display or, when taking a screenshot, instructs * the rendering process to save it to a file. // TODO: update this sentence when the * FrameBuffersManager becomes available. * * @param renderingStage Can be MONO, LEFT_EYE or RIGHT_EYE, and communicates to the method * weather it is dealing with a standard display or an OculusVR setup, and in the latter case, * which eye is currently being rendered. Notice that if the OculusVR support is enabled, the * image is sent to screen or saved to file only when the value passed in is RIGHT_EYE, as the * processing for the LEFT_EYE comes first and leads to an incomplete image. */ public void finalPostProcessing(WorldRenderer.WorldRenderingStage renderingStage) { PerformanceMonitor.startActivity("Rendering final scene"); if (!renderingDebugConfig.isEnabled()) { materials.finalPost.enable(); } else { materials.debug.enable(); } if (!renderingConfig.isOculusVrSupport()) { renderFinalMonoImage(); } else { renderFinalStereoImage(renderingStage); } PerformanceMonitor.endActivity(); }
/** Generates light shafts and stores them in their own FBO. */ public void generateLightShafts() { if (renderingConfig.isLightShafts()) { PerformanceMonitor.startActivity("Rendering light shafts"); materials.lightShafts.enable(); // TODO: verify what the inputs are buffers.lightShafts.bind(); setViewportTo(buffers.lightShafts.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary PerformanceMonitor.endActivity(); } }
private void generateBlur(FBO sceneBlur) { materials.blur.enable(); materials.blur.setFloat( "radius", overallBlurRadiusFactor * renderingConfig.getBlurRadius(), true); materials.blur.setFloat2( "texelSize", 1.0f / sceneBlur.width(), 1.0f / sceneBlur.height(), true); if (sceneBlur == buffers.sceneBlur0) { buffers.sceneToneMapped.bindTexture(); } else { buffers.sceneBlur0.bindTexture(); } sceneBlur.bind(); setViewportTo(sceneBlur.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderFullscreenQuad(); graphicState.bindDisplay(); setViewportToWholeDisplay(); }
// TODO: Future work should not only "think" in terms of a DAG-like rendering pipeline // TODO: but actually implement one, see https://github.com/MovingBlocks/Terasology/issues/1741 public class PostProcessor { private static final Logger logger = LoggerFactory.getLogger(PostProcessor.class); @EditorRange(min = 0.0f, max = 10.0f) private float hdrExposureDefault = 2.5f; @EditorRange(min = 0.0f, max = 10.0f) private float hdrMaxExposure = 8.0f; @EditorRange(min = 0.0f, max = 10.0f) private float hdrMaxExposureNight = 8.0f; @EditorRange(min = 0.0f, max = 10.0f) private float hdrMinExposure = 1.0f; @EditorRange(min = 0.0f, max = 4.0f) private float hdrTargetLuminance = 1.0f; @EditorRange(min = 0.0f, max = 0.5f) private float hdrExposureAdjustmentSpeed = 0.05f; @EditorRange(min = 0.0f, max = 5.0f) private float bloomHighPassThreshold = 0.75f; @EditorRange(min = 0.0f, max = 32.0f) private float bloomBlurRadius = 12.0f; @EditorRange(min = 0.0f, max = 16.0f) private float overallBlurRadiusFactor = 0.8f; private float currentExposure = 2.0f; private float currentSceneLuminance = 1.0f; private int displayListQuad = -1; private FBO.Dimensions fullScale; private LwjglRenderingProcess renderingProcess; private GraphicState graphicState; private Materials materials = new Materials(); private Buffers buffers = new Buffers(); private RenderingConfig renderingConfig = CoreRegistry.get(Config.class).getRendering(); private RenderingDebugConfig renderingDebugConfig = renderingConfig.getDebug(); /** * Returns a PostProcessor instance. On instantiation the returned instance is not yet usable. It * lacks references to Material assets and Frame Buffer Objects (FBOs) it needs to function. * * <p>Method initializeMaterials() must be called to initialize the Materials references. Method * obtainStaticFBOs() must be called to initialize unchanging FBOs references. Method * refreshDynamicFBOs() must be called at least once to initialize all other FBOs references. * * @param renderingProcess An LwjglRenderingProcess instance, required to obtain FBO references. * @param graphicState A GraphicState instance, providing opengl state-changing methods. */ // TODO: update javadoc when the rendering process becomes the FrameBuffersManager public PostProcessor(LwjglRenderingProcess renderingProcess, GraphicState graphicState) { this.renderingProcess = renderingProcess; this.graphicState = graphicState; } /** * Initializes the internal references to Materials assets. * * <p>Must be called at least once before the PostProcessor instance is in use. Failure to do so * will result in NullPointerExceptions. Calling it additional times shouldn't hurt but shouldn't * be necessary either: the asset system refreshes the assets behind the scenes if necessary. */ public void initializeMaterials() { // initial renderings materials.lightBufferPass = getMaterial("engine:prog.lightBufferPass"); // pre-post composite materials.outline = getMaterial("engine:prog.sobel"); materials.ssao = getMaterial("engine:prog.ssao"); materials.ssaoBlurred = getMaterial("engine:prog.ssaoBlur"); materials.prePostComposite = getMaterial("engine:prog.combine"); // initial post-processing materials.lightShafts = getMaterial("engine:prog.lightshaft"); // TODO: rename shader to lightShafts materials.initialPost = getMaterial("engine:prog.prePost"); // TODO: rename shader to initialPost materials.downSampler = getMaterial("engine:prog.down"); // TODO: rename shader to downSampler materials.highPass = getMaterial("engine:prog.highp"); // TODO: rename shader to highPass materials.blur = getMaterial("engine:prog.blur"); materials.toneMapping = getMaterial("engine:prog.hdr"); // TODO: rename shader to toneMapping // final post-processing materials.ocDistortion = getMaterial("engine:prog.ocDistortion"); materials.finalPost = getMaterial("engine:prog.post"); // TODO: rename shader to finalPost materials.debug = getMaterial("engine:prog.debug"); } private Material getMaterial(String assetId) { return Assets.getMaterial(assetId) .orElseThrow( () -> new RuntimeException("Failed to resolve required asset: '" + assetId + "'")); } /** * Fetches a number of static FBOs from the RenderingProcess instance and initializes a number of * internal references with them. They are called "static" as they do not change over the lifetime * of a PostProcessor instance. * * <p>This method must to be called at least once for the PostProcessor instance to function, but * does not need to be called additional times. * * <p>Failure to call this method -may- result in a NullPointerException. This is due to the * downsampleSceneAndUpdateExposure() method relying on these FBOs. But this method is fully * executed only if eye adaptation is enabled: an NPE would be thrown only in that case. */ // TODO: update javadoc when the rendering process becomes the FrameBuffersManager public void obtainStaticFBOs() { buffers.downSampledScene[4] = renderingProcess.getFBO("scene16"); buffers.downSampledScene[3] = renderingProcess.getFBO("scene8"); buffers.downSampledScene[2] = renderingProcess.getFBO("scene4"); buffers.downSampledScene[1] = renderingProcess.getFBO("scene2"); buffers.downSampledScene[0] = renderingProcess.getFBO("scene1"); } /** * Fetches a number of FBOs from the RenderingProcess instance and initializes or refreshes a * number of internal references with them. These FBOs may become obsolete over the lifetime of a * PostProcessor instance and refreshing the internal references might be needed. These FBOs are * therefore referred to as "dynamic" FBOs. * * <p>This method must be called at least once for the PostProcessor instance to function. Failure * to do so will result in NullPointerExceptions. It will then need to be called every time the * dynamic FBOs become obsolete and the internal references need to be refreshed with new FBOs. */ // TODO: update javadoc when the rendering process becomes the FrameBuffersManager public void refreshDynamicFBOs() { // initial renderings buffers.sceneOpaque = renderingProcess.getFBO("sceneOpaque"); buffers.sceneOpaquePingPong = renderingProcess.getFBO("sceneOpaquePingPong"); buffers.sceneSkyBand0 = renderingProcess.getFBO("sceneSkyBand0"); buffers.sceneSkyBand1 = renderingProcess.getFBO("sceneSkyBand1"); buffers.sceneReflectiveRefractive = renderingProcess.getFBO("sceneReflectiveRefractive"); // sceneReflected, in case one wonders, is not used by the post-processor. // pre-post composite buffers.outline = renderingProcess.getFBO("outline"); buffers.ssao = renderingProcess.getFBO("ssao"); buffers.ssaoBlurred = renderingProcess.getFBO("ssaoBlurred"); // initial post-processing buffers.lightShafts = renderingProcess.getFBO("lightShafts"); buffers.initialPost = renderingProcess.getFBO("initialPost"); buffers.currentReadbackPBO = renderingProcess.getCurrentReadbackPBO(); buffers.sceneToneMapped = renderingProcess.getFBO("sceneToneMapped"); buffers.sceneHighPass = renderingProcess.getFBO("sceneHighPass"); buffers.sceneBloom0 = renderingProcess.getFBO("sceneBloom0"); buffers.sceneBloom1 = renderingProcess.getFBO("sceneBloom1"); buffers.sceneBloom2 = renderingProcess.getFBO("sceneBloom2"); buffers.sceneBlur0 = renderingProcess.getFBO("sceneBlur0"); buffers.sceneBlur1 = renderingProcess.getFBO("sceneBlur1"); // final post-processing buffers.ocUndistorted = renderingProcess.getFBO("ocUndistorted"); buffers.sceneFinal = renderingProcess.getFBO("sceneFinal"); fullScale = buffers.sceneOpaque.dimensions(); } /** * In a number of occasions the rendering loop swaps two important FBOs. This method is used to * trigger the PostProcessor instance into refreshing the internal references to these FBOs. */ public void refreshSceneOpaqueFBOs() { buffers.sceneOpaque = renderingProcess.getFBO("sceneOpaque"); buffers.sceneOpaquePingPong = renderingProcess.getFBO("sceneOpaquePingPong"); } /** Disposes of the PostProcessor instance. */ // Not strictly necessary given the simplicity of the objects being nulled, // it is probably a good habit to have a dispose() method. It both properly // dispose support objects and it clearly marks the end of a PostProcessor // instance's lifecycle. public void dispose() { renderingProcess = null; graphicState = null; fullScale = null; } /** * Generates SkyBands and stores them into their specific FBOs if inscattering is enabled in the * rendering config. * * <p>SkyBands visually fade the far landscape and its entities into the color of the sky, * effectively constituting a form of depth cue. */ public void generateSkyBands() { if (renderingConfig.isInscattering()) { generateSkyBand(buffers.sceneSkyBand0); generateSkyBand(buffers.sceneSkyBand1); } } private void generateSkyBand(FBO skyBand) { materials.blur.enable(); materials.blur.setFloat("radius", 8.0f, true); materials.blur.setFloat2("texelSize", 1.0f / skyBand.width(), 1.0f / skyBand.height(), true); if (skyBand == buffers.sceneSkyBand0) { buffers.sceneOpaque.bindTexture(); } else { buffers.sceneSkyBand0.bindTexture(); } skyBand.bind(); graphicState.setRenderBufferMask(skyBand, true, false, false); setViewportTo(skyBand.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary } /** * Part of the deferred lighting technique, this method applies lighting through screen-space * calculations to the previously flat-lit world rendering stored in the primary FBO. // TODO: * rename sceneOpaque* FBOs to primaryA/B * * <p>See http://en.wikipedia.org/wiki/Deferred_shading as a starting point. */ public void applyLightBufferPass() { int texId = 0; GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId); buffers.sceneOpaque.bindTexture(); materials.lightBufferPass.setInt("texSceneOpaque", texId++); GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId); buffers.sceneOpaque.bindDepthTexture(); materials.lightBufferPass.setInt("texSceneOpaqueDepth", texId++); GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId); buffers.sceneOpaque.bindNormalsTexture(); materials.lightBufferPass.setInt("texSceneOpaqueNormals", texId++); GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId); buffers.sceneOpaque.bindLightBufferTexture(); materials.lightBufferPass.setInt("texSceneOpaqueLightBuffer", texId, true); buffers.sceneOpaquePingPong.bind(); graphicState.setRenderBufferMask(buffers.sceneOpaquePingPong, true, true, true); setViewportTo(buffers.sceneOpaquePingPong.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary renderingProcess.swapSceneOpaqueFBOs(); buffers.sceneOpaque.attachDepthBufferTo(buffers.sceneReflectiveRefractive); } /** * Enabled by the "outline" option in the render settings, this method generates landscape/objects * outlines and stores them into a buffer in its own FBO. The stored image is eventually combined * with others. * * <p>The outlines visually separate a given object (including the landscape) or parts of it from * sufficiently distant objects it overlaps. It is effectively a depth-based edge detection * technique and internally uses a Sobel operator. * * <p>For further information see: http://en.wikipedia.org/wiki/Sobel_operator */ public void generateOutline() { if (renderingConfig.isOutline()) { materials.outline.enable(); // TODO: verify inputs: shouldn't there be a texture binding here? buffers.outline.bind(); setViewportTo(buffers.outline.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary } } /** * If Ambient Occlusion is enabled in the render settings, this method generates and stores the * necessary images into their own FBOs. The stored images are eventually combined with others. * * <p>For further information on Ambient Occlusion see: * http://en.wikipedia.org/wiki/Ambient_occlusion */ public void generateAmbientOcclusionPasses() { if (renderingConfig.isSsao()) { generateSSAO(); generateBlurredSSAO(); } } private void generateSSAO() { materials.ssao.enable(); materials.ssao.setFloat2( "texelSize", 1.0f / buffers.ssao.width(), 1.0f / buffers.ssao.height(), true); materials.ssao.setFloat2("noiseTexelSize", 1.0f / 4.0f, 1.0f / 4.0f, true); // TODO: verify if some textures should be bound here buffers.ssao.bind(); setViewportTo(buffers.ssao.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary } private void generateBlurredSSAO() { materials.ssaoBlurred.enable(); materials.ssaoBlurred.setFloat2( "texelSize", 1.0f / buffers.ssaoBlurred.width(), 1.0f / buffers.ssaoBlurred.height(), true); buffers.ssao.bindTexture(); // TODO: verify this is the only input buffers.ssaoBlurred.bind(); setViewportTo(buffers.ssaoBlurred.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary } /** * Adds outlines and ambient occlusion to the rendering obtained so far stored in the primary FBO. * Stores the resulting output back into the primary buffer. */ public void generatePrePostComposite() { materials.prePostComposite.enable(); // TODO: verify if there should be bound textures here. buffers.sceneOpaquePingPong.bind(); setViewportTo(buffers.sceneOpaquePingPong.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary renderingProcess.swapSceneOpaqueFBOs(); buffers.sceneOpaque.attachDepthBufferTo(buffers.sceneReflectiveRefractive); } /** Generates light shafts and stores them in their own FBO. */ public void generateLightShafts() { if (renderingConfig.isLightShafts()) { PerformanceMonitor.startActivity("Rendering light shafts"); materials.lightShafts.enable(); // TODO: verify what the inputs are buffers.lightShafts.bind(); setViewportTo(buffers.lightShafts.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary PerformanceMonitor.endActivity(); } } /** * Adds chromatic aberration, light shafts, 1/8th resolution bloom, vignette onto the rendering * achieved so far. Stores the result into its own buffer to be used at a later stage. */ public void initialPostProcessing() { PerformanceMonitor.startActivity("Initial Post-Processing"); materials.initialPost.enable(); // TODO: verify what the inputs are buffers.initialPost.bind(); // TODO: see if we could write this straight into sceneOpaque setViewportTo(buffers.initialPost.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary PerformanceMonitor.endActivity(); } private void downsampleSceneInto1x1pixelsBuffer() { PerformanceMonitor.startActivity("Rendering eye adaption"); materials.downSampler.enable(); FBO downSampledFBO; for (int i = 4; i >= 0; i--) { downSampledFBO = buffers.downSampledScene[i]; materials.downSampler.setFloat("size", downSampledFBO.width(), true); downSampledFBO.bind(); setViewportTo(downSampledFBO.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: move this block above, for consistency if (i == 4) { buffers.initialPost.bindTexture(); } else { buffers.downSampledScene[i + 1].bindTexture(); } renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: probably can be removed or moved out of the loop } setViewportToWholeDisplay(); // TODO: verify this is necessary PerformanceMonitor.endActivity(); } /** * First downsamples the rendering obtained so far, after the initial post processing, into a 1x1 * pixel buffer. Then calculate its pixel's luma to update the exposure value. This is used later, * during tone mapping. */ // TODO: verify if this can be achieved entirely in the GPU, during tone mapping perhaps? public void downsampleSceneAndUpdateExposure() { if (renderingConfig.isEyeAdaptation()) { PerformanceMonitor.startActivity("Updating exposure"); downsampleSceneInto1x1pixelsBuffer(); renderingProcess .getCurrentReadbackPBO() .copyFromFBO( buffers.downSampledScene[0].fboId, 1, 1, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE); renderingProcess.swapReadbackPBOs(); ByteBuffer pixels = renderingProcess.getCurrentReadbackPBO().readBackPixels(); if (pixels.limit() < 3) { logger.error("Failed to auto-update the exposure value."); return; } // TODO: make this line more readable by breaking it in smaller pieces currentSceneLuminance = 0.2126f * (pixels.get(2) & 0xFF) / 255.f + 0.7152f * (pixels.get(1) & 0xFF) / 255.f + 0.0722f * (pixels.get(0) & 0xFF) / 255.f; float targetExposure = hdrMaxExposure; if (currentSceneLuminance > 0) { targetExposure = hdrTargetLuminance / currentSceneLuminance; } float maxExposure = hdrMaxExposure; if (CoreRegistry.get(BackdropProvider.class).getDaylight() == 0.0) { // TODO: fetch the backdropProvider earlier and only once maxExposure = hdrMaxExposureNight; } if (targetExposure > maxExposure) { targetExposure = maxExposure; } else if (targetExposure < hdrMinExposure) { targetExposure = hdrMinExposure; } currentExposure = TeraMath.lerp(currentExposure, targetExposure, hdrExposureAdjustmentSpeed); } else { if (CoreRegistry.get(BackdropProvider.class).getDaylight() == 0.0) { currentExposure = hdrMaxExposureNight; } else { currentExposure = hdrExposureDefault; } } PerformanceMonitor.endActivity(); } /** // TODO: write javadoc */ // TODO: Tone mapping usually maps colors from HDR to a more limited range, // TODO: i.e. the 24 bit a monitor can display. This method however maps from an HDR buffer // TODO: to another HDR buffer and this puzzles me. Will need to dig deep in the shader to // TODO: see what it does. public void generateToneMappedScene() { PerformanceMonitor.startActivity("Tone mapping"); materials.toneMapping.enable(); buffers.sceneToneMapped.bind(); setViewportTo(buffers.sceneToneMapped.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary PerformanceMonitor.endActivity(); } /** * If bloom is enabled via the rendering settings, this method generates the images needed for the * bloom shader effect and stores them in their own frame buffers. * * <p>This effects renders adds fringes (or "feathers") of light to areas of intense brightness. * This in turn give the impression of those areas partially overwhelming the camera or the eye. * * <p>For more information see: http://en.wikipedia.org/wiki/Bloom_(shader_effect) */ public void generateBloomPasses() { if (renderingConfig.isBloom()) { PerformanceMonitor.startActivity("Generating Bloom Passes"); generateHighPass(); generateBloom(buffers.sceneBloom0); generateBloom(buffers.sceneBloom1); generateBloom(buffers.sceneBloom2); PerformanceMonitor.endActivity(); } } private void generateHighPass() { materials.highPass.enable(); materials.highPass.setFloat("highPassThreshold", bloomHighPassThreshold, true); int texId = 0; GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId); buffers.sceneOpaque.bindTexture(); materials.highPass.setInt("tex", texId); // GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId); // buffers.sceneOpaque.bindDepthTexture(); // program.setInt("texDepth", texId++); buffers.sceneHighPass.bind(); setViewportTo(buffers.sceneHighPass.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderFullscreenQuad(); graphicState.bindDisplay(); setViewportToWholeDisplay(); } private void generateBloom(FBO sceneBloom) { materials.blur.enable(); materials.blur.setFloat("radius", bloomBlurRadius, true); materials.blur.setFloat2( "texelSize", 1.0f / sceneBloom.width(), 1.0f / sceneBloom.height(), true); if (sceneBloom == buffers.sceneBloom0) { buffers.sceneHighPass.bindTexture(); } else if (sceneBloom == buffers.sceneBloom1) { buffers.sceneBloom0.bindTexture(); } else { buffers.sceneBloom1.bindTexture(); } sceneBloom.bind(); setViewportTo(sceneBloom.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // TODO: verify this is necessary renderFullscreenQuad(); graphicState.bindDisplay(); // TODO: verify this is necessary setViewportToWholeDisplay(); // TODO: verify this is necessary } /** * If blur is enabled through the rendering settings, this method generates the images used by the * Blur effect when underwater and for the Depth of Field effect when above water. * * <p>For more information on blur: http://en.wikipedia.org/wiki/Defocus_aberration For more * information on DoF: http://en.wikipedia.org/wiki/Depth_of_field */ public void generateBlurPasses() { if (renderingConfig.getBlurIntensity() != 0) { PerformanceMonitor.startActivity("Generating Blur Passes"); generateBlur(buffers.sceneBlur0); generateBlur(buffers.sceneBlur1); PerformanceMonitor.endActivity(); } } private void generateBlur(FBO sceneBlur) { materials.blur.enable(); materials.blur.setFloat( "radius", overallBlurRadiusFactor * renderingConfig.getBlurRadius(), true); materials.blur.setFloat2( "texelSize", 1.0f / sceneBlur.width(), 1.0f / sceneBlur.height(), true); if (sceneBlur == buffers.sceneBlur0) { buffers.sceneToneMapped.bindTexture(); } else { buffers.sceneBlur0.bindTexture(); } sceneBlur.bind(); setViewportTo(sceneBlur.dimensions()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderFullscreenQuad(); graphicState.bindDisplay(); setViewportToWholeDisplay(); } // Final Post-Processing: depth-of-field blur, motion blur, film grain, grading, OculusVR // distortion /** * If each is enabled through the rendering settings, this method adds depth-of-field blur, motion * blur and film grain to the rendering obtained so far. If OculusVR support is enabled, it * composes (over two calls) the images for each eye into a single image, and applies a distortion * pattern to each, to match the optics in the OculusVR headset. * * <p>Finally, it either sends the image to the display or, when taking a screenshot, instructs * the rendering process to save it to a file. // TODO: update this sentence when the * FrameBuffersManager becomes available. * * @param renderingStage Can be MONO, LEFT_EYE or RIGHT_EYE, and communicates to the method * weather it is dealing with a standard display or an OculusVR setup, and in the latter case, * which eye is currently being rendered. Notice that if the OculusVR support is enabled, the * image is sent to screen or saved to file only when the value passed in is RIGHT_EYE, as the * processing for the LEFT_EYE comes first and leads to an incomplete image. */ public void finalPostProcessing(WorldRenderer.WorldRenderingStage renderingStage) { PerformanceMonitor.startActivity("Rendering final scene"); if (!renderingDebugConfig.isEnabled()) { materials.finalPost.enable(); } else { materials.debug.enable(); } if (!renderingConfig.isOculusVrSupport()) { renderFinalMonoImage(); } else { renderFinalStereoImage(renderingStage); } PerformanceMonitor.endActivity(); } private void renderFinalMonoImage() { if (renderingProcess.isNotTakingScreenshot()) { graphicState.bindDisplay(); renderFullscreenQuad(0, 0, Display.getWidth(), Display.getHeight()); } else { buffers.sceneFinal.bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderFullscreenQuad(0, 0, fullScale.width(), fullScale.height()); renderingProcess.saveScreenshot(); // when saving a screenshot we do not send the image to screen, // to avoid the brief one-frame flicker of the screenshot // This is needed to avoid the UI (which is not currently saved within the // screenshot) being rendered for one frame with buffers.sceneFinal size. setViewportToWholeDisplay(); } } // TODO: have a flag to invert the eyes (Cross Eye 3D), as mentioned in // TODO: http://forum.terasology.org/threads/happy-coding.1018/#post-11264 private void renderFinalStereoImage(WorldRenderer.WorldRenderingStage renderingStage) { if (renderingProcess.isNotTakingScreenshot()) { buffers.sceneFinal.bind(); } else { buffers.ocUndistorted.bind(); } switch (renderingStage) { case LEFT_EYE: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderFullscreenQuad(0, 0, fullScale.width() / 2, fullScale.height()); break; case RIGHT_EYE: // no glClear() here: the rendering for the second eye is being added besides the first // eye's rendering renderFullscreenQuad( fullScale.width() / 2 + 1, 0, fullScale.width() / 2, fullScale.height()); if (renderingProcess.isNotTakingScreenshot()) { graphicState.bindDisplay(); applyOculusDistortion(buffers.sceneFinal); } else { buffers.sceneFinal.bind(); applyOculusDistortion(buffers.ocUndistorted); renderingProcess.saveScreenshot(); // when saving a screenshot we do NOT send the image to screen, // to avoid the brief flicker of the screenshot for one frame } break; } } private void applyOculusDistortion(FBO inputBuffer) { materials.ocDistortion.enable(); int texId = 0; GL13.glActiveTexture(GL13.GL_TEXTURE0 + texId); inputBuffer.bindTexture(); materials.ocDistortion.setInt("texInputBuffer", texId, true); if (renderingProcess.isNotTakingScreenshot()) { updateOcShaderParametersForVP( 0, 0, fullScale.width() / 2, fullScale.height(), WorldRenderer.WorldRenderingStage.LEFT_EYE); renderFullscreenQuad(0, 0, Display.getWidth(), Display.getHeight()); updateOcShaderParametersForVP( fullScale.width() / 2 + 1, 0, fullScale.width() / 2, fullScale.height(), WorldRenderer.WorldRenderingStage.RIGHT_EYE); renderFullscreenQuad(0, 0, Display.getWidth(), Display.getHeight()); } else { // what follows -should- work also when there is no screenshot being taken, but somehow it // doesn't, hence the block above updateOcShaderParametersForVP( 0, 0, fullScale.width() / 2, fullScale.height(), WorldRenderer.WorldRenderingStage.LEFT_EYE); renderFullscreenQuad(0, 0, fullScale.width(), fullScale.height()); updateOcShaderParametersForVP( fullScale.width() / 2 + 1, 0, fullScale.width() / 2, fullScale.height(), WorldRenderer.WorldRenderingStage.RIGHT_EYE); renderFullscreenQuad(0, 0, fullScale.width(), fullScale.height()); } } private void updateOcShaderParametersForVP( int vpX, int vpY, int vpWidth, int vpHeight, WorldRenderer.WorldRenderingStage renderingStage) { float w = (float) vpWidth / fullScale.width(); float h = (float) vpHeight / fullScale.height(); float x = (float) vpX / fullScale.width(); float y = (float) vpY / fullScale.height(); float as = (float) vpWidth / vpHeight; materials.ocDistortion.setFloat4( "ocHmdWarpParam", OculusVrHelper.getDistortionParams()[0], OculusVrHelper.getDistortionParams()[1], OculusVrHelper.getDistortionParams()[2], OculusVrHelper.getDistortionParams()[3], true); float ocLensCenter = (renderingStage == WorldRenderer.WorldRenderingStage.RIGHT_EYE) ? -1.0f * OculusVrHelper.getLensViewportShift() : OculusVrHelper.getLensViewportShift(); materials.ocDistortion.setFloat2( "ocLensCenter", x + (w + ocLensCenter * 0.5f) * 0.5f, y + h * 0.5f, true); materials.ocDistortion.setFloat2("ocScreenCenter", x + w * 0.5f, y + h * 0.5f, true); float scaleFactor = 1.0f / OculusVrHelper.getScaleFactor(); materials.ocDistortion.setFloat2( "ocScale", (w / 2) * scaleFactor, (h / 2) * scaleFactor * as, true); materials.ocDistortion.setFloat2("ocScaleIn", (2 / w), (2 / h) / as, true); } /** Renders a quad filling the whole currently set viewport. */ public void renderFullscreenQuad() { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); renderQuad(); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } /** * First sets a viewport and then renders a quad filling it. * * @param x an integer representing the x coordinate (in pixels) of the origin of the viewport. * @param y an integer representing the y coordinate (in pixels) of the origin of the viewport. * @param viewportWidth an integer representing the width (in pixels) the viewport. * @param viewportHeight an integer representing the height (in pixels) the viewport. */ // TODO: perhaps remove this method and make sure the viewport is set explicitely. public void renderFullscreenQuad(int x, int y, int viewportWidth, int viewportHeight) { glViewport(x, y, viewportWidth, viewportHeight); renderFullscreenQuad(); } // TODO: replace with a proper resident buffer with interleaved vertex and uv coordinates private void renderQuad() { if (displayListQuad == -1) { displayListQuad = glGenLists(1); glNewList(displayListQuad, GL11.GL_COMPILE); glBegin(GL_QUADS); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glTexCoord2d(0.0, 0.0); glVertex3i(-1, -1, -1); glTexCoord2d(1.0, 0.0); glVertex3i(1, -1, -1); glTexCoord2d(1.0, 1.0); glVertex3i(1, 1, -1); glTexCoord2d(0.0, 1.0); glVertex3i(-1, 1, -1); glEnd(); glEndList(); } glCallList(displayListQuad); } private void setViewportToWholeDisplay() { glViewport(0, 0, fullScale.width(), fullScale.height()); } private void setViewportTo(FBO.Dimensions dimensions) { glViewport(0, 0, dimensions.width(), dimensions.height()); } /** * Returns the current exposure value (set in downsampleSceneAndUpdateExposure()). * * @return a float representing the current exposure value. */ public float getExposure() { return currentExposure; } private class Materials { // initial renderings public Material lightBufferPass; // pre-post composite public Material outline; public Material ssao; public Material ssaoBlurred; public Material prePostComposite; // initial post-processing public Material lightShafts; public Material downSampler; public Material highPass; public Material blur; public Material toneMapping; public Material initialPost; // final post-processing public Material ocDistortion; public Material finalPost; public Material debug; } private class Buffers { // initial renderings public FBO sceneOpaque; public FBO sceneOpaquePingPong; public FBO sceneSkyBand0; public FBO sceneSkyBand1; public FBO sceneReflectiveRefractive; // sceneReflected is not used by the postProcessor // pre-post composite public FBO outline; public FBO ssao; public FBO ssaoBlurred; public FBO initialPost; // initial post-processing public FBO lightShafts; public FBO[] downSampledScene = new FBO[5]; public PBO currentReadbackPBO; public FBO sceneToneMapped; public FBO sceneHighPass; public FBO sceneBloom0; public FBO sceneBloom1; public FBO sceneBloom2; public FBO sceneBlur0; public FBO sceneBlur1; // final post-processing public FBO ocUndistorted; public FBO sceneFinal; } }
/** * If Ambient Occlusion is enabled in the render settings, this method generates and stores the * necessary images into their own FBOs. The stored images are eventually combined with others. * * <p>For further information on Ambient Occlusion see: * http://en.wikipedia.org/wiki/Ambient_occlusion */ public void generateAmbientOcclusionPasses() { if (renderingConfig.isSsao()) { generateSSAO(); generateBlurredSSAO(); } }
/** * Generates SkyBands and stores them into their specific FBOs if inscattering is enabled in the * rendering config. * * <p>SkyBands visually fade the far landscape and its entities into the color of the sky, * effectively constituting a form of depth cue. */ public void generateSkyBands() { if (renderingConfig.isInscattering()) { generateSkyBand(buffers.sceneSkyBand0); generateSkyBand(buffers.sceneSkyBand1); } }
public boolean isFullscreen() { return renderingConfig.isFullscreen(); }