LightControl with point light position does not match

Hi all,
I am busy integrating the point and spot light into my scene editor when I noticed some strange behaviour when making use of the LightControl. It seems like the position of the pointlight and the spatial does not match up.

I then wrote my own control just to make the position of the pointlight the same as the world position of the spatial I want it to follow and it still seems to be an issue.

This let me think there is something wrong in the way jME renders or position the Pointlight.
If there is anyone who knows about the problem or who has a solution, please let me know.

Here is an example code to recreate the problem:


import com.jme3.app.SimpleApplication;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.LightControl;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;

/**
 *
 * Light Control Test, where the point light does not exactly follow the node.
 *
 * @author nicki
 */
public class TestLightControl extends SimpleApplication {

    private Node sceneNode;
    private Node light1Node;
    private Node light2Node;
    private Node light3Node;
    private float boardExtend = 50;

    public static void main(String[] args) {

        TestLightControl app = new TestLightControl();
        AppSettings settings = new AppSettings(true);
        settings.setWidth(1280);
        settings.setHeight(720);
        app.setSettings(settings);
        app.start(JmeContext.Type.Display);

    }

    @Override
    public void simpleInitApp() {
        loadScene();
        loadCamera();

    }

    private void loadScene() {

        Node sceneNode = new Node("scene");
        rootNode.attachChild(sceneNode);

        Spatial floor = addBox(sceneNode, boardExtend, 0.1f, boardExtend);
        addColor(floor, ColorRGBA.Gray, 1);

        light1Node = addLight(sceneNode, 0, ColorRGBA.Red, 2);
        light2Node = addLight(sceneNode, 0, ColorRGBA.Green, 2);
        light3Node = addLight(sceneNode, 0, ColorRGBA.Blue, 2);

    }

    private void loadCamera() {
        cam.setLocation(new Vector3f(-boardExtend, boardExtend * 0.5f, boardExtend));
        cam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
        flyCam.setMoveSpeed(10);
    }

    private Node addLight(Node parent, float radius, ColorRGBA colorRGBA, float height) {
        Node lightNode = new Node("light");
        parent.attachChild(lightNode);
        lightNode.move(0, height, 0);

        Spatial sphere = addSphere(lightNode, 0.5f);
        addColor(sphere, colorRGBA, 0);

        PointLight pointLight = new PointLight(new Vector3f(0, 0, 0));
        pointLight.setRadius(radius);
        pointLight.setEnabled(true);
        pointLight.setColor(colorRGBA);
        rootNode.addLight(pointLight);

        lightNode.addControl(new LightControl(pointLight));

        lightNode.addControl(new AbstractControl() {

            private Vector3f targetPosition;
            private Vector3f direction;
            private float speed = 8f;

            @Override
            protected void controlUpdate(float tpf) {

                //Pick a new target position
                if (targetPosition == null) {
                    targetPosition = new Vector3f(FastMath.nextRandomInt(-(int) boardExtend, (int) boardExtend),
                            spatial.getWorldTranslation().y,
                            FastMath.nextRandomInt(-(int) boardExtend, (int) boardExtend));
                }

                if (targetPosition != null) {
                    spatial.lookAt(targetPosition, Vector3f.UNIT_Y);
                    direction = spatial.getWorldRotation().getRotationColumn(2).normalize();
                    spatial.move(direction.x * tpf * speed, 0, direction.z * tpf * speed);

                    if (spatial.getWorldTranslation().distance(targetPosition) <= 0.1f) {
                        targetPosition = null; //Reset the target position so that a new one can be generated.                        
                    }

                }

            }

            @Override
            protected void controlRender(RenderManager rm, ViewPort vp) {
            }

        });

        return lightNode;
    }

    private Spatial addBox(Node parent, float xExtend, float yExtend, float zExtend) {
        Box box = new Box(xExtend, yExtend, zExtend);
        Geometry geometry = new Geometry("box", box);
        parent.attachChild(geometry);
        return geometry;
    }

