/*
* Copyright 2017 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.world;
import org.terasology.rendering.dag.nodes.AmbientOcclusionNode;
import org.terasology.rendering.dag.nodes.ApplyDeferredLightingNode;
import org.terasology.rendering.dag.nodes.BlurredAmbientOcclusionNode;
import org.terasology.rendering.dag.nodes.CopyImageToScreenNode;
import org.terasology.rendering.dag.nodes.DeferredMainLightNode;
import org.terasology.rendering.dag.nodes.DownSamplerForExposureNode;
import org.terasology.rendering.dag.nodes.HighPassNode;
import org.terasology.rendering.dag.nodes.LateBlurNode;
import org.terasology.rendering.dag.nodes.UpdateExposureNode;
import org.terasology.rendering.openvrprovider.OpenVRProvider;
import org.terasology.assets.ResourceUrn;
import org.terasology.config.Config;
import org.terasology.config.RenderingConfig;
import org.terasology.context.Context;
import org.terasology.engine.subsystem.lwjgl.GLBufferPool;
import org.terasology.engine.subsystem.lwjgl.LwjglGraphics;
import org.terasology.logic.players.LocalPlayerSystem;
import org.terasology.math.TeraMath;
import org.terasology.math.geom.Vector3f;
import org.terasology.math.geom.Vector3i;
import org.terasology.rendering.ShaderManager;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.backdrop.BackdropProvider;
import org.terasology.rendering.cameras.Camera;
import org.terasology.rendering.cameras.OpenVRStereoCamera;
import org.terasology.rendering.cameras.PerspectiveCamera;
import org.terasology.rendering.cameras.SubmersibleCamera;
import org.terasology.rendering.dag.Node;
import org.terasology.rendering.dag.NodeFactory;
import org.terasology.rendering.dag.RenderGraph;
import org.terasology.rendering.dag.RenderPipelineTask;
import org.terasology.rendering.dag.RenderTaskListGenerator;
import org.terasology.rendering.dag.nodes.BackdropNode;
import org.terasology.rendering.dag.nodes.BloomBlurNode;
import org.terasology.rendering.dag.nodes.BufferClearingNode;
import org.terasology.rendering.dag.nodes.AlphaRejectBlocksNode;
import org.terasology.rendering.dag.nodes.OpaqueBlocksNode;
import org.terasology.rendering.dag.nodes.RefractiveReflectiveBlocksNode;
import org.terasology.rendering.dag.nodes.FinalPostProcessingNode;
import org.terasology.rendering.dag.nodes.CopyImageToHMDNode;
import org.terasology.rendering.dag.nodes.FirstPersonViewNode;
import org.terasology.rendering.dag.nodes.InitialPostProcessingNode;
import org.terasology.rendering.dag.nodes.DeferredPointLightsNode;
import org.terasology.rendering.dag.nodes.LightShaftsNode;
import org.terasology.rendering.dag.nodes.OpaqueObjectsNode;
import org.terasology.rendering.dag.nodes.OutlineNode;
import org.terasology.rendering.dag.nodes.OverlaysNode;
import org.terasology.rendering.dag.nodes.PrePostCompositeNode;
import org.terasology.rendering.dag.nodes.BackdropReflectionNode;
import org.terasology.rendering.dag.nodes.ShadowMapNode;
import org.terasology.rendering.dag.nodes.SimpleBlendMaterialsNode;
import org.terasology.rendering.dag.nodes.HazeNode;
import org.terasology.rendering.dag.nodes.ToneMappingNode;
import org.terasology.rendering.dag.nodes.WorldReflectionNode;
import org.terasology.rendering.opengl.FBO;
import org.terasology.rendering.opengl.FBOConfig;
import org.terasology.rendering.opengl.ScreenGrabber;
import org.terasology.rendering.opengl.fbms.DisplayResolutionDependentFBOs;
import org.terasology.rendering.opengl.fbms.ShadowMapResolutionDependentFBOs;
import org.terasology.rendering.opengl.fbms.ImmutableFBOs;
import org.terasology.rendering.world.viewDistance.ViewDistance;
import org.terasology.utilities.Assets;
import org.terasology.world.WorldProvider;
import org.terasology.world.chunks.ChunkProvider;
import java.util.List;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_STENCIL_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.terasology.rendering.dag.NodeFactory.DELAY_INIT;
import static org.terasology.rendering.dag.nodes.DownSamplerForExposureNode.*;
import static org.terasology.rendering.dag.nodes.LateBlurNode.FIRST_LATE_BLUR_FBO;
import static org.terasology.rendering.dag.nodes.LateBlurNode.SECOND_LATE_BLUR_FBO;
import static org.terasology.rendering.dag.nodes.ToneMappingNode.TONE_MAPPED_FBO;
import static org.terasology.rendering.opengl.DefaultDynamicFBOs.READ_ONLY_GBUFFER;
import static org.terasology.rendering.opengl.ScalingFactors.FULL_SCALE;
import static org.terasology.rendering.opengl.ScalingFactors.HALF_SCALE;
import static org.terasology.rendering.opengl.ScalingFactors.QUARTER_SCALE;
import static org.terasology.rendering.opengl.ScalingFactors.ONE_8TH_SCALE;
import static org.terasology.rendering.opengl.ScalingFactors.ONE_16TH_SCALE;
import static org.terasology.rendering.opengl.ScalingFactors.ONE_32TH_SCALE;
/**
* Renders the 3D world, including background, overlays and first person/in hand objects. 2D UI elements are dealt with elsewhere.
*
* This implementation includes support for OpenVR, through which HTC Vive and Oculus Rift is supported.
*
* This implementation works closely with a number of support objects, in particular:
*
* TODO: update this section to include new, relevant objects
* - a RenderableWorld instance, providing acceleration structures caching blocks requiring different rendering treatments<br/>
*/
public final class WorldRendererImpl implements WorldRenderer {
private boolean isFirstRenderingStageForCurrentFrame;
private final RenderQueuesHelper renderQueues;
private final Context context;
private final BackdropProvider backdropProvider;
private final WorldProvider worldProvider;
private final RenderableWorld renderableWorld;
private final ShaderManager shaderManager;
private final SubmersibleCamera playerCamera;
// TODO: @In
private final OpenVRProvider vrProvider;
private float timeSmoothedMainLightIntensity;
private RenderingStage currentRenderingStage;
private float millisecondsSinceRenderingStart;
private float secondsSinceLastFrame;
private int statChunkMeshEmpty;
private int statChunkNotReady;
private int statRenderedTriangles;
private final RenderingConfig renderingConfig;
private RenderTaskListGenerator renderTaskListGenerator;
private boolean requestedTaskListRefresh;
private List<RenderPipelineTask> renderPipelineTaskList;
private ShadowMapNode shadowMapNode;
private ImmutableFBOs immutableFBOs;
private DisplayResolutionDependentFBOs displayResolutionDependentFBOs;
private ShadowMapResolutionDependentFBOs shadowMapResolutionDependentFBOs;
/**
* Instantiates a WorldRenderer implementation.
*
* This particular implementation works as deferred shader. The scene is rendered multiple times per frame
* in a number of separate passes (each stored in GPU buffers) and the passes are combined throughout the
* rendering pipeline to calculate per-pixel lighting and other effects.
*
* Transparencies are handled through alpha rejection (i.e. ground plants) and alpha-based blending.
* An exception to this is water, which is handled separately to allow for reflections and refractions, if enabled.
*
* By the time it is fully instantiated this implementation is already connected to all the support objects
* it requires and is ready to render via the render(RenderingStage) method.
*
* @param context a context object, to obtain instances of classes such as the rendering config.
* @param bufferPool a GLBufferPool, to be passed to the RenderableWorld instance used by this implementation.
*/
public WorldRendererImpl(Context context, GLBufferPool bufferPool) {
this.context = context;
this.worldProvider = context.get(WorldProvider.class);
this.backdropProvider = context.get(BackdropProvider.class);
this.renderingConfig = context.get(Config.class).getRendering();
this.shaderManager = context.get(ShaderManager.class);
vrProvider = OpenVRProvider.getInstance();
if (renderingConfig.isVrSupport()) {
context.put(OpenVRProvider.class, vrProvider);
// If vrProvider.init() returns false, this means that we are unable to initialize VR hardware for some
// reason (for example, no HMD is connected). In that case, even though the configuration requests
// vrSupport, we fall back on rendering to the main display. The reason for init failure can be read from
// the log.
if (vrProvider.init()) {
playerCamera = new OpenVRStereoCamera(vrProvider, worldProvider, renderingConfig);
currentRenderingStage = RenderingStage.LEFT_EYE;
} else {
playerCamera = new PerspectiveCamera(worldProvider, renderingConfig);
currentRenderingStage = RenderingStage.MONO;
}
} else {
playerCamera = new PerspectiveCamera(worldProvider, renderingConfig);
currentRenderingStage = RenderingStage.MONO;
}
// TODO: won't need localPlayerSystem here once camera is in the ES proper
LocalPlayerSystem localPlayerSystem = context.get(LocalPlayerSystem.class);
localPlayerSystem.setPlayerCamera(playerCamera);
renderableWorld = new RenderableWorldImpl(worldProvider, context.get(ChunkProvider.class), bufferPool, playerCamera);
renderQueues = renderableWorld.getRenderQueues();
initRenderingSupport();
}
private void initRenderingSupport() {
context.put(ScreenGrabber.class, new ScreenGrabber(context));
immutableFBOs = new ImmutableFBOs();
displayResolutionDependentFBOs = new DisplayResolutionDependentFBOs(context);
shadowMapResolutionDependentFBOs = new ShadowMapResolutionDependentFBOs();
context.put(DisplayResolutionDependentFBOs.class, displayResolutionDependentFBOs);
context.put(ImmutableFBOs.class, immutableFBOs);
context.put(ShadowMapResolutionDependentFBOs.class, shadowMapResolutionDependentFBOs);
shaderManager.initShaders();
context.put(WorldRenderer.class, this);
context.put(RenderQueuesHelper.class, renderQueues);
context.put(RenderableWorld.class, renderableWorld);
initRenderGraph();
}
private void initRenderGraph() {
// FIXME: init pipeline without specifying them as a field in this class
NodeFactory nodeFactory = new NodeFactory(context);
RenderGraph renderGraph = new RenderGraph();
// ShadowMap generation
FBOConfig shadowMapConfig =
new FBOConfig(ShadowMapNode.SHADOW_MAP, FBO.Type.NO_COLOR).useDepthBuffer();
BufferClearingNode shadowMapClearingNode = nodeFactory.createInstance(BufferClearingNode.class, DELAY_INIT);
shadowMapClearingNode.initialise(shadowMapConfig, shadowMapResolutionDependentFBOs, GL_DEPTH_BUFFER_BIT);
renderGraph.addNode(shadowMapClearingNode, "shadowMapClearingNode");
shadowMapNode = nodeFactory.createInstance(ShadowMapNode.class);
renderGraph.addNode(shadowMapNode, "shadowMapNode");
// (i.e. water) reflection generation
FBOConfig reflectedBufferConfig =
new FBOConfig(BackdropReflectionNode.REFLECTED, HALF_SCALE, FBO.Type.DEFAULT).useDepthBuffer();
BufferClearingNode reflectedBufferClearingNode = nodeFactory.createInstance(BufferClearingNode.class, DELAY_INIT);
reflectedBufferClearingNode.initialise(reflectedBufferConfig, displayResolutionDependentFBOs, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderGraph.addNode(reflectedBufferClearingNode, "reflectedBufferClearingNode"); // TODO: verify this is necessary
Node reflectedBackdropNode = nodeFactory.createInstance(BackdropReflectionNode.class);
renderGraph.addNode(reflectedBackdropNode, "reflectedBackdropNode");
Node worldReflectionNode = nodeFactory.createInstance(WorldReflectionNode.class);
renderGraph.addNode(worldReflectionNode, "worldReflectionNode");
// sky rendering
FBOConfig reflectedRefractedBufferConfig = new FBOConfig(new ResourceUrn("engine:sceneReflectiveRefractive"), FULL_SCALE, FBO.Type.HDR).useNormalBuffer();
BufferClearingNode reflectedRefractedClearingNode = nodeFactory.createInstance(BufferClearingNode.class, DELAY_INIT);
reflectedRefractedClearingNode.initialise(reflectedRefractedBufferConfig, displayResolutionDependentFBOs, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderGraph.addNode(reflectedRefractedClearingNode, "reflectedRefractedClearingNode");
BufferClearingNode readBufferClearingNode = nodeFactory.createInstance(BufferClearingNode.class, DELAY_INIT);
readBufferClearingNode.initialise(READ_ONLY_GBUFFER.getConfig(), displayResolutionDependentFBOs,
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
renderGraph.addNode(readBufferClearingNode, "readBufferClearingNode");
Node backdropNode = nodeFactory.createInstance(BackdropNode.class);
renderGraph.addNode(backdropNode, "backdropNode");
String aLabel = "hazeIntermediateNode";
FBOConfig hazeIntermediateConfig = new FBOConfig(HazeNode.INTERMEDIATE_HAZE, ONE_16TH_SCALE, FBO.Type.DEFAULT);
HazeNode hazeIntermediateNode = nodeFactory.createInstance(HazeNode.class, DELAY_INIT);
hazeIntermediateNode.initialise(READ_ONLY_GBUFFER.getConfig(), hazeIntermediateConfig, aLabel);
renderGraph.addNode(hazeIntermediateNode, aLabel);
aLabel = "hazeFinalNode";
FBOConfig hazeFinalConfig = new FBOConfig(HazeNode.FINAL_HAZE, ONE_32TH_SCALE, FBO.Type.DEFAULT);
HazeNode hazeFinalNode = nodeFactory.createInstance(HazeNode.class, DELAY_INIT);
hazeFinalNode.initialise(hazeIntermediateConfig, hazeFinalConfig, aLabel);
renderGraph.addNode(hazeFinalNode, aLabel);
// world rendering
Node opaqueObjectsNode = nodeFactory.createInstance(OpaqueObjectsNode.class);
renderGraph.addNode(opaqueObjectsNode, "opaqueObjectsNode");
Node opaqueBlocksNode = nodeFactory.createInstance(OpaqueBlocksNode.class);
renderGraph.addNode(opaqueBlocksNode, "opaqueBlocksNode");
Node alphaRejectBlocksNode = nodeFactory.createInstance(AlphaRejectBlocksNode.class);
renderGraph.addNode(alphaRejectBlocksNode, "alphaRejectBlocksNode");
Node overlaysNode = nodeFactory.createInstance(OverlaysNode.class);
renderGraph.addNode(overlaysNode, "overlaysNode");
// TODO: remove this, including associated method in the RenderSystem interface
Node firstPersonViewNode = nodeFactory.createInstance(FirstPersonViewNode.class);
renderGraph.addNode(firstPersonViewNode, "firstPersonViewNode");
// lighting
Node deferredPointLightsNode = nodeFactory.createInstance(DeferredPointLightsNode.class);
renderGraph.addNode(deferredPointLightsNode, "DeferredPointLightsNode");
Node deferredMainLightNode = nodeFactory.createInstance(DeferredMainLightNode.class);
renderGraph.addNode(deferredMainLightNode, "deferredMainLightNode");
Node applyDeferredLightingNode = nodeFactory.createInstance(ApplyDeferredLightingNode.class);
renderGraph.addNode(applyDeferredLightingNode, "applyDeferredLightingNode");
Node chunksRefractiveReflectiveNode = nodeFactory.createInstance(RefractiveReflectiveBlocksNode.class);
renderGraph.addNode(chunksRefractiveReflectiveNode, "chunksRefractiveReflectiveNode");
// TODO: consider having a none-rendering node for FBO.attachDepthBufferTo() methods
// 3d-based decorations (versus purely 2d, post-production effects)
Node outlineNode = nodeFactory.createInstance(OutlineNode.class);
renderGraph.addNode(outlineNode, "outlineNode");
Node ambientOcclusionNode = nodeFactory.createInstance(AmbientOcclusionNode.class);
renderGraph.addNode(ambientOcclusionNode, "ambientOcclusionNode");
Node blurredAmbientOcclusionNode = nodeFactory.createInstance(BlurredAmbientOcclusionNode.class);
renderGraph.addNode(blurredAmbientOcclusionNode, "blurredAmbientOcclusionNode");
// Pre-post-processing, just one more interaction with 3D data (semi-transparent objects, in SimpleBlendMaterialsNode)
// and then it's 2D post-processing all the way to the image shown on the display.
Node prePostCompositeNode = nodeFactory.createInstance(PrePostCompositeNode.class);
renderGraph.addNode(prePostCompositeNode, "prePostCompositeNode");
Node simpleBlendMaterialsNode = nodeFactory.createInstance(SimpleBlendMaterialsNode.class);
renderGraph.addNode(simpleBlendMaterialsNode, "simpleBlendMaterialsNode");
// Post-Processing proper: tone mapping, light shafts, bloom and blur passes
Node lightShaftsNode = nodeFactory.createInstance(LightShaftsNode.class);
renderGraph.addNode(lightShaftsNode, "lightShaftsNode");
Node initialPostProcessingNode = nodeFactory.createInstance(InitialPostProcessingNode.class);
renderGraph.addNode(initialPostProcessingNode, "initialPostProcessingNode");
aLabel = "downSampling_gBuffer_to_16x16px_forExposure";
DownSamplerForExposureNode exposureDownSamplerTo16pixels = nodeFactory.createInstance(DownSamplerForExposureNode.class, DELAY_INIT);
exposureDownSamplerTo16pixels.initialise(READ_ONLY_GBUFFER.getConfig(), displayResolutionDependentFBOs, FBO_16X16_CONFIG, immutableFBOs, aLabel);
renderGraph.addNode(exposureDownSamplerTo16pixels, aLabel);
aLabel = "downSampling_16x16px_to_8x8px_forExposure";
DownSamplerForExposureNode exposureDownSamplerTo8pixels = nodeFactory.createInstance(DownSamplerForExposureNode.class, DELAY_INIT);
exposureDownSamplerTo8pixels.initialise(FBO_16X16_CONFIG, immutableFBOs, FBO_8X8_CONFIG, immutableFBOs, aLabel);
renderGraph.addNode(exposureDownSamplerTo8pixels, aLabel);
aLabel = "downSampling_8x8px_to_4x4px_forExposure";
DownSamplerForExposureNode exposureDownSamplerTo4pixels = nodeFactory.createInstance(DownSamplerForExposureNode.class, DELAY_INIT);
exposureDownSamplerTo4pixels.initialise(FBO_8X8_CONFIG, immutableFBOs, FBO_4X4_CONFIG, immutableFBOs, aLabel);
renderGraph.addNode(exposureDownSamplerTo4pixels, aLabel);
aLabel = "downSampling_4x4px_to_2x2px_forExposure";
DownSamplerForExposureNode exposureDownSamplerTo2pixels = nodeFactory.createInstance(DownSamplerForExposureNode.class, DELAY_INIT);
exposureDownSamplerTo2pixels.initialise(FBO_4X4_CONFIG, immutableFBOs, FBO_2X2_CONFIG, immutableFBOs, aLabel);
renderGraph.addNode(exposureDownSamplerTo2pixels, aLabel);
aLabel = "downSampling_2x2px_to_1x1px_forExposure";
DownSamplerForExposureNode exposureDownSamplerTo1pixel = nodeFactory.createInstance(DownSamplerForExposureNode.class, DELAY_INIT);
exposureDownSamplerTo1pixel.initialise(FBO_2X2_CONFIG, immutableFBOs, FBO_1X1_CONFIG, immutableFBOs, aLabel);
renderGraph.addNode(exposureDownSamplerTo1pixel, aLabel);
Node updateExposureNode = nodeFactory.createInstance(UpdateExposureNode.class);
renderGraph.addNode(updateExposureNode, "updateExposureNode");
Node toneMappingNode = nodeFactory.createInstance(ToneMappingNode.class);
renderGraph.addNode(toneMappingNode, "toneMappingNode");
// Bloom Effect: one high-pass filter and three blur passes
Node highPassNode = nodeFactory.createInstance(HighPassNode.class);
renderGraph.addNode(highPassNode, "highPassNode");
FBOConfig halfScaleBloomConfig = new FBOConfig(BloomBlurNode.HALF_SCALE_FBO, HALF_SCALE, FBO.Type.DEFAULT);
FBOConfig quarterScaleBloomConfig = new FBOConfig(BloomBlurNode.QUARTER_SCALE_FBO, QUARTER_SCALE, FBO.Type.DEFAULT);
FBOConfig one8thScaleBloomConfig = new FBOConfig(BloomBlurNode.ONE_8TH_SCALE_FBO, ONE_8TH_SCALE, FBO.Type.DEFAULT);
aLabel = "halfScaleBlurredBloom";
BloomBlurNode halfScaleBlurredBloom = nodeFactory.createInstance(BloomBlurNode.class, DELAY_INIT);
halfScaleBlurredBloom.initialise(HighPassNode.HIGH_PASS_FBO_CONFIG, halfScaleBloomConfig, aLabel);
renderGraph.addNode(halfScaleBlurredBloom, aLabel);
aLabel = "quarterScaleBlurredBloom";
BloomBlurNode quarterScaleBlurredBloom = nodeFactory.createInstance(BloomBlurNode.class, DELAY_INIT);
quarterScaleBlurredBloom.initialise(halfScaleBloomConfig, quarterScaleBloomConfig, aLabel);
renderGraph.addNode(quarterScaleBlurredBloom, aLabel);
aLabel = "one8thScaleBlurredBloom";
BloomBlurNode one8thScaleBlurredBloom = nodeFactory.createInstance(BloomBlurNode.class, DELAY_INIT);
one8thScaleBlurredBloom.initialise(quarterScaleBloomConfig, one8thScaleBloomConfig, aLabel);
renderGraph.addNode(one8thScaleBlurredBloom, aLabel);
// Late Blur nodes: assisting Motion Blur and Depth-of-Field effects - TODO: place next line closer to ToneMappingNode eventually.
FBOConfig toneMappedConfig = new FBOConfig(TONE_MAPPED_FBO, FULL_SCALE, FBO.Type.HDR);
FBOConfig firstLateBlurConfig = new FBOConfig(FIRST_LATE_BLUR_FBO, HALF_SCALE, FBO.Type.DEFAULT);
FBOConfig secondLateBlurConfig = new FBOConfig(SECOND_LATE_BLUR_FBO, HALF_SCALE, FBO.Type.DEFAULT);
aLabel = "firstLateBlurNode";
LateBlurNode firstLateBlurNode = nodeFactory.createInstance(LateBlurNode.class, DELAY_INIT);
firstLateBlurNode.initialise(toneMappedConfig, firstLateBlurConfig, aLabel);
renderGraph.addNode(firstLateBlurNode, aLabel);
aLabel = "secondLateBlurNode";
LateBlurNode secondLateBlurNode = nodeFactory.createInstance(LateBlurNode.class, DELAY_INIT);
secondLateBlurNode.initialise(firstLateBlurConfig, secondLateBlurConfig, aLabel);
renderGraph.addNode(secondLateBlurNode, aLabel);
Node finalPostProcessingNode = nodeFactory.createInstance(FinalPostProcessingNode.class);
renderGraph.addNode(finalPostProcessingNode, "finalPostProcessingNode");
Node copyToVRFrameBufferNode = nodeFactory.createInstance(CopyImageToHMDNode.class);
renderGraph.addNode(copyToVRFrameBufferNode, "copyToVRFrameBufferNode");
Node copyImageToScreenNode = nodeFactory.createInstance(CopyImageToScreenNode.class);
renderGraph.addNode(copyImageToScreenNode, "copyImageToScreenNode");
renderTaskListGenerator = new RenderTaskListGenerator();
List<Node> orderedNodes = renderGraph.getNodesInTopologicalOrder();
renderPipelineTaskList = renderTaskListGenerator.generateFrom(orderedNodes);
}
@Override
public float getSecondsSinceLastFrame() {
return secondsSinceLastFrame;
}
@Override
public Material getMaterial(String assetId) {
return Assets.getMaterial(assetId).orElseThrow(() ->
new RuntimeException("Failed to resolve required asset: '" + assetId + "'"));
}
@Override
public void onChunkLoaded(Vector3i pos) {
renderableWorld.onChunkLoaded(pos);
}
@Override
public void onChunkUnloaded(Vector3i pos) {
renderableWorld.onChunkUnloaded(pos);
}
@Override
public boolean pregenerateChunks() {
return renderableWorld.pregenerateChunks();
}
@Override
public void update(float deltaInSeconds) {
secondsSinceLastFrame += deltaInSeconds;
}
private void resetStats() {
statChunkMeshEmpty = 0;
statChunkNotReady = 0;
statRenderedTriangles = 0;
}
@Override
public void increaseTrianglesCount(int increase) {
statRenderedTriangles += increase;
}
@Override
public void increaseNotReadyChunkCount(int increase) {
statChunkNotReady += increase;
}
private void preRenderUpdate(RenderingStage renderingStage) {
resetStats();
currentRenderingStage = renderingStage;
if ((currentRenderingStage == RenderingStage.MONO) || (currentRenderingStage == RenderingStage.LEFT_EYE)) {
isFirstRenderingStageForCurrentFrame = true;
} else {
isFirstRenderingStageForCurrentFrame = false;
}
// this is done to execute this code block only once per frame
// instead of once per eye in a stereo setup.
if (isFirstRenderingStageForCurrentFrame) {
timeSmoothedMainLightIntensity = TeraMath.lerp(timeSmoothedMainLightIntensity, getMainLightIntensityAt(playerCamera.getPosition()), secondsSinceLastFrame);
playerCamera.update(secondsSinceLastFrame);
renderableWorld.update();
renderableWorld.generateVBOs();
secondsSinceLastFrame = 0;
displayResolutionDependentFBOs.update();
millisecondsSinceRenderingStart += secondsSinceLastFrame * 1000; // updates the variable animations are based on.
}
if (currentRenderingStage != RenderingStage.MONO) {
playerCamera.updateFrustum();
}
// this line needs to be here as deep down it relies on the camera's frustrum, updated just above.
renderableWorld.queueVisibleChunks(isFirstRenderingStageForCurrentFrame);
if (requestedTaskListRefresh) {
renderTaskListGenerator.refresh();
requestedTaskListRefresh = false;
}
}
/**
* TODO: update javadocs
* This method triggers the execution of the rendering pipeline and, eventually, sends the output to the display
* or to a file, when grabbing a screenshot.
*
* In this particular implementation this method can be called once per frame, when rendering to a standard display,
* or twice, each time with a different rendering stage, when rendering to the head mounted display.
*
* PerformanceMonitor.startActivity/endActivity statements are used in this method and in those it executes,
* to provide statistics regarding the ongoing rendering and its individual steps (i.e. rendering shadows,
* reflections, 2D filters...).
*
* @param renderingStage "MONO" for standard rendering and "LEFT_EYE" or "RIGHT_EYE" for stereoscopic displays.
*/
@Override
public void render(RenderingStage renderingStage) {
preRenderUpdate(renderingStage);
// TODO: Add a method here to check wireframe configuration and regenerate "renderPipelineTask" accordingly.
// The following line re-establish OpenGL defaults, so that the nodes/tasks can rely on them.
// A place where Terasology overrides the defaults is LwjglGraphics.initOpenGLParams(), but
// there could be potentially other places, i.e. in the UI code. In the rendering engine we'd like
// to eventually rely on a default OpenGL state.
glDisable(GL_CULL_FACE);
//glDisable(GL_DEPTH_TEST);
//glDisable(GL_NORMALIZE); // currently keeping these as they are, until we find where they are used.
//glDepthFunc(GL_LESS);
renderPipelineTaskList.forEach(RenderPipelineTask::execute);
// this line re-establish Terasology defaults, so that the rest of the application can rely on them.
LwjglGraphics.initOpenGLParams();
playerCamera.updatePrevViewProjectionMatrix();
}
@Override
public void requestTaskListRefresh() {
requestedTaskListRefresh = true;
}
@Override
public boolean isFirstRenderingStageForCurrentFrame() {
return isFirstRenderingStageForCurrentFrame;
}
/**
* Disposes of support objects used by this implementation.
*/
@Override
public void dispose() {
renderableWorld.dispose();
worldProvider.dispose();
}
@Override
public void setViewDistance(ViewDistance viewDistance) {
renderableWorld.updateChunksInProximity(viewDistance);
}
@Override
public float getTimeSmoothedMainLightIntensity() {
return timeSmoothedMainLightIntensity;
}
@Override
public float getRenderingLightIntensityAt(Vector3f pos) {
float rawLightValueSun = worldProvider.getSunlight(pos) / 15.0f;
float rawLightValueBlock = worldProvider.getLight(pos) / 15.0f;
float lightValueSun = (float) Math.pow(BLOCK_LIGHT_SUN_POW, (1.0f - rawLightValueSun) * 16.0f) * rawLightValueSun;
lightValueSun *= backdropProvider.getDaylight();
// TODO: Hardcoded factor and value to compensate for daylight tint and night brightness
lightValueSun *= 0.9f;
lightValueSun += 0.05f;
float lightValueBlock = (float) Math.pow(BLOCK_LIGHT_POW, (1.0f - rawLightValueBlock) * 16.0f) * rawLightValueBlock * BLOCK_INTENSITY_FACTOR;
return Math.max(lightValueBlock, lightValueSun);
}
@Override
public float getMainLightIntensityAt(Vector3f position) {
return backdropProvider.getDaylight() * worldProvider.getSunlight(position) / 15.0f;
}
@Override
public float getBlockLightIntensityAt(Vector3f position) {
return worldProvider.getLight(position) / 15.0f;
}
@Override
public String getMetrics() {
String stringToReturn = "";
stringToReturn += renderableWorld.getMetrics();
stringToReturn += "Empty Mesh Chunks: ";
stringToReturn += statChunkMeshEmpty;
stringToReturn += "\n";
stringToReturn += "Unready Chunks: ";
stringToReturn += statChunkNotReady;
stringToReturn += "\n";
stringToReturn += "Rendered Triangles: ";
stringToReturn += statRenderedTriangles;
stringToReturn += "\n";
return stringToReturn;
}
@Override
public float getMillisecondsSinceRenderingStart() {
return millisecondsSinceRenderingStart;
}
@Override
public SubmersibleCamera getActiveCamera() {
return playerCamera;
}
@Override
public Camera getLightCamera() {
//FIXME: remove this method
return shadowMapNode.shadowMapCamera;
}
@Override
public RenderingStage getCurrentRenderStage() {
return currentRenderingStage;
}
}