Seeking documentation for light probes

I need to add a light probe to Maud. I was ready to figure out how to use light probes by reading source code and then experimenting. Then it occurred to me: we ought to have a tutorial in the wiki!

I looked in the wiki and didn’t find anything.

Any pointers?

3 Likes

This is not good but I had to read this to use probes.

2 Likes

It is a bit like passed down lore at this point so a wiki page would be awesome.

I will contribute how I load a presaved light probe from my general set.

First:

        Spatial probeHolder = getApplication().getAssetManager().loadModel(probeName);
        LightProbe probe = (LightProbe)probeHolder.getLocalLightList().get(0);
        probe.setPosition(Vector3f.ZERO);
        probeHolder.removeLight(probe);   

Which prepares “probe” to be used more generally.

Then rootNode.addLight(probe) or someNode.addLight(probe) when you want it.

Edit: and note this is not for generated probes… it’s for pregenerated probes or using probes from a standard set. (For example, the studio light probe is a pretty general probe that will work for a lot of indoor scenes.)

3 Likes

I’ll see what I can do about a wiki page. (Something tells me I should give up coding for the month of December and just work on documentation.)

The intended workflow is a mystery to me.

Suppose I have a working environment with no probes: just a floor geometry, skybox, DirectionalLight, AmbientLight, and a shadow processor. Should I do a makeProbe(), serialize it, and then restart the application with just the floor geometry, skybox, and (deserialized) probe?

Or do I need the shadow processor in addition to the probe? Will they even work together?

How do I decide where to locate the probe?

How do I decide whether I need multiple probes?

What follows are my observations about probes when last I tried them, about a year ago.

When using the probe if the model is outside the radius it will be black. Using the SDK will show the radius of the probe and whats being lit inside the radius.

The size of the world and radius of probe determines what is lit. The larger the radius, the longer for the probe to build. It can take a huge amount of time for a large radius.

I wasn’t paying attention to the shadows when I tried probes as they were there and working as expected. This being said, its the reflection of things that I found determines what to do with a probe. The probe will reflect whatever was at that position in the scene when it was built. So if a model like the hover car is in the scene when built, it will show in the reflection of certain materials the probe lights, like metal or shiny objects , even though it may not be in the scene.

Its been awhile so I am rusty on the subject so others should chime in to give more details if they can.

3 Likes

I build some trivial (uniform) light probes at runtime in the MyWorld client - will chime in as soon as I’ve some time to condense what I’m doing with them into a useful forum post.

1 Like

My knowledge on the topic might be a bit outdated, but here’s how I did it when I last used jME for something that wasn’t a short experiment.

How to create a light probe in code (kitbashed together from my old projects and current documentation, might need some testing):

EnvironmentCamera envCam = new EnvironmentCamera(); //Make an env camera
stateManager.attach(envCam);
envCam.initialize(stateManager, app); //Manually initilaize so we can add a probe before the next update happens
LightProbe probe = LightProbeFactory.makeProbe(envCam, rootNode);
probe.getArea().setRadius(10); //Set the probe's radius in world units
probe.setPosition(new Vector3f(1, 0, -2)); //Position it in 3d space
rootNode.addLight(probe);

This will add the light probe to the root node and kick off the rendering in a separate thread. After the rendering finishes, there is usually a visible change in scene lighting, so using the SDK or a custom baker to bake the light probes beforehand is kind of a must. That way the probe can be simply loaded from a j3o (as paul already posted) and is available for use immediately.

3 Likes

I do believe so. For reference, here is my current lighting appstate that I am using:

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.light.LightProbe;
import com.jme3.light.SphereProbeArea;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.post.filters.ToneMapFilter;
import com.jme3.post.ssao.SSAOFilter;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.util.SkyFactory;
import io.tlf.outside.world.JmeWorldAppState;

/**
 * @author Trevor Flynn trevorflynn@liquidcrystalstudios.com
 */
public class JmeLightingState extends BaseAppState {

    private DirectionalLight dl;
    private AmbientLight al;
    private SimpleApplication app;
    private FilterPostProcessor fpp;
    private EnvironmentCamera envCam;
    private LightProbe probe;
    private Node lightingNode;
    private volatile Spatial tracking = null;

