diff --git a/jme3-core/src/main/java/com/jme3/light/AmbientLight.java b/jme3-core/src/main/java/com/jme3/light/AmbientLight.java index dc23ebb12..e147c6590 100644 --- a/jme3-core/src/main/java/com/jme3/light/AmbientLight.java +++ b/jme3-core/src/main/java/com/jme3/light/AmbientLight.java @@ -31,7 +31,11 @@ */ package com.jme3.light; +import com.jme3.bounding.BoundingBox; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; /** * An ambient light adds a constant color to the scene. @@ -45,6 +49,16 @@ import com.jme3.scene.Spatial; */ public class AmbientLight extends Light { + @Override + public boolean intersectsBox(BoundingBox box, TempVars vars) { + return true; + } + + @Override + public boolean intersectsFrustum(Camera camera, TempVars vars) { + return true; + } + @Override public void computeLastDistance(Spatial owner) { } diff --git a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java new file mode 100644 index 000000000..81287279e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2014 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.bounding.BoundingVolume; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.util.TempVars; +import java.util.HashSet; + +public final class DefaultLightFilter implements LightFilter { + + private Camera camera; + private final HashSet processedLights = new HashSet(); + + public void setCamera(Camera camera) { + this.camera = camera; + for (Light light : processedLights) { + light.frustumCheckNeeded = true; + } + } + + public void filterLights(Geometry geometry, LightList filteredLightList) { + TempVars vars = TempVars.get(); + try { + LightList worldLights = geometry.getWorldLightList(); + for (int i = 0; i < worldLights.size(); i++) { + Light light = worldLights.get(i); + + if (light.frustumCheckNeeded) { + processedLights.add(light); + light.frustumCheckNeeded = false; + light.intersectsFrustum = light.intersectsFrustum(camera, vars); + } + + if (!light.intersectsFrustum) { + continue; + } + + BoundingVolume bv = geometry.getWorldBound(); + + if (bv instanceof BoundingBox) { + if (!light.intersectsBox((BoundingBox)bv, vars)) { + continue; + } + } else if (bv instanceof BoundingSphere) { + if (!Float.isInfinite( ((BoundingSphere)bv).getRadius() )) { + // Non-infinite bounding sphere... Not supported yet. + throw new UnsupportedOperationException("Only AABB supported for now"); + } + } + + filteredLightList.add(light); + } + } finally { + vars.release(); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java index cb65258e7..c4258a67f 100644 --- a/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java +++ b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java @@ -31,12 +31,15 @@ */ package com.jme3.light; +import com.jme3.bounding.BoundingBox; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; import java.io.IOException; /** @@ -81,6 +84,16 @@ public class DirectionalLight extends Light { } } + @Override + public boolean intersectsBox(BoundingBox box, TempVars vars) { + return true; + } + + @Override + public boolean intersectsFrustum(Camera camera, TempVars vars) { + return true; + } + @Override public Type getType() { return Type.Directional; diff --git a/jme3-core/src/main/java/com/jme3/light/Light.java b/jme3-core/src/main/java/com/jme3/light/Light.java index 58e62cf34..4217e1b62 100644 --- a/jme3-core/src/main/java/com/jme3/light/Light.java +++ b/jme3-core/src/main/java/com/jme3/light/Light.java @@ -31,9 +31,12 @@ */ package com.jme3.light; +import com.jme3.bounding.BoundingBox; import com.jme3.export.*; import com.jme3.math.ColorRGBA; +import com.jme3.renderer.Camera; import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; import java.io.IOException; /** @@ -64,8 +67,8 @@ public abstract class Light implements Savable, Cloneable { /** * Spot light. - *

- * Not supported by jMonkeyEngine + * + * @see SpotLight */ Spot(2), @@ -108,6 +111,9 @@ public abstract class Light implements Savable, Cloneable { * The light name. */ protected String name; + + boolean frustumCheckNeeded = true; + boolean intersectsFrustum = false; /** * Returns the color of the light. @@ -169,6 +175,33 @@ public abstract class Light implements Savable, Cloneable { } */ + /** + * Determines if the light intersects with the given bounding box. + *

+ * For non-local lights, such as {@link DirectionalLight directional lights}, + * {@link AmbientLight ambient lights}, or {@link PointLight point lights} + * without influence radius, this method should always return true. + * + * @param box The box to check intersection against. + * @param vars TempVars in case it is needed. + * + * @return True if the light intersects the box, false otherwise. + */ + public abstract boolean intersectsBox(BoundingBox box, TempVars vars); + + /** + * Determines if the lgiht intersects with the given camera frustum. + * + * For non-local lights, such as {@link DirectionalLight directional lights}, + * {@link AmbientLight ambient lights}, or {@link PointLight point lights} + * without influence radius, this method should always return true. + * + * @param camera The camera frustum to check intersection against. + * @param vars TempVars in case it is needed. + * @return True if the light intersects the frustum, false otherwise. + */ + public abstract boolean intersectsFrustum(Camera camera, TempVars vars); + @Override public Light clone(){ try { diff --git a/jme3-core/src/main/java/com/jme3/light/LightFilter.java b/jme3-core/src/main/java/com/jme3/light/LightFilter.java new file mode 100644 index 000000000..7196255d2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/LightFilter.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2014 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; + +/** + * LightFilter is used to determine which lights should be + * rendered for a particular {@link Geometry} + {@link Camera} combination. + * + * @author Kirill Vainer + */ +public interface LightFilter { + + /** + * Sets the camera for which future filtering is to be done against in + * {@link #filterLights(com.jme3.scene.Geometry, com.jme3.light.LightList)}. + * + * @param camera The camera to perform light filtering against. + */ + public void setCamera(Camera camera); + + /** + * Determine which lights on the {@link Geometry#getWorldLightList() world + * light list} are to be rendered. + *

+ * The simplest implementation (e.g. one that performs no filtering) would + * simply copy the contents of {@link Geometry#getWorldLightList()} to + * {@code filteredLightList}. + *

+ * An advanced implementation would determine if the light intersects + * the {@link Geometry#getWorldBound() geometry's bounding volume} and if + * the light intersects the frustum of the camera set in + * {@link #setCamera(com.jme3.renderer.Camera)} as well as sort the lights + * according to some "influence" criteria - this will then provide + * an optimal set of lights that should be used for rendering. + * + * @param geometry The geometry for which the light filtering is performed. + * @param filteredLightList The results are to be stored here. + */ + public void filterLights(Geometry geometry, LightList filteredLightList); +} diff --git a/jme3-core/src/main/java/com/jme3/light/PointLight.java b/jme3-core/src/main/java/com/jme3/light/PointLight.java index a86b92f1f..55a129275 100644 --- a/jme3-core/src/main/java/com/jme3/light/PointLight.java +++ b/jme3-core/src/main/java/com/jme3/light/PointLight.java @@ -31,13 +31,19 @@ */ package com.jme3.light; +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingVolume; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; import java.io.IOException; /** @@ -114,9 +120,9 @@ public class PointLight extends Light { throw new IllegalArgumentException("Light radius cannot be negative"); } this.radius = radius; - if(radius!=0){ + if (radius != 0) { this.invRadius = 1 / radius; - }else{ + } else { this.invRadius = 0; } } @@ -134,6 +140,32 @@ public class PointLight extends Light { return Light.Type.Point; } + @Override + public boolean intersectsBox(BoundingBox box, TempVars vars) { + if (this.radius == 0) { + return true; + } else { + // Sphere v. box collision + return FastMath.abs(box.getCenter().x - position.x) < radius + box.getXExtent() + && FastMath.abs(box.getCenter().y - position.y) < radius + box.getYExtent() + && FastMath.abs(box.getCenter().z - position.z) < radius + box.getZExtent(); + } + } + + @Override + public boolean intersectsFrustum(Camera camera, TempVars vars) { + if (this.radius == 0) { + return true; + } else { + for (int i = 5; i >= 0; i--) { + if (camera.getWorldPlane(i).pseudoDistance(position) <= -radius) { + return false; + } + } + return true; + } + } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java index 0ede9cd72..54b3698f0 100644 --- a/jme3-core/src/main/java/com/jme3/light/SpotLight.java +++ b/jme3-core/src/main/java/com/jme3/light/SpotLight.java @@ -31,11 +31,14 @@ */ package com.jme3.light; +import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingVolume; import com.jme3.export.*; import com.jme3.math.FastMath; import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; import java.io.IOException; /** @@ -61,16 +64,20 @@ public class SpotLight extends Light implements Savable { protected float spotRange = 100; protected float invSpotRange = 1 / 100; protected float packedAngleCos=0; + + protected float outerAngleCosSqr, outerAngleSinSqr; + protected float outerAngleSinRcp; public SpotLight() { super(); - computePackedCos(); + computeAngleParameters(); } - private void computePackedCos() { + private void computeAngleParameters() { float innerCos = FastMath.cos(spotInnerAngle); float outerCos = FastMath.cos(spotOuterAngle); packedAngleCos = (int) (innerCos * 1000); + //due to approximations, very close angles can give the same cos //here we make sure outer cos is bellow inner cos. if (((int) packedAngleCos) == ((int) (outerCos * 1000))) { @@ -81,8 +88,62 @@ public class SpotLight extends Light implements Savable { if (packedAngleCos == 0.0f) { throw new IllegalArgumentException("Packed angle cosine is invalid"); } + + // compute parameters needed for cone vs sphere check. + float outerSin = FastMath.sin(spotOuterAngle); + outerAngleCosSqr = outerCos * outerCos; + outerAngleSinSqr = outerSin * outerSin; + outerAngleSinRcp = 1.0f / outerSin; } + @Override + public boolean intersectsBox(BoundingBox box, TempVars vars) { + if (this.spotRange > 0f) { + // Check spot range first. + // Sphere v. box collision + if (FastMath.abs(box.getCenter().x - position.x) >= spotRange + box.getXExtent() + || FastMath.abs(box.getCenter().y - position.y) >= spotRange + box.getYExtent() + || FastMath.abs(box.getCenter().z - position.z) >= spotRange + box.getZExtent()) { + return false; + } + } + + Vector3f otherCenter = box.getCenter(); + Vector3f radVect = vars.vect4; + radVect.set(box.getXExtent(), box.getYExtent(), box.getZExtent()); + float otherRadiusSquared = radVect.lengthSquared(); + float otherRadius = FastMath.sqrt(otherRadiusSquared); + + // Check if sphere is within spot angle. + // Cone v. sphere collision. + Vector3f E = direction.mult(otherRadius * outerAngleSinRcp, vars.vect1); + Vector3f U = position.subtract(E, vars.vect2); + Vector3f D = otherCenter.subtract(U, vars.vect3); + + float dsqr = D.dot(D); + float e = direction.dot(D); + + if (e > 0f && e * e >= dsqr * outerAngleCosSqr) { + D = otherCenter.subtract(position, vars.vect3); + dsqr = D.dot(D); + e = -direction.dot(D); + + if (e > 0f && e * e >= dsqr * outerAngleSinSqr) { + return dsqr <= otherRadiusSquared; + } else { + return true; + } + } + + return false; + } + + @Override + public boolean intersectsFrustum(Camera camera, TempVars vars) { + // TODO: implement cone vs. frustum collision detection. + return true; + } + @Override protected void computeLastDistance(Spatial owner) { if (owner.getWorldBound() != null) { @@ -166,7 +227,7 @@ public class SpotLight extends Light implements Savable { */ public void setSpotInnerAngle(float spotInnerAngle) { this.spotInnerAngle = spotInnerAngle; - computePackedCos(); + computeAngleParameters(); } /** @@ -185,7 +246,7 @@ public class SpotLight extends Light implements Savable { */ public void setSpotOuterAngle(float spotOuterAngle) { this.spotOuterAngle = spotOuterAngle; - computePackedCos(); + computeAngleParameters(); } /** @@ -196,8 +257,6 @@ public class SpotLight extends Light implements Savable { return packedAngleCos; } - - @Override public void write(JmeExporter ex) throws IOException { super.write(ex); @@ -215,7 +274,7 @@ public class SpotLight extends Light implements Savable { InputCapsule ic = im.getCapsule(this); spotInnerAngle = ic.readFloat("spotInnerAngle", FastMath.QUARTER_PI / 8); spotOuterAngle = ic.readFloat("spotOuterAngle", FastMath.QUARTER_PI / 6); - computePackedCos(); + computeAngleParameters(); direction = (Vector3f) ic.readSavable("direction", new Vector3f()); position = (Vector3f) ic.readSavable("position", new Vector3f()); spotRange = ic.readFloat("spotRange", 100); diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 2462e40a3..20fcd1e9a 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -741,12 +741,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * g_LightPosition.w is the inverse radius (1/r) of the light (for * attenuation)

*/ - protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) { + protected void updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights) { if (numLights == 0) { // this shader does not do lighting, ignore. return; } - LightList lightList = g.getWorldLightList(); Uniform lightColor = shader.getUniform("g_LightColor"); Uniform lightPos = shader.getUniform("g_LightPosition"); Uniform lightDir = shader.getUniform("g_LightDirection"); @@ -813,10 +812,9 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { } } - protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) { + protected void renderMultipassLighting(Shader shader, Geometry g, LightList lightList, RenderManager rm) { Renderer r = rm.getRenderer(); - LightList lightList = g.getWorldLightList(); Uniform lightDir = shader.getUniform("g_LightDirection"); Uniform lightColor = shader.getUniform("g_LightColor"); Uniform lightPos = shader.getUniform("g_LightPosition"); @@ -1116,9 +1114,10 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * * * @param geom The geometry to render + * @param lights Presorted and filtered light list to use for rendering * @param rm The render manager requesting the rendering */ - public void render(Geometry geom, RenderManager rm) { + public void render(Geometry geom, LightList lights, RenderManager rm) { autoSelectTechnique(rm); Renderer r = rm.getRenderer(); @@ -1126,7 +1125,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { TechniqueDef techDef = technique.getDef(); if (techDef.getLightMode() == LightMode.MultiPass - && geom.getWorldLightList().size() == 0) { + && lights.size() == 0) { return; } @@ -1163,15 +1162,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { r.setLighting(null); break; case SinglePass: - updateLightListUniforms(shader, geom, 4); + updateLightListUniforms(shader, geom, lights, 4); break; case FixedPipeline: - r.setLighting(geom.getWorldLightList()); + r.setLighting(lights); break; case MultiPass: // NOTE: Special case! resetUniformsNotSetByCurrent(shader); - renderMultipassLighting(shader, geom, rm); + renderMultipassLighting(shader, geom, lights, rm); // very important, notice the return statement! return; } @@ -1186,6 +1185,20 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { renderMeshFromGeometry(r, geom); } + /** + * Called by {@link RenderManager} to render the geometry by + * using this material. + * + * Note that this version of the render method + * does not perform light filtering. + * + * @param geom The geometry to render + * @param rm The render manager requesting the rendering + */ + public void render(Geometry geom, RenderManager rm) { + render(geom, geom.getWorldLightList(), rm); + } + public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(def.getAssetName(), "material_def", null); diff --git a/jme3-core/src/main/java/com/jme3/renderer/Camera.java b/jme3-core/src/main/java/com/jme3/renderer/Camera.java index 77b494d49..0ce5bafd7 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Camera.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Camera.java @@ -1051,7 +1051,11 @@ public class Camera implements Savable, Cloneable { return rVal; } - + + public Plane getWorldPlane(int planeId) { + return worldPlane[planeId]; + } + /** * containsGui tests a bounding volume against the ortho * bounding box of the camera. A bounding box spanning from diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index 95bcf0cf2..ea4d2e84d 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -31,6 +31,9 @@ */ package com.jme3.renderer; +import com.jme3.light.DefaultLightFilter; +import com.jme3.light.LightFilter; +import com.jme3.light.LightList; import com.jme3.material.Material; import com.jme3.material.MaterialDef; import com.jme3.material.RenderState; @@ -81,9 +84,11 @@ public class RenderManager { private boolean shader; private int viewX, viewY, viewWidth, viewHeight; private Matrix4f orthoMatrix = new Matrix4f(); + private LightList filteredLightList = new LightList(null); private String tmpTech; private boolean handleTranlucentBucket = true; private AppProfiler prof; + private LightFilter lightFilter = new DefaultLightFilter(); /** * Create a high-level rendering interface over the @@ -521,6 +526,16 @@ public class RenderManager { } else { setWorldMatrix(g.getWorldMatrix()); } + + // Perform light filtering if we have a light filter. + LightList lightList = g.getWorldLightList(); + + if (lightFilter != null) { + filteredLightList.clear(); + lightFilter.filterLights(g, filteredLightList); + lightList = filteredLightList; + } + //if forcedTechnique we try to force it for render, //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null @@ -536,7 +551,7 @@ public class RenderManager { forcedRenderState = g.getMaterial().getActiveTechnique().getDef().getForcedRenderState(); } // use geometry's material - g.getMaterial().render(g, this); + g.getMaterial().render(g, lightList, this); g.getMaterial().selectTechnique(tmpTech, this); //restoring forcedRenderState @@ -546,13 +561,13 @@ public class RenderManager { //If forcedTechnique does not exists, and frocedMaterial is not set, the geom MUST NOT be rendered } else if (forcedMaterial != null) { // use forced material - forcedMaterial.render(g, this); + forcedMaterial.render(g, lightList, this); } } else if (forcedMaterial != null) { // use forced material - forcedMaterial.render(g, this); + forcedMaterial.render(g, lightList, this); } else { - g.getMaterial().render(g, this); + g.getMaterial().render(g, lightList, this); } } @@ -790,6 +805,11 @@ public class RenderManager { Camera cam = vp.getCamera(); boolean depthRangeChanged = false; + // Tell the light filter which camera to use for filtering. + if (lightFilter != null) { + lightFilter.setCamera(cam); + } + // render opaque objects with default depth range // opaque objects are sorted front-to-back, reducing overdraw if (prof!=null) prof.vpStep(VpStep.RenderBucket, vp, Bucket.Opaque);