Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accelerated env baking on the GPU #1165

Merged
merged 29 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b88be51
Accelerated env baker that runs on the GPU.
riccardobl Aug 8, 2023
0c8801f
Add render filter
riccardobl Aug 8, 2023
616c052
EnvironmentProbeControl, for simpler env baking
riccardobl Aug 8, 2023
67a3702
Add copyright and javadoc, fix some merging issues
riccardobl Aug 8, 2023
be89b88
Make lightprobes serializable on demand when baked with accelerated b…
riccardobl Aug 8, 2023
5261a98
Add javadoc fix typo
riccardobl Aug 8, 2023
7bc7670
Make EnvironmentProbeControl serializable
riccardobl Aug 8, 2023
29ffa68
Fix TestPBRSimple to use new api
riccardobl Aug 8, 2023
33e7f34
do not dispose the env image
riccardobl Aug 8, 2023
8b59c31
Add default constructor
riccardobl Aug 9, 2023
9ad84f1
Set default EnvironmentProbeControl area to Float.MAX_VALUE
riccardobl Aug 21, 2023
6c5322c
Do not call Spatial.updateModelBound() in env baker
riccardobl Aug 21, 2023
e648d6c
Fix compatibility with GL ES. Ensure mipmaps are never recomputed by …
riccardobl Sep 2, 2023
d756e17
Refactor accelerated baker code to map more closely with the original…
riccardobl Sep 2, 2023
0804ecd
Drivers workaround: do not crash if lower mips cannot be computed.
riccardobl Oct 14, 2023
4aa922d
Rename LightProbeFactory2 to FastLightProbeFactory
riccardobl Nov 3, 2023
3a63528
Add support for probe position, frustum and local tagging
riccardobl Nov 3, 2023
4df92a8
formatting and documentation
riccardobl Nov 3, 2023
a77eb2c
Remove new line
riccardobl Nov 3, 2023
da2e3b3
Add AbstractControl functionalities to EnvironmentProbeControl
riccardobl Nov 8, 2023
a846a63
Rename serializable to requiredSavableResults
riccardobl Nov 8, 2023
8314499
Replace Function with Predicate
riccardobl Nov 8, 2023
cbb2166
improvements
riccardobl Nov 10, 2023
7b87ff5
Rename env -> envMap
riccardobl Nov 10, 2023
4665aaa
JavaDoc corrections
scenemax3d Jan 21, 2024
aae61d3
JavaDoc fixes
scenemax3d Jan 22, 2024
8663388
JavaDoc fixes
scenemax3d Jan 22, 2024
d4ff52c
JavaDocs Fixes
scenemax3d Jan 22, 2024
7a77c14
JavaDocs fixes
scenemax3d Jan 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package com.jme3.environment;

import java.io.IOException;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.asset.AssetManager;
import com.jme3.environment.baker.IBLGLEnvBakerLight;
import com.jme3.environment.baker.IBLHybridEnvBakerLight;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.light.LightProbe;
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.Control;
import com.jme3.texture.Image.Format;