    private short renderSteps = 0;
    private float probeRadius = 100;
    private boolean probing = true;

    @Override
    protected void initialize(Application a) {
        app = (SimpleApplication) a;
        app.getViewPort().setBackgroundColor(ColorRGBA.White);
        JmeWorldAppState world = app.getStateManager().getState(JmeWorldAppState.class);
        if (world == null) {
            lightingNode = (Node) app.getRootNode().getChild("world"); //For iso-test
        } else {
            lightingNode = world.getGlobalNode();
        }

        //Lights
        al = new AmbientLight();
        al.setColor(ColorRGBA.White.mult(1.3f));
        lightingNode.addLight(al);

        dl = new DirectionalLight();
        dl.setDirection(new Vector3f(-1, -1, -1));
        lightingNode.addLight(dl);
        dl.setColor(ColorRGBA.White);

        final int SHADOWMAP_SIZE=4096;
        /*
        DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(app.getAssetManager(), SHADOWMAP_SIZE, 4);
        dlsr.setLight(dl);
        dlsr.setLambda(0.55f);
        dlsr.setShadowIntensity(0.8f);
        dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
        //dlsr.displayDebug();
        app.getViewPort().addProcessor(dlsr);
*/
        DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(app.getAssetManager(), SHADOWMAP_SIZE, 4);
        dlsf.setLight(dl);
        dlsf.setLambda(1f);
        dlsf.setShadowIntensity(0.4f);
        dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
        dlsf.setEnabled(true);

        //Filter
        fpp = new FilterPostProcessor(app.getAssetManager());
        fpp.addFilter(new FXAAFilter());
        fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
        fpp.addFilter(dlsf);
        fpp.addFilter(new SSAOFilter(5.1f, 1.2f, 0.2f, 0.1f));
        app.getViewPort().addProcessor(fpp);

        //Spatial sky = SkyFactory.createSky(app.getAssetManager(), "Textures/test/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
        //app.getRootNode().attachChild(sky);

        //Env Cam
        envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0));
        app.getStateManager().attach(envCam);

        //LightsDebugState debugState = new LightsDebugState();
        //app.getStateManager().attach(debugState);

        //MaterialDebugAppState debug = new MaterialDebugAppState();
        //debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", app.getRootNode());
        //debug.registerBinding("Common/ShaderLib/PBR.glsllib", app.getRootNode());
        //getStateManager().attach(debug);

    }

    @Override
    protected void cleanup(Application a) {
        app.getRootNode().removeLight(dl);
        app.getViewPort().removeProcessor(fpp);
        app.getStateManager().detach(envCam);
    }

    @Override
    public void update(float tpf) {
        if (probing) {
            renderSteps++;
            if (renderSteps == 2) { //Give the scene a frame to update
                lightingNode.removeFromParent();
                probe = LightProbeFactory.makeProbe(app.getStateManager().getState(EnvironmentCamera.class), app.getRootNode(), new JobProgressAdapter<LightProbe>() {

                    @Override
                    public void done(LightProbe result) {
                        //System.out.println("PBR Probe results in");
                    }
                });
                probe.getArea().setRadius(probeRadius);
                app.getRootNode().addLight(probe);
            } else if (renderSteps > 10) {
                app.getRootNode().attachChild(lightingNode);
                probing = false;
                renderSteps = 0;
            }
        }
        if (probe != null && tracking != null) {
            probe.getArea().setCenter(tracking.getLocalTranslation());
        }
    }

    public void setTracking(Spatial spatial) {
        tracking = spatial;
    }

    public void reprobe() {
        probing = true;
    }

    public void setProbeLocation(Vector3f pos) {
        probe.setPosition(pos);
    }

    public float getProbeRadius() {
        return probeRadius;
    }

    public void setProbeRadius(float probeRadius) {
        this.probeRadius = probeRadius;
    }

    @Override
    protected void onEnable() {
        dl.setEnabled(true);
        envCam.setEnabled(true);
        reprobe();
        app.getViewPort().addProcessor(fpp);
    }

    @Override
    protected void onDisable() {
        dl.setEnabled(false);
        envCam.setEnabled(false);
        app.getViewPort().removeProcessor(fpp);
        if (probe != null) {
            app.getRootNode().removeLight(probe);
        }
    }

}
2 Likes

