/* * Copyright 2016 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.dag.nodes; import org.terasology.assets.ResourceUrn; import org.terasology.config.Config; import org.terasology.config.RenderingConfig; import org.terasology.math.geom.Vector3f; import org.terasology.monitoring.PerformanceMonitor; import org.terasology.registry.In; import org.terasology.rendering.assets.material.Material; import org.terasology.rendering.assets.shader.ShaderProgramFeature; import org.terasology.rendering.cameras.Camera; import org.terasology.rendering.dag.ConditionDependentNode; import org.terasology.rendering.dag.stateChanges.BindFBO; import org.terasology.rendering.dag.stateChanges.EnableFaceCulling; import org.terasology.rendering.dag.stateChanges.EnableMaterial; import org.terasology.rendering.dag.stateChanges.LookThrough; import org.terasology.rendering.dag.stateChanges.ReflectedCamera; import org.terasology.rendering.dag.stateChanges.SetFacesToCull; import org.terasology.rendering.dag.stateChanges.SetViewportToSizeOf; import org.terasology.rendering.opengl.FBO; import org.terasology.rendering.opengl.FBOConfig; import org.terasology.rendering.opengl.fbms.DisplayResolutionDependentFBOs; import org.terasology.rendering.primitives.ChunkMesh; import org.terasology.rendering.world.RenderQueuesHelper; import org.terasology.rendering.world.WorldRenderer; import org.terasology.world.chunks.RenderableChunk; import static org.terasology.rendering.opengl.ScalingFactors.HALF_SCALE; import static org.lwjgl.opengl.GL11.GL_FRONT; import static org.terasology.rendering.primitives.ChunkMesh.RenderPhase.OPAQUE; /** * An instance of this class is responsible for rendering a reflected landscape into the * "engine:sceneReflected" buffer. This buffer is then used to produce the reflection * of the landscape on the water surface. * * It could potentially be used also for other reflecting surfaces, i.e. metal, but it only works * for horizontal surfaces. * * An instance of this class is enabled or disabled depending on the reflections setting in the rendering config. * * Diagram of this node can be viewed from: * TODO: move diagram to the wiki when this part of the code is stable * - https://docs.google.com/drawings/d/1Iz7MA8Y5q7yjxxcgZW-0antv5kgx6NYkvoInielbwGU/edit?usp=sharing */ public class WorldReflectionNode extends ConditionDependentNode { public static final ResourceUrn REFLECTED = new ResourceUrn("engine:sceneReflected"); private static final ResourceUrn CHUNK_MATERIAL = new ResourceUrn("engine:prog.chunk"); @In private RenderQueuesHelper renderQueues; @In private Config config; @In private WorldRenderer worldRenderer; @In private DisplayResolutionDependentFBOs displayResolutionDependentFBOs; private Camera playerCamera; private Material chunkShader; private RenderingConfig renderingConfig; /** * Node initialization. * * Internally requires the "engine:sceneReflected" buffer, stored in the (display) resolution-dependent FBO manager. * This is a default, half-scale buffer inclusive of a depth buffer FBO. See FBOConfig and ScalingFactors for details * on possible FBO configurations. * * This method also requests the material using the "chunk" shaders (vertex, fragment) to be enabled. */ @Override public void initialise() { playerCamera = worldRenderer.getActiveCamera(); addDesiredStateChange(new ReflectedCamera(playerCamera)); // this has to go before the LookThrough state change addDesiredStateChange(new LookThrough(playerCamera)); renderingConfig = config.getRendering(); requiresCondition(() -> renderingConfig.isReflectiveWater()); renderingConfig.subscribe(RenderingConfig.REFLECTIVE_WATER, this); requiresFBO(new FBOConfig(REFLECTED, HALF_SCALE, FBO.Type.DEFAULT).useDepthBuffer(), displayResolutionDependentFBOs); addDesiredStateChange(new BindFBO(REFLECTED, displayResolutionDependentFBOs)); addDesiredStateChange(new SetViewportToSizeOf(REFLECTED, displayResolutionDependentFBOs)); addDesiredStateChange(new EnableFaceCulling()); addDesiredStateChange(new SetFacesToCull(GL_FRONT)); addDesiredStateChange(new EnableMaterial(CHUNK_MATERIAL.toString())); // we must get this here because in process we activate/deactivate a specific shader feature. // TODO: improve EnableMaterial to take advantage of shader feature bitmasks. chunkShader = getMaterial(CHUNK_MATERIAL); } /** * Renders the landscape, reflected, into the buffers attached to the "engine:sceneReflected" FBO. It is used later, * to render horizontal reflective surfaces, i.e. water. * * Notice that this method -does not- clear the FBO. The rendering takes advantage of the depth buffer to decide * which pixel is in front of the one already stored in the buffer. * * See: https://en.wikipedia.org/wiki/Deep_image_compositing */ @Override public void process() { PerformanceMonitor.startActivity("rendering/worldReflection"); int numberOfRenderedTriangles = 0; int numberOfChunksThatAreNotReadyYet = 0; final Vector3f cameraPosition = playerCamera.getPosition(); chunkShader.activateFeature(ShaderProgramFeature.FEATURE_USE_FORWARD_LIGHTING); chunkShader.setFloat("clip", playerCamera.getClipHeight(), true); while (renderQueues.chunksOpaqueReflection.size() > 0) { RenderableChunk chunk = renderQueues.chunksOpaqueReflection.poll(); if (chunk.hasMesh()) { final ChunkMesh chunkMesh = chunk.getMesh(); final Vector3f chunkPosition = chunk.getPosition().toVector3f(); chunkMesh.updateMaterial(chunkShader, chunkPosition, chunk.isAnimated()); numberOfRenderedTriangles += chunkMesh.render(OPAQUE, chunkPosition, cameraPosition); } else { numberOfChunksThatAreNotReadyYet++; } } chunkShader.deactivateFeature(ShaderProgramFeature.FEATURE_USE_FORWARD_LIGHTING); worldRenderer.increaseTrianglesCount(numberOfRenderedTriangles); worldRenderer.increaseNotReadyChunkCount(numberOfChunksThatAreNotReadyYet); PerformanceMonitor.endActivity(); } }