/*
* 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.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.Sphere;
import org.terasology.assets.ResourceUrn;
import org.terasology.entitySystem.entity.EntityManager;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.logic.location.LocationComponent;
import org.terasology.math.geom.Matrix4f;
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.AbstractNode;
import org.terasology.rendering.dag.stateChanges.DisableDepthTest;
import org.terasology.rendering.dag.stateChanges.EnableBlending;
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.SetBlendFunction;
import org.terasology.rendering.dag.stateChanges.SetFacesToCull;
import org.terasology.rendering.logic.LightComponent;
import static org.lwjgl.opengl.GL11.*;
import static org.terasology.rendering.opengl.DefaultDynamicFBOs.READ_ONLY_GBUFFER;
import org.terasology.rendering.opengl.fbms.DisplayResolutionDependentFBOs;
import org.terasology.rendering.world.WorldRenderer;
/**
* Instances of this class are integral to the deferred rendering process.
* They render point lights as spheres, into the light accumulation buffer
* (the spheres have a radius proportional to each light's attenuation radius).
* Data from the light accumulation buffer is eventually combined with the
* content of other buffers to correctly light up the scene.
*/
public class DeferredPointLightsNode extends AbstractNode {
private static final ResourceUrn LIGHT_GEOMETRY_MATERIAL = new ResourceUrn("engine:prog.lightGeometryPass");
private static int lightSphereDisplayList = -1;
@In
private DisplayResolutionDependentFBOs displayResolutionDependentFBOs;
@In
private WorldRenderer worldRenderer;
@In
private EntityManager entityManager;
private Material lightGeometryMaterial;
private Camera playerCamera;
/**
* Initializes an instance of this node.
*
* This method -must- be called once for this node to be fully operational.
*/
@Override
public void initialise() {
playerCamera = worldRenderer.getActiveCamera();
addDesiredStateChange(new LookThrough(playerCamera));
lightGeometryMaterial = getMaterial(LIGHT_GEOMETRY_MATERIAL);
addDesiredStateChange(new EnableMaterial(LIGHT_GEOMETRY_MATERIAL.toString()));
addDesiredStateChange(new EnableFaceCulling());
addDesiredStateChange(new SetFacesToCull(GL_FRONT));
addDesiredStateChange(new EnableBlending());
addDesiredStateChange(new SetBlendFunction(GL_ONE, GL_ONE_MINUS_SRC_COLOR));
addDesiredStateChange(new DisableDepthTest());
initLightSphereDisplayList();
}
private void initLightSphereDisplayList() {
lightSphereDisplayList = glGenLists(1);
Sphere sphere = new Sphere();
glNewList(lightSphereDisplayList, GL11.GL_COMPILE);
sphere.draw(1, 8, 8);
glEndList();
}
private boolean lightIsRenderable(LightComponent lightComponent, Vector3f lightPositionRelativeToCamera) {
// if lightRenderingDistance is 0.0, the light is always considered, no matter the distance.
boolean lightIsRenderable = lightComponent.lightRenderingDistance == 0.0f
|| lightPositionRelativeToCamera.lengthSquared() < (lightComponent.lightRenderingDistance * lightComponent.lightRenderingDistance);
// above: rendering distance must be higher than distance from the camera or the light is ignored
// No matter what, we ignore lights that are not in the camera frustrum
lightIsRenderable &= playerCamera.getViewFrustum().intersects(lightPositionRelativeToCamera, lightComponent.lightAttenuationRange);
// TODO: (above) what about lights just off-frame? They might light up in-frame surfaces.
return lightIsRenderable;
}
/**
* Iterates over all available point lights and renders them as spheres into the light accumulation buffer.
*
* Furthermore, lights that are further from the camera than their set rendering distance are ignored,
* while lights with a rendering distance set to 0.0 are always considered. However, only lights within
* the camera's field of view (frustrum) are rendered.
*/
@Override
public void process() {
PerformanceMonitor.startActivity("rendering/pointLightsGeometry");
READ_ONLY_GBUFFER.bind(); // TODO: remove and replace with a state change
READ_ONLY_GBUFFER.setRenderBufferMask(false, false, true); // Only write to the light buffer
for (EntityRef entity : entityManager.getEntitiesWith(LightComponent.class, LocationComponent.class)) {
LightComponent lightComponent = entity.getComponent(LightComponent.class);
if (lightComponent.lightType == LightComponent.LightType.POINT) {
LocationComponent locationComponent = entity.getComponent(LocationComponent.class);
final Vector3f lightPositionInTeraCoords = locationComponent.getWorldPosition();
Vector3f lightPositionRelativeToCamera = new Vector3f();
lightPositionRelativeToCamera.sub(lightPositionInTeraCoords, playerCamera.getPosition());
if (lightIsRenderable(lightComponent, lightPositionRelativeToCamera)) {
lightGeometryMaterial.activateFeature(ShaderProgramFeature.FEATURE_LIGHT_POINT);
lightGeometryMaterial.setCamera(playerCamera);
// setting shader parameters regarding the light's properties
lightGeometryMaterial.setFloat3("lightColorDiffuse",
lightComponent.lightColorDiffuse.x, lightComponent.lightColorDiffuse.y, lightComponent.lightColorDiffuse.z, true);
lightGeometryMaterial.setFloat3("lightColorAmbient",
lightComponent.lightColorAmbient.x, lightComponent.lightColorAmbient.y, lightComponent.lightColorAmbient.z, true);
lightGeometryMaterial.setFloat3("lightProperties",
lightComponent.lightAmbientIntensity, lightComponent.lightDiffuseIntensity, lightComponent.lightSpecularPower, true);
lightGeometryMaterial.setFloat4("lightExtendedProperties",
lightComponent.lightAttenuationRange, lightComponent.lightAttenuationFalloff, 0.0f, 0.0f, true);
// setting shader parameters for the light position in camera space
Vector3f lightPositionInViewSpace = new Vector3f(lightPositionRelativeToCamera);
playerCamera.getViewMatrix().transformPoint(lightPositionInViewSpace);
lightGeometryMaterial.setFloat3("lightViewPos", lightPositionInViewSpace.x, lightPositionInViewSpace.y, lightPositionInViewSpace.z, true);
// set the size and location of the sphere to be rendered via shader parameters
Matrix4f modelMatrix = new Matrix4f();
modelMatrix.set(lightComponent.lightAttenuationRange); // scales the modelview matrix, effectively scales the light sphere
modelMatrix.setTranslation(lightPositionRelativeToCamera); // effectively moves the light sphere in the right position relative to camera
lightGeometryMaterial.setMatrix4("modelMatrix", modelMatrix, true);
glCallList(lightSphereDisplayList); // draws the light sphere
lightGeometryMaterial.deactivateFeature(ShaderProgramFeature.FEATURE_LIGHT_POINT);
}
}
}
READ_ONLY_GBUFFER.setRenderBufferMask(true, true, true); // TODO: eventually remove - used for safety for the time being
PerformanceMonitor.endActivity();
}
}