Thanks for all the pointers!

A bit late responding, but here’s the setup I use in the MyWorld client to generate simple uniform lighting probes (these are handy for OSR model viewing windows, for one):

public static LightProbe generateUniform(Application app, float intensity, Runnable completionListener) {
		intensity = Math.min(1.0f, Math.max(intensity, 0)); // cap intensity between 0 & 1
		
		Geometry skyBox = new Geometry("sky", new Box(1, 1, 1));
		
		Material unshaded = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
		unshaded.setColor("Color", ColorRGBA.White.clone().multLocal(intensity));
		// Need to disable face culling since we're interested in the inside of the mesh, not the outside
		unshaded.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
		
		skyBox.setMaterial(unshaded);
		skyBox.setQueueBucket(Bucket.Sky);
		
		Node scene = new Node("Snapshot");
		scene.attachChild(skyBox);
		scene.updateGeometricState();
		
		EnvironmentCamera envCam = new EnvironmentCamera(4);
		envCam.initialize(app.getStateManager(), app);
		app.getStateManager().attach(envCam);
		
		return LightProbeFactory.makeProbe(envCam, scene, new JobProgressListener<LightProbe>() {

			@Override
			public void start() {
			}

			@Override
			public void step(String message) {
			}

			@Override
			public void progress(double value) {
			}

			@Override
			public void done(LightProbe result) {
				
				if(completionListener != null) {
					completionListener.run();
				}
				
				app.enqueue(() -> {
					app.getStateManager().detach(envCam);
				});
			}
			
		});
	}

This is used like:

probe = LightUtils.generateUniform(app, 0.5f);
probe.getArea().setCenter(Vector3f.ZERO.clone());
probe.getArea().setRadius(1000);
sceneNode.addLight(probe);

Note that even though generating the light probe is an asynchronous process, they’re safe to attach immediately - they’re aware of whether or not they’re ready for use and won’t cause problems if they’re attached before they’re ready.

2 Likes

I have tried this , but in Android context & I cannot see a 360 reflections on my Spatials , even though they are reflecting light properly , is there something that should be added ?

1 Like

I don’t know as I never tried running PBR on android :frowning:
I did however try this code out after posting it and it works fine on desktop.

2 Likes

Thanks, @danielp. The concepts are starting to fall into place for me.

One thing I recently realized is that light probes are a supplement to directionals and other lights, not a replacement for them.

Another thing, there are 2 sizes associated with a probe:

  • its radius (in world units, which helps define its area of effect) and
  • the size of its environment map (in pixels, which determines how detailed specular reflections can be).
2 Likes

I was under the opposite impression - other light types do work with the shaders, but I think that baking directionality and global ambience into a light probe would give a better result (assuming that the data was closer to real-world conditions than, say, a single directional vector for directional light). I could be totally off there, however - curious to get some feedback as I’ve not experimented with that yet

1 Like

I experimented by removing the DirectionalLight from TestPBRLighting after adding the LightProbe. Once the probe was ready, the hovertank illuminated only by the probe looked great, so apparently light probes aren’t supplementary in the way I imagined.

Perhaps the PBR material uses directional lights only if there’s no ready probe in range. I browsed “PBRLighting.frag” and wasn’t convinced. Still figuring this out…

3 Likes

Was it illuminated by the hdr map? iirc the tank example also has the Path.hdr file which has lighting in it.

1 Like

My understanding is that the HDR by itself can’t illuminate the tank model. Information from the HDR gets baked into the probe, and the probe illuminates the model.

1 Like

I believe that is correct. Without the light probe, the hdr lighting does not work.

2 Likes

In fact , I am using only DirectionalLight plus some PointLights with different colors & it’s good illuminated on Android , but still havenot tried lightProbes yet , because it’s not supported in android java code but what I see from my experiments that PBRLighting Materials could be lit up by any light except ambient & they gave different results :slightly_smiling_face:, I donot know if I am true or not , but that were live experiments

For Android, indirect lighting cannot be obtained through code, you can only add indirect lighting through the SceneComposer of the SDK. Then load j3o in android. :grinning:

1 Like