/**
* A control that automatically handles environment bake and rebake including
* only tagged spatials.
*
* @author Riccardo Balbo
*/
public class EnvironmentProbeControl extends LightProbe implements Control {
private final boolean USE_GL_IR = true;
private static final Logger LOG = Logger.getLogger(EnvironmentProbeControl.class.getName());


private AssetManager assetManager;
private boolean bakeNeeded = true;
private int envMapSize=256;
private Spatial spatial;
private boolean serializable = false;

private Function<Geometry, Boolean> filter = (s) -> {
return s.getUserData("tags.env") != null;
};

/**
* Tag spatial as part of the environment. Only tagged spatials will be
* rendered in the environment map.
*
* @param s
* the spatial
*/
public static void tag(Spatial s) {
if (s instanceof Node) {
Node n = (Node) s;
for (Spatial sx : n.getChildren()) {
tag(sx);
}
} else if (s instanceof Geometry) {
s.setUserData("tags.env", true);
}
}

protected EnvironmentProbeControl() {
}

public EnvironmentProbeControl(AssetManager assetManager,int size) {
this.envMapSize = size;
this.assetManager = assetManager;
this.setAreaType(AreaType.Spherical);
this.getArea().setRadius(Float.MAX_VALUE);
}

@Override
public Control cloneForSpatial(Spatial spatial) {
throw new UnsupportedOperationException();
}

public void setSerializeBakeResults(boolean v) {
serializable = v;
}

public boolean isSerializeBakeResults() {
return serializable;
}

@Override
public void setSpatial(Spatial spatial) {
spatial.addLight(this);
this.spatial = spatial;
}

@Override
public void update(float tpf) {

}

@Override
public void render(RenderManager rm, ViewPort vp) {
if (bakeNeeded) {
bakeNeeded = false;
rebakeNow(rm);
}
}

/**
* Schedule a rebake of the environment map.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Javadoc summary fragment should not be a complete sentence.
I suggest "Schedule" -> "Schedules"

*/
public void rebake() {
bakeNeeded = true;
}

/**
* Set the asset manager used to load the shaders needed for the baking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Javadoc summary fragment should not be a complete sentence.
I suggest "Set" -> "Sets"

Also, the Javadoc summary fragment should end with a period (".")

* @param assetManager
*/
public void setAssetManager(AssetManager assetManager) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setAssetManager() method is not necessary, since it is a mandatory parameter of the public constructor

public EnvironmentProbeControl(AssetManager assetManager, int size) {}

I would suggest removing it.

Copy link
Member Author

@riccardobl riccardobl Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can be used to change the asset manager used internally by the control that might not be the same used to deserialize it in some circumstances i supppose

Copy link
Contributor

@capdevon capdevon Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, but if you are not sure about this feature, you could change the visibility of the AssetManager variable from private to protected by letting the developers decide whether to add a set method or not. You also benefited from this trick by extending the LigthProbe class. More freedom and less code to maintain ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected fields are evil. They become part of the public API and provide no recourse for changing.
Always always always better to provide protected methods because at least then you can change implementations, provide backwards compatibility, etc.. With protected fields you are stuck forever.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JME technically supports multiple instances of AssetManager, so setAssetManager was there to allow to change the internal asset manager after the control is deserialized, but on second thought, maybe it only adds confusion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@riccardobl Ok, I suggest removing it.

this.assetManager = assetManager;
}

void rebakeNow(RenderManager renderManager) {
if (assetManager == null) {
LOG.log(Level.SEVERE, "AssetManager is null, cannot bake environment. Please use setAssetManager() to set it.");
return;
}
IBLHybridEnvBakerLight baker;
if(!USE_GL_IR){
baker = new IBLHybridEnvBakerLight(renderManager, assetManager, Format.RGB16F, Format.Depth, envMapSize, envMapSize);
} else {
baker = new IBLGLEnvBakerLight(renderManager, assetManager, Format.RGB16F, Format.Depth, envMapSize, envMapSize);
}
baker.setTexturePulling(isSerializeBakeResults());

baker.bakeEnvironment(spatial, Vector3f.ZERO, 0.001f, 1000f, filter);
baker.bakeSpecularIBL();
baker.bakeSphericalHarmonicsCoefficients();

setPrefilteredMap(baker.getSpecularIBL());

int[] mipSizes = getPrefilteredEnvMap().getImage().getMipMapSizes();
setNbMipMaps(mipSizes != null ? mipSizes.length : 1);

setShCoeffs(baker.getSphericalHarmonicsCoefficients());
setPosition(Vector3f.ZERO);
setReady(true);

baker.clean();
}


@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(envMapSize, "size", 256);
oc.write(serializable, "serializable", false);
oc.write(bakeNeeded, "bakeNeeded", true);
}

@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
envMapSize = ic.readInt("size", 256);
serializable = ic.readBoolean("serializable", false);
bakeNeeded = ic.readBoolean("bakeNeeded", true);
assetManager = im.getAssetManager();
}

}
31 changes: 18 additions & 13 deletions jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,36 @@
/**
* Creates LightProbes within a scene, given an EnvironmentCamera.
*
* Since this process can take a long time, you can provide a JobProgressListener that
* will be notified of the ongoing generation process when calling the makeProbe method.
* Since this process can take a long time, you can provide a
* JobProgressListener that will be notified of the ongoing generation process
* when calling the makeProbe method.
*
* The process is as follows:
* 1. Create an EnvironmentCamera
* 2. give it a position in the scene
* 3. call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Spatial)}
* 4. add the created LightProbe to a node with the {@link Node#addLight(com.jme3.light.Light) } method.
* The process is as follows: 1. Create an EnvironmentCamera 2. give it a
* position in the scene 3. call
* {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Spatial)}
* 4. add the created LightProbe to a node with the
* {@link Node#addLight(com.jme3.light.Light) } method.
*
* Optionally for step 3 call
* {@link #makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Spatial, com.jme3.environment.generation.JobProgressListener)}
* with a {@link JobProgressListener} to be notified of the progress of the generation process.
* with a {@link JobProgressListener} to be notified of the progress of the
* generation process.
*
* The generation will be split in several threads for faster generation.
* The generation will be split in several threads for faster generation.
*
* This class is entirely thread safe and can be called from any thread.
* This class is entirely thread safe and can be called from any thread.
*
* Note that in case you are using a {@link JobProgressListener}, all its
* methods will be called inside an app.enqueue callable.
* This means that it's completely safe to modify the scenegraph within the
* Listener method, but also means that the event will be delayed until next update loop.
* methods will be called inside an app.enqueue callable. This means that it's
* completely safe to modify the scenegraph within the Listener method, but also
* means that the event will be delayed until next update loop.
*
* @deprecated Use LightProbeFactory2 or EnvironmentProbeControl whenever possible.
* @see EnvironmentCamera
* @author bouquet
*/

@Deprecated
public class LightProbeFactory {

/**
Expand Down
114 changes: 114 additions & 0 deletions jme3-core/src/main/java/com/jme3/environment/LightProbeFactory2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2009-2019 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.environment;

import com.jme3.asset.AssetManager;
import com.jme3.environment.baker.IBLGLEnvBakerLight;
import com.jme3.environment.baker.IBLHybridEnvBakerLight;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.Image.Format;

/**
* A faster version of LightProbeFactory that uses accelerated Baking.
* @author Riccardo Balbo
*/
public class LightProbeFactory2 {


/**
* Creates a LightProbe with the giver EnvironmentCamera in the given scene.
* @param rm The RenderManager
* @param am The AssetManager
* @param size The size of the probe
* @param pos The position of the probe
* @param frustumNear The near frustum of the probe
* @param frustumFar The far frustum of the probe
* @param scene The scene to bake
* @return The baked LightProbe
*/
public static LightProbe makeProbe(RenderManager rm,
AssetManager am, int size,Vector3f pos, float frustumNear,float frustumFar,Spatial scene) {
IBLHybridEnvBakerLight baker=new IBLGLEnvBakerLight(rm, am, Format.RGB16F, Format.Depth, size, size);

baker.setTexturePulling(true);
baker.bakeEnvironment(scene,pos, frustumNear,frustumFar,null);
baker.bakeSpecularIBL();
baker.bakeSphericalHarmonicsCoefficients();

LightProbe probe = new LightProbe();

probe.setPosition(pos);
probe.setPrefilteredMap(baker.getSpecularIBL());

int[] mipSizes = probe.getPrefilteredEnvMap().getImage().getMipMapSizes();
probe.setNbMipMaps(mipSizes != null ? mipSizes.length : 1);

probe.setShCoeffs(baker.getSphericalHarmonicsCoefficients());
probe.setReady(true);

baker.clean();

return probe;

}




/**
* For debuging porpose only
* Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
*
* @param manager the asset manager
* @return a debug node
*/
public static Node getDebugGui(AssetManager manager, LightProbe probe) {
if (!probe.isReady()) {
throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
}

Node debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager);
debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0);

return debugNode;
}



}
Loading