    private Spatial addSphere(Node parent, float radius) {
        Sphere sphere = new Sphere(30, 30, radius);
        Geometry geometry = new Geometry("sphere", sphere);
        parent.attachChild(geometry);
        return geometry;
    }

    private Material addColor(Spatial spatial, ColorRGBA colorRGBA, int type) {
        Material material = null;

        if (type == 1) {
            material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
            material.setBoolean("UseMaterialColors", true);
            material.setColor("Ambient", colorRGBA);
            material.setColor("Diffuse", colorRGBA);

        } else if (type == 2) {
            material = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
            material.setColor("BaseColor", colorRGBA);
            material.setFloat("Metallic", 0f);
            material.setFloat("Roughness", 0.5f);

            spatial.setMaterial(material);

        } else {
            material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
            material.setColor("Color", colorRGBA);

        }

        spatial.setMaterial(material);

        return material;
    }

}

And a screenshot:

1 Like

I copied your class and tested first applying the lightcontrol, but don’t move it around. Then setting the light position without using a LightControl (ie just matching translation of the node and position of light). Here they are at red: 10, 2, 10 and green: 20, 2, 20, and blue: 30, 2, 30.

So something seems to be up.

2 Likes

It’s a material thing. Here’s the same example with PBRLighting. Example above (and original test) uses Lighting.j3md:

3 Likes

@ndebruyn can you try with different JME versions (e.g. 3.3, 3.4, 3.5, 3.6) and see if it happens in all versions? Finding the version that regression happened might be of great help to detect the issue.

Thanks for checking.
Yes, let me take some time and check in which version it started.
I do however remember from long ago, like jME 3.3 or 3.2 that it happend.
But that might have been something else.
Let me check.

1 Like

Okay I have done a quick test on 3.3.0 and it happens there as well.
So maybe we should go even further back.

EDIT: Happens on 3.2.2-stable also.

1 Like

OMG!

Could the issue be this:
vec3 viewDir = normalize(-wvPosition);

viewdir is defined in the vertex shader, so it would be affected by the vertex position? Edit: Wrong explanation, but you know what I mean. It introduces an error due to interpolation.

Ref different scales on the board changes apparent error:

So the error is more noticable in simple geometries than complex ones.

So what is the solution here?

So what is the solution here?

subdivide the mesh, I think

1 Like

Curious why this is only happening with the Lighting material but not PBRLighting?

1 Like

I think PBR does things in world space instead of view space and thus avoids a bunch of interpolation issues. Also the nature of PBR means that it will calculate some things per fragment that lighting tries to do per vertex.

This is without looking at the code again and only going from memory and remembering long-ago discussions. (I always wished Lighting.j3md had been done in world space also because then I wouldn’t have needed to write my own for trilinear mapping and stuff.)

1 Like

I think the same issue was discussed in this thread:

2 Likes

I have a side question relating to pointlights and spotlights.

The question is, if I want shadows on my spotlights or pointlights do I need to add a SpotLightShadowRenderer or PointLightShadowRenderer for each light to the viewPort?

Do we not have one for them all?

An what will the performance impact be with lets say 10 or 20 shadow renderers?

Yes, that seems so.

Thank you so much.

Another link :slight_smile: Deferred rendering:

1 Like

That depends on your scene, every geometry rendered by a shadow casting light will get rendered two additional times.

1 Like

But note: every geometry with light cast upon it was already rendering once per light.

…shadows add one extra render.

10 objects lit by 5 lights: 50 draw calls.
10 objects lit by 5 lights with shadow renderers: 100 draw calls.

Dynamic lights are expensive which is why there are techniques to use baked lighting.

Deferred rendering is the other solution… with it’s own set of completely different trade offs.

1 Like

Jme does have singlepass lighting. without checking it should come down to:

10 objects 5 lights: 10 draw calls
10 objects 4 lights + 1 shadow casting: 10 + (10 + 10) = 30
10 objects 3 lights + 2 shadow casting: 10 + (10 + 10) + (10 +10) = 50