PBR has come

Merge branch 'PBRisComing'

# Conflicts:
#	jme3-core/src/main/java/com/jme3/material/Material.java
#	jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
define_list_fix
Nehon 9 years ago
commit 16023fa481
  1. 337
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 291
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  3. 176
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java
  4. 59
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java
  5. 67
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java
  6. 265
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  7. 89
      jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java
  8. 177
      jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java
  9. 243
      jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java
  10. 947
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java
  11. 214
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  12. 70
      jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java
  13. 22
      jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java
  14. 9
      jme3-core/src/main/java/com/jme3/light/Light.java
  15. 266
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  16. 208
      jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java
  17. 55
      jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java
  18. 107
      jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java
  19. 10
      jme3-core/src/main/java/com/jme3/material/Material.java
  20. 4
      jme3-core/src/main/java/com/jme3/material/Technique.java
  21. 45
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  22. 8
      jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
  23. 4
      jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java
  24. 251
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  25. 4
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java
  26. 2
      jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java
  27. 2
      jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
  28. 9
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  29. 7
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  30. 2
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  31. 238
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  32. 307
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  33. 65
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  34. 45
      jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md
  35. 22
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
  36. 8
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
  37. 8
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
  38. 25
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
  39. 14
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert
  40. 17
      jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
  41. 154
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  42. BIN
      jme3-core/src/main/resources/Common/Textures/integrateBRDF.ktx
  43. 21
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  44. 6
      jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java
  45. 354
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java
  46. 279
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java
  47. 46
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java
  48. 60
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java
  49. 58
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java
  50. 2
      jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java
  51. 90
      jme3-examples/src/main/java/jme3test/light/TestColorApp.java
  52. 131
      jme3-examples/src/main/java/jme3test/light/TestShadowBug.java
  53. 96
      jme3-examples/src/main/java/jme3test/light/TestTangentCube.java
  54. 71
      jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java
  55. 141
      jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
  56. 199
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
  57. 387
      jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java
  58. 157
      jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java
  59. 180
      jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java
  60. 112
      jme3-examples/src/main/java/jme3test/texture/ktx/TestLoadKtx.java
  61. 13
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m
  62. 12
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m
  63. 9
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m
  64. 11
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m
  65. BIN
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png
  66. BIN
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png
  67. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Base_Color.png
  68. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Emissive.png
  69. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Metallic.png
  70. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Normal.png
  71. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Roughness.png
  72. 14
      jme3-testdata/src/main/resources/Models/Tank/tank.j3m
  73. BIN
      jme3-testdata/src/main/resources/Models/Tank/tank.j3o
  74. 3
      jme3-testdata/src/main/resources/Models/Tank/tank.j3odata
  75. BIN
      jme3-testdata/src/main/resources/Scenes/PBR/spheres.j3o
  76. BIN
      jme3-testdata/src/main/resources/Textures/Sky/Path.hdr
  77. 9
      jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR.j3m
  78. 8
      jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR2.j3m
  79. BIN
      jme3-testdata/src/main/resources/Textures/ktx/down-reference.ktx
  80. BIN
      jme3-testdata/src/main/resources/Textures/ktx/irrMap.ktx
  81. BIN
      jme3-testdata/src/main/resources/Textures/ktx/prefilteredMap.ktx
  82. BIN
      jme3-testdata/src/main/resources/Textures/ktx/rgb-amg-reference.ktx
  83. BIN
      jme3-testdata/src/main/resources/Textures/ktx/rgb-mipmap-reference.ktx
  84. BIN
      jme3-testdata/src/main/resources/Textures/ktx/rgba-reference.ktx
  85. BIN
      jme3-testdata/src/main/resources/Textures/ktx/up-reference.ktx
  86. 131
      sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbe.java
  87. 99
      sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeButtonProperty.java
  88. 42
      sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeProgressHandler.java
  89. 57
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoControl.java
  90. 68
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoFactory.java
  91. 59
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightProbeGizmoControl.java
  92. 23
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn
  93. 11
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag
  94. 37
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md
  95. 168
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/shape/ProbeRadiusShape.java

@ -0,0 +1,337 @@
/*
* Copyright (c) 2009-2015 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.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.environment.generation.JobProgressListener;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
/**
* A 360 camera that can capture a cube map of a scene, and then generate the
* Prefiltered Environment cube Map and the Irradiance cube Map needed for PBR
* indirect lighting
*
* @see LightProbeFactory
* @see LightProbe
*
* @author Nehon
*/
public class EnvironmentCamera extends BaseAppState {
protected static Vector3f[] axisX = new Vector3f[6];
protected static Vector3f[] axisY = new Vector3f[6];
protected static Vector3f[] axisZ = new Vector3f[6];
protected Image.Format imageFormat = Image.Format.RGB16F;
//Axis for cameras
static {
//PositiveX axis(left, up, direction)
axisX[0] = Vector3f.UNIT_Z.mult(1f);
axisY[0] = Vector3f.UNIT_Y.mult(-1f);
axisZ[0] = Vector3f.UNIT_X.mult(1f);
//NegativeX
axisX[1] = Vector3f.UNIT_Z.mult(-1f);
axisY[1] = Vector3f.UNIT_Y.mult(-1f);
axisZ[1] = Vector3f.UNIT_X.mult(-1f);
//PositiveY
axisX[2] = Vector3f.UNIT_X.mult(-1f);
axisY[2] = Vector3f.UNIT_Z.mult(1f);
axisZ[2] = Vector3f.UNIT_Y.mult(1f);
//NegativeY
axisX[3] = Vector3f.UNIT_X.mult(-1f);
axisY[3] = Vector3f.UNIT_Z.mult(-1f);
axisZ[3] = Vector3f.UNIT_Y.mult(-1f);
//PositiveZ
axisX[4] = Vector3f.UNIT_X.mult(-1f);
axisY[4] = Vector3f.UNIT_Y.mult(-1f);
axisZ[4] = Vector3f.UNIT_Z;
//NegativeZ
axisX[5] = Vector3f.UNIT_X.mult(1f);
axisY[5] = Vector3f.UNIT_Y.mult(-1f);
axisZ[5] = Vector3f.UNIT_Z.mult(-1f);
}
protected Image images[];
protected ViewPort[] viewports;
protected FrameBuffer[] framebuffers;
protected ByteBuffer[] buffers;
protected Vector3f position = new Vector3f();
protected ColorRGBA backGroundColor;
protected int size = 128;
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
/**
* Creates an EnvironmentCamera with a size of 128
*/
public EnvironmentCamera() {
}
/**
* Creates an EnvironmentCamera with the given size.
*
* @param size the size of the resulting texture.
*/
public EnvironmentCamera(int size) {
this.size = size;
}
/**
* Creates an EnvironmentCamera with the given size, and the given position
*
* @param size the size of the resulting texture.
* @param position the position of the camera.
*/
public EnvironmentCamera(int size, Vector3f position) {
this.size = size;
this.position.set(position);
}
/**
* Creates an EnvironmentCamera with the given size, and the given position
*
* @param size the size of the resulting texture, and the given ImageFormat.
* @param position the position of the camera.
* @param imageFormat the ImageFormat to use for the resulting texture.
*/
public EnvironmentCamera(int size, Vector3f position, Image.Format imageFormat) {
this.size = size;
this.position.set(position);
this.imageFormat = imageFormat;
}
/**
* Takes a snapshot of the surrounding scene.
*
* @param scene the scene to snapshot.
* @param done a callback to call when the snapshot is done.
*/
public void snapshot(final Spatial scene, final JobProgressListener<TextureCubeMap> done) {
getApplication().enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
SnapshotJob job = new SnapshotJob(done, scene);
jobs.add(job);
return null;
}
});
}
@Override
public void render(final RenderManager renderManager) {
if (jobs.isEmpty()) {
return;
}
final SnapshotJob job = jobs.get(0);
for (int i = 0; i < 6; i++) {
viewports[i].clearScenes();
viewports[i].attachScene(job.scene);
renderManager.renderViewPort(viewports[i], 0.16f);
buffers[i] = BufferUtils.createByteBuffer(size * size * imageFormat.getBitsPerPixel() / 8);
renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat);
images[i] = new Image(imageFormat, size, size, buffers[i], ColorSpace.Linear);
}
final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat);
job.callback.done(map);
map.getImage().dispose();
jobs.remove(0);
}
public int getSize() {
return size;
}
public Vector3f getPosition() {
return position;
}
/**
* Sets the camera position in world space.
*
* @param position the position in world space
*/
public void setPosition(final Vector3f position) {
this.position.set(position);
if (viewports == null) {
return;
}
for (final ViewPort viewPort : viewports) {
viewPort.getCamera().setLocation(position);
}
}
@Override
protected void initialize(Application app) {
this.backGroundColor = app.getViewPort().getBackgroundColor();
final Camera[] cameras = new Camera[6];
Texture2D[] textures = new Texture2D[6];
viewports = new ViewPort[6];
framebuffers = new FrameBuffer[6];
buffers = new ByteBuffer[6];
images = new Image[6];
for (int i = 0; i < 6; i++) {
cameras[i] = createOffCamera(size, position, axisX[i], axisY[i], axisZ[i]);
viewports[i] = createOffViewPort("EnvView" + i, cameras[i]);
framebuffers[i] = createOffScreenFrameBuffer(size, viewports[i]);
textures[i] = new Texture2D(size, size, imageFormat);
framebuffers[i].setColorTexture(textures[i]);
}
}
@Override
protected void cleanup(Application app) {
this.backGroundColor = null;
for (final FrameBuffer frameBuffer : framebuffers) {
frameBuffer.dispose();
}
for (final Image image : images) {
if( image != null){
image.dispose();
}
}
}
/**
* returns the images format used for the generated maps.
*
* @return
*/
public Image.Format getImageFormat() {
return imageFormat;
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
/**
* Creates an off camera
*
* @param mapSize the size
* @param worldPos the position
* @param axisX the x axis
* @param axisY the y axis
* @param axisZ tha z axis
* @return
*/
protected Camera createOffCamera(final int mapSize, final Vector3f worldPos, final Vector3f axisX, final Vector3f axisY, final Vector3f axisZ) {
final Camera offCamera = new Camera(mapSize, mapSize);
offCamera.setLocation(worldPos);
offCamera.setAxes(axisX, axisY, axisZ);
offCamera.setFrustumPerspective(90f, 1f, 1, 1000);
offCamera.setLocation(position);
return offCamera;
}
/**
* creates an offsceen VP
*
* @param name
* @param offCamera
* @return
*/
protected ViewPort createOffViewPort(final String name, final Camera offCamera) {
final ViewPort offView = new ViewPort(name, offCamera);
offView.setClearFlags(true, true, true);
offView.setBackgroundColor(backGroundColor);
return offView;
}
/**
* create an offscreen frame buffer.
*
* @param mapSize
* @param offView
* @return
*/
protected FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView) {
// create offscreen framebuffer
final FrameBuffer offBuffer = new FrameBuffer(mapSize, mapSize, 1);
offBuffer.setDepthBuffer(Image.Format.Depth);
offView.setOutputFrameBuffer(offBuffer);
return offBuffer;
}
/**
* An inner class to keep track on a snapshot job.
*/
protected class SnapshotJob {
JobProgressListener<TextureCubeMap> callback;
Spatial scene;
public SnapshotJob(JobProgressListener callback, Spatial scene) {
this.callback = callback;
this.scene = scene;
}
}
}

@ -0,0 +1,291 @@
/*
* Copyright (c) 2009-2015 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.light.LightProbe;
import com.jme3.environment.generation.JobProgressListener;
import com.jme3.environment.generation.PrefilteredEnvMapFaceGenerator;
import com.jme3.environment.generation.IrradianceMapGenerator;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.app.Application;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* This Factory allows to create LightProbes within a scene given an EnvironmentCamera.
*
* Since the process can be long, you can provide a JobProgressListener that
* will be notified of the ongoing generation process when calling the makeProbe method.
*
* The process is the folowing :
* 1. Create an EnvironmentCamera
* 2. give it a position in the scene
* 3. call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* 4. add the created LightProbe to a node with the {@link Node#addLight(com.jme3.light.Light) } method.
*
* Optionally for step 3 call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
* 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.
*
* This class is entirely thread safe and can be called from any thread.
*
* Note that in case you are using a {@link JobProgressListener} all the its
* method will be called inside and app.enqueu callable.
* This means that it's completely safe to modify the scenegraph within the
* Listener method, but also means that the even will be delayed until next update loop.
*
* @see EnvironmentCamera
* @author bouquet
*/
public class LightProbeFactory {
/**
* Creates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* If you want to monitor the process use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
*
*
*
* @see LightProbe
* @see EnvironmentCamera
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @return the created LightProbe
*/
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene) {
return makeProbe(envCam, scene, null);
}
/**
* Creates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param listener the listener of the genration progress.
* @return the created LightProbe
*/
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
final LightProbe probe = new LightProbe();
probe.setPosition(envCam.getPosition());
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
}
});
return probe;
}
/**
* Updates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
*
* @param probe the Light probe to update
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param listener the listener of the genration progress.
* @return the created LightProbe
*/
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
envCam.setPosition(probe.getPosition());
probe.setReady(false);
if(probe.getIrradianceMap() != null) {
probe.getIrradianceMap().getImage().dispose();
probe.getPrefilteredEnvMap().getImage().dispose();
}
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
}
});
return probe;
}
/**
* Internally called to generate the maps.
* This method will spawn 7 thread (one for the IrradianceMap, and one for each face of the prefiltered env map).
* Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done.
*
* @param envMap the raw env map rendered by the env camera
* @param probe the LigthProbe to generate maps for
* @param app the Application
* @param listener a progress listener. (can be null if no progress reporting is needed)
*/
private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, final JobProgressListener<LightProbe> listener) {
IrradianceMapGenerator irrMapGenerator;
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6));
int size = envMap.getImage().getWidth();
irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap());
jobState.executor.execute(irrMapGenerator);
for (int i = 0; i < pemGenerators.length; i++) {
pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i));
pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getPrefilteredEnvMap());
jobState.executor.execute(pemGenerators[i]);
}
}
/**
* An inner class to keep the state of a generation process
*/
private static class JobState {
double progress[] = new double[7];
boolean done[] = new boolean[7];
ScheduledThreadPoolExecutor executor;
boolean started = false;
public JobState(ScheduledThreadPoolExecutor executor) {
this.executor = executor;
}
boolean isDone() {
for (boolean d : done) {
if (d == false) {
return false;
}
}
return true;
}
float getProgress() {
float mean = 0;
for (double progres : progress) {
mean += progres;
}
return mean / 7f;
}
}
/**
* An inner JobProgressListener to controll the genration process and properly clean up when it's done
*/
private static class JobListener extends JobProgressAdapter<Integer> {
JobProgressListener<LightProbe> globalListener;
JobState jobState;
LightProbe probe;
int index;
public JobListener(JobProgressListener<LightProbe> globalListener, JobState jobState, LightProbe probe, int index) {
this.globalListener = globalListener;
this.jobState = jobState;
this.probe = probe;
this.index = index;
}
@Override
public void start() {
if (globalListener != null && !jobState.started) {
jobState.started = true;
globalListener.start();
}
}
@Override
public void progress(double value) {
jobState.progress[index] = value;
if (globalListener != null) {
globalListener.progress(jobState.getProgress());
}
}
@Override
public void done(Integer result) {
if (globalListener != null) {
if (result < 6) {
globalListener.step("Prefiltered env map face " + result + " generated");
} else {
globalListener.step("Irradiance map generated");
}
}
jobState.done[index] = true;
if (jobState.isDone()) {
probe.setReady(true);
if (globalListener != null) {
globalListener.done(probe);
}
jobState.executor.shutdownNow();
}
}
}
}

@ -0,0 +1,176 @@
/*
* Copyright (c) 2009-2015 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.generation;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.texture.TextureCubeMap;
import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
/**
*
* Generates the Irrafiance map for PBR. This job can be lauched from a separate
* thread.
*
* TODO there is a lot of duplicate code here with the EnvMapUtils.
*
* @author Nehon
*/
//TODO there is a lot of duplicate code here with the EnvMapUtils. We should,
//either leverage the code from the util class either remove it and only allow
//parallel generation using this runnable.
public class IrradianceMapGenerator extends RunnableWithProgress {
private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private TextureCubeMap sourceMap;
private TextureCubeMap store;
private final Application app;
/**
* Creates an Irradiance map generator. The app is needed to enqueue the
* call to the EnvironmentCamera when the generation is done, so that this
* process is thread safe.
*
* @param app the Application
* @param listener
*/
public IrradianceMapGenerator(Application app, JobProgressListener<Integer> listener) {
super(listener);
this.app = app;
}
/**
* Fills all the genration parameters
*
* @param sourceMap the source cube map
* @param targetMapSize the size of the generated map (width or height in
* pixel)
* @param fixSeamsMethod the method used to fix seams as described here
* {@link EnvMapUtils.FixSeamsMethod}
*
* @param store The cube map to store the result in.
*/
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
this.sourceMap = sourceMap;
this.targetMapSize = targetMapSize;
this.fixSeamsMethod = fixSeamsMethod;
this.store = store;
reset();
}
@Override
public void run() {
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.start();
return null;
}
});
try {
Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
store = generateIrradianceMap(shCoeffs, targetMapSize, fixSeamsMethod, store);
} catch (Exception e) {
e.printStackTrace();
}
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.done(6);
return null;
}
});
}
/**
* Generates the Irradiance map (used for image based difuse lighting) from
* Spherical Harmonics coefficients previously computed with
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
*
* @param shCoeffs the SH coeffs
* @param targetMapSize the size of the irradiance map to generate
* @param fixSeamsMethod the method to fix seams
* @param store
* @return The irradiance cube map for the given coefficients
*/
public TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap irrCubeMap = store;
setEnd(6 + 6);
for (int i = 0; i < 6; i++) {
ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * store.getImage().getFormat().getBitsPerPixel() / 8);
irrCubeMap.getImage().setData(i, buf);
progress();
}
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
float[] shDir = new float[9];
CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < targetMapSize; y++) {
for (int x = 0; x < targetMapSize; x++) {
EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
EnvMapUtils.evalShBasis(texelVect, shDir);
color.set(0, 0, 0, 0);
for (int i = 0; i < EnvMapUtils.NUM_SH_COEFFICIENT; i++) {
color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
1.0f);
}
//clamping the color because very low value close to zero produce artifacts
color.r = Math.max(0.0001f, color.r);
color.g = Math.max(0.0001f, color.g);
color.b = Math.max(0.0001f, color.b);
envMapWriter.setPixel(x, y, face, color);
}
}
progress();
}
return irrCubeMap;
}
}

@ -0,0 +1,59 @@
/*
* Copyright (c) 2009-2015 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.generation;
/**
* Abstract Adapter class that implements optional methods of JobProgressListener.
* Extends this class instead of implementing a JobProgressListener if you need
* only a subset of method implemented.
*
* @author nehon
* @param <T>
*/
public abstract class JobProgressAdapter<T> implements JobProgressListener<T>{
@Override
public void progress(double value) {
}
@Override
public void start() {
}
@Override
public void step(String message) {
}
@Override
public abstract void done(T result);
}

@ -0,0 +1,67 @@
/*
* Copyright (c) 2009-2015 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.generation;
/**
* An interface listener that will be notified of the progress of an asynchronous
* generation job.
*
*
* @author nehon
* @param <T> The type of object generated.
*/
public interface JobProgressListener<T> {
/**
* Called when the process starts.
*/
public void start();
/**
* Can be called when a step of the process has been completed with a relevant message.
* @param message the message stating of the paricular step completion.
*/
public void step(String message);
/**
* Called when the process has made some progress.
* @param value a value from 0 to 1 representing the percentage of completion of the process.
*/
public void progress(double value);
/**
* Called when the process is done.
* @param result the object generated by the process.
*/
public void done(T result);
}

@ -0,0 +1,265 @@
/*
* Copyright (c) 2009-2015 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.generation;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.math.ColorRGBA;
import static com.jme3.math.FastMath.abs;
import static com.jme3.math.FastMath.clamp;
import static com.jme3.math.FastMath.pow;
import static com.jme3.math.FastMath.sqrt;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.texture.TextureCubeMap;
import static com.jme3.environment.util.EnvMapUtils.getHammersleyPoint;
import static com.jme3.environment.util.EnvMapUtils.getRoughnessFromMip;
import static com.jme3.environment.util.EnvMapUtils.getSampleFromMip;
import static com.jme3.environment.util.EnvMapUtils.getVectorFromCubemapFaceTexCoord;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* Generates one face of the prefiltered environnement map for PBR. This job can
* be lauched from a separate thread.
*
* TODO there is a lot of duplicate code here with the EnvMapUtils.
*
* @author Nehon
*/
//TODO there is a lot of duplicate code here with the EnvMapUtils. We should,
//either leverage the code from the util class either remove it and only allow
//parallel generation using this runnable.
public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
private final static Logger log = Logger.getLogger(PrefilteredEnvMapFaceGenerator.class.getName());
private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private TextureCubeMap sourceMap;
private TextureCubeMap store;
private final Application app;
private int face = 0;
Vector4f Xi = new Vector4f();
Vector3f H = new Vector3f();
Vector3f tmp = new Vector3f();
ColorRGBA c = new ColorRGBA();
Vector3f tmp1 = new Vector3f();
Vector3f tmp2 = new Vector3f();
Vector3f tmp3 = new Vector3f();
/**
* Creates a pem generator for the given face. The app is needed to enqueue
* the call to the EnvironmentCamera when the generation is done, so that
* this process is thread safe.
*
* @param app the Application
* @param face the face to generate
* @param listener
*/
public PrefilteredEnvMapFaceGenerator(Application app, int face, JobProgressListener<Integer> listener) {
super(listener);
this.app = app;
this.face = face;
}
/**
* Fills all the genration parameters
*
* @param sourceMap the source cube map
* @param targetMapSize the size of the generated map (width or height in
* pixel)
* @param fixSeamsMethod the method used to fix seams as described here
* {@link EnvMapUtils.FixSeamsMethod}
*
* @param store The cube map to store the result in.
*/
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
this.sourceMap = sourceMap;
this.targetMapSize = targetMapSize;
this.fixSeamsMethod = fixSeamsMethod;
this.store = store;
init();
}
private void init(){
Xi.set(0, 0, 0, 0);
H.set(0, 0, 0);
tmp.set(0, 0, 0);
c.set(1, 1, 1, 1);
tmp1.set(0, 0, 0);
tmp2.set(0, 0, 0);
tmp3.set(0, 0, 0);
reset();
}
@Override
public void run() {
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.start();
return null;
}
});
store = generatePrefilteredEnvMap(sourceMap, targetMapSize, fixSeamsMethod, store);
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.done(face);
return null;
}
});
}
/**
* Generates the prefiltered env map (used for image based specular
* lighting) With the GGX/Shlick brdf
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
* Note that the output cube map is in RGBA8 format.
*
* @param sourceEnvMap
* @param targetMapSize the size of the irradiance map to generate
* @param store
* @param fixSeamsMethod the method to fix seams
* @return The irradiance cube map for the given coefficients
*/
private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap pem = store;
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
setEnd(nbMipMap);
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
for (int y = 0; y < targetMipMapSize; y++) {
for (int x = 0; x < targetMipMapSize; x++) {
color.set(0, 0, 0);
getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, EnvMapUtils.FixSeamsMethod.Wrap);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y,0.0001f), Math.max(color.z, 0.0001f), 1);
log.log(Level.FINE, "coords {0},{1}", new Object[]{x, y});
targetWrapper.setPixel(x, y, face, mipLevel, outColor);
}
}
progress();
}
return pem;
}
private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
Vector3f prefilteredColor = store;
float totalWeight = 0.0f;
// a = roughness² and a2 = a²
float a2 = roughness * roughness;
a2 *= a2;
a2 *= 10;
for (int i = 0; i < numSamples; i++) {
Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H);
H.normalizeLocal();
tmp.set(H);
float NoH = N.dot(tmp);
Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
float NoL = clamp(N.dot(L), 0.0f, 1.0f);
if (NoL > 0) {
envMapReader.getPixel(L, c);
prefilteredColor.setX(prefilteredColor.x + c.r * NoL);
prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
prefilteredColor.setZ(prefilteredColor.z + c.b * NoL);
totalWeight += NoL;
}
}
return prefilteredColor.divideLocal(totalWeight);
}
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {
if (store == null) {
store = new Vector3f();
}
float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
float sinTheta = sqrt(1f - cosTheta * cosTheta);
float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
Vector3f upVector = Vector3f.UNIT_X;
if (abs(normal.z) < 0.999) {
upVector = Vector3f.UNIT_Y;
}
Vector3f tangentX = tmp1.set(upVector).crossLocal(normal).normalizeLocal();
Vector3f tangentY = tmp2.set(normal).crossLocal(tangentX);
// Tangent to world space
tangentX.multLocal(sinThetaCosPhi);
tangentY.multLocal(sinThetaSinPhi);
tmp3.set(normal).multLocal(cosTheta);
// Tangent to world space
store.set(tangentX).addLocal(tangentY).addLocal(tmp3);
return store;
}
}

@ -0,0 +1,89 @@
/*
* Copyright (c) 2009-2015 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.generation;
/**
*
* Abstract runnable that can report its progress
*
* @author Nehon
*/
public abstract class RunnableWithProgress implements Runnable {
private int progress;
private int end;
protected JobProgressListener listener;
public RunnableWithProgress() {
}
public RunnableWithProgress(JobProgressListener listener) {
this.listener = listener;
}
/**
* set the end step value of the process.
*
* @param end
*/
protected void setEnd(int end) {
this.end = end;
}
/**
* return the curent progress of the process.
*
* @return
*/
public double getProgress() {
return (double) progress / (double) end;
}
/**
* adds one progression step to the process.
*/
protected void progress() {
progress++;
if (listener != null) {
listener.progress(getProgress());
}
}
/**
* resets the progression of the process.
*/
protected void reset() {
progress = 0;
}
}

@ -0,0 +1,177 @@
/*
* Copyright (c) 2009-2015 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.util;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
*
* A debuging shape for a BoundingSphere
* Consists of 3 axis aligned circles.
*
* @author nehon
*/
public class BoundingSphereDebug extends Mesh {
protected int vertCount;
protected int triCount;
protected int radialSamples = 32;
protected boolean useEvenSlices;
protected boolean interior;
/**
* the distance from the center point each point falls on
*/
public float radius;
public float getRadius() {
return radius;
}
public BoundingSphereDebug() {
setGeometryData();
setIndexData();
}
/**
* builds the vertices based on the radius
*/
private void setGeometryData() {
setMode(Mode.Lines);
FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 3);
FloatBuffer colBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 4);
setBuffer(Type.Position, 3, posBuf);
setBuffer(Type.Color, 4, colBuf);
// generate geometry
float fInvRS = 1.0f / radialSamples;
// Generate points on the unit circle to be used in computing the mesh
// points on a sphere slice.
float[] afSin = new float[(radialSamples + 1)];
float[] afCos = new float[(radialSamples + 1)];
for (int iR = 0; iR < radialSamples; iR++) {
float fAngle = FastMath.TWO_PI * fInvRS * iR;
afCos[iR] = FastMath.cos(fAngle);
afSin[iR] = FastMath.sin(fAngle);
}
afSin[radialSamples] = afSin[0];
afCos[radialSamples] = afCos[0];
for (int iR = 0; iR <= radialSamples; iR++) {
posBuf.put(afCos[iR])
.put(afSin[iR])
.put(0);
colBuf.put(ColorRGBA.Blue.r)
.put(ColorRGBA.Blue.g)
.put(ColorRGBA.Blue.b)
.put(ColorRGBA.Blue.a);
}
for (int iR = 0; iR <= radialSamples; iR++) {
posBuf.put(afCos[iR])
.put(0)
.put(afSin[iR]);
colBuf.put(ColorRGBA.Green.r)
.put(ColorRGBA.Green.g)
.put(ColorRGBA.Green.b)
.put(ColorRGBA.Green.a);
}
for (int iR = 0; iR <= radialSamples; iR++) {
posBuf.put(0)
.put(afCos[iR])
.put(afSin[iR]);
colBuf.put(ColorRGBA.Yellow.r)
.put(ColorRGBA.Yellow.g)
.put(ColorRGBA.Yellow.b)
.put(ColorRGBA.Yellow.a);
}
updateBound();
setStatic();
}
/**
* sets the indices for rendering the sphere.
*/
private void setIndexData() {
// allocate connectivity
int nbSegments = (radialSamples) * 3;
ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments);
setBuffer(Type.Index, 2, idxBuf);
int idx = 0;
int segDone = 0;
while (segDone < nbSegments) {
idxBuf.put((short) idx);
idxBuf.put((short) (idx + 1));
idx++;
segDone++;
if (segDone == radialSamples || segDone == radialSamples * 2) {
idx++;
}
}
}
/**
* Convenience factory method that creates a debuging bounding sphere geometry
* @param assetManager the assetManager
* @return the bounding sphere debug geometry.
*/
public static Geometry createDebugSphere(AssetManager assetManager) {
BoundingSphereDebug b = new BoundingSphereDebug();
Geometry geom = new Geometry("BoundingDebug", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setBoolean("VertexColor", true);
mat.getAdditionalRenderState().setWireframe(true);
geom.setMaterial(mat);
return geom;
}
}

@ -0,0 +1,243 @@
/*
* Copyright (c) 2009-2015 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.util;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.math.ColorRGBA;
import static com.jme3.math.FastMath.pow;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.texture.Image;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.DefaultImageRaster;
import com.jme3.texture.image.MipMapImageRaster;
import com.jme3.util.BufferUtils;
/**
* Wraps a Cube map and allows to read from or write pixels into it.
*
* It uses the ImageRaster class to tailor the read write operations.
*
* @author Nehon
*/
public class CubeMapWrapper {
private MipMapImageRaster mipMapRaster;
private final DefaultImageRaster raster;
private int[] sizes;
private final Vector2f uvs = new Vector2f();
private final Image image;
/**
* Creates a CubeMapWrapper for the given cube map
* Note that the cube map must be initialized, and the mipmaps sizes should
* be set if relevant for them to be readable/writable
* @param cubeMap the cubemap to wrap.
*/
public CubeMapWrapper(TextureCubeMap cubeMap) {
image = cubeMap.getImage();
if (image.hasMipmaps()) {
int nbMipMaps = image.getMipMapSizes().length;
sizes = new int[nbMipMaps];
mipMapRaster = new MipMapImageRaster(image, 0);
for (int i = 0; i < nbMipMaps; i++) {
sizes[i] = Math.max(1, image.getWidth() >> i);
}
} else {
sizes = new int[1];
sizes[0] = image.getWidth();
}
raster = new DefaultImageRaster(image, 0,0 , false);
}
/**
* Reads a pixel from the cube map given the coordinate vector
* @param vector the direction vector to fetch the texel
* @param store the color in which to store the pixel color read.
* @return the color of the pixel read.
*/
public ColorRGBA getPixel(Vector3f vector, ColorRGBA store) {
if (store == null) {
store = new ColorRGBA();
}
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
raster.setSlice(face);
return raster.getPixel((int) uvs.x, (int) uvs.y, store);
}
/**
*
* Reads a pixel from the cube map given the coordinate vector
* @param vector the direction vector to fetch the texel
* @param mipLevel the mip level to read from
* @param store the color in which to store the pixel color read.
* @return the color of the pixel read.
*/
public ColorRGBA getPixel(Vector3f vector, int mipLevel, ColorRGBA store) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
if (store == null) {
store = new ColorRGBA();
}
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
}
/**
* Reads a pixel from the cube map given the 2D coordinates and the face to read from
* @param x the x tex coordinate (from 0 to width)
* @param y the y tex coordinate (from 0 to height)
* @param face the face to read from
* @param store the color where the result is stored.
* @return the color read.
*/
public ColorRGBA getPixel(int x, int y, int face, ColorRGBA store) {
if (store == null) {
store = new ColorRGBA();
}
raster.setSlice(face);
return raster.getPixel((int) x, (int) y, store);
}
/**
* Reads a pixel from the cube map given the 2D coordinates and the face and
* the mip level to read from
* @param x the x tex coordinate (from 0 to width)
* @param y the y tex coordinate (from 0 to height)
* @param face the face to read from
* @param mipLevel the miplevel to read from
* @param store the color where the result is stored.
* @return the color read.
*/
public ColorRGBA getPixel(int x, int y, int face, int mipLevel, ColorRGBA store) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
if (store == null) {
store = new ColorRGBA();
}
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
return mipMapRaster.getPixel((int) x, (int) y, store);
}
/**
* writes a pixel given the coordinates vector and the color.
* @param vector the cooredinates where to write the pixel
* @param color the color to write
*/
public void setPixel(Vector3f vector, ColorRGBA color) {
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
raster.setSlice(face);
raster.setPixel((int) uvs.x, (int) uvs.y, color);
}
/**
* writes a pixel given the coordinates vector, the mip level and the color.
* @param vector the cooredinates where to write the pixel
* @param mipLevel the miplevel to write to
* @param color the color to write
*/
public void setPixel(Vector3f vector, int mipLevel, ColorRGBA color) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
mipMapRaster.setPixel((int) uvs.x, (int) uvs.y, color);
}
/**
* Writes a pixel given the 2D cordinates and the color
* @param x the x tex coord (from 0 to width)
* @param y the y tex coord (from 0 to height)
* @param face the face to write to
* @param color the color to write
*/
public void setPixel(int x, int y, int face, ColorRGBA color) {
raster.setSlice(face);
raster.setPixel((int) x, (int) y, color);
}
/**
* Writes a pixel given the 2D cordinates, the mip level and the color
* @param x the x tex coord (from 0 to width)
* @param y the y tex coord (from 0 to height)
* @param face the face to write to
* @param mipLevel the mip level to write to
* @param color the color to write
*/
public void setPixel(int x, int y, int face, int mipLevel, ColorRGBA color) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
mipMapRaster.setPixel((int) x, (int) y, color);
}
/**
* Inits the mip maps of a cube map witht he given number of mip maps
* @param nbMipMaps the number of mip maps to initialize
*/
public void initMipMaps(int nbMipMaps) {
int maxMipMap = (int) (Math.log(image.getWidth()) / Math.log(2) + 1);
if (nbMipMaps > maxMipMap) {
throw new IllegalArgumentException("Max mip map number for a " + image.getWidth() + "x" + image.getHeight() + " cube map is " + maxMipMap);
}
sizes = new int[nbMipMaps];
int totalSize = 0;
for (int i = 0; i < nbMipMaps; i++) {
int size = (int) pow(2, maxMipMap - 1 - i);
sizes[i] = size * size * image.getFormat().getBitsPerPixel() / 8;
totalSize += sizes[i];
}
image.setMipMapSizes(sizes);
image.getData().clear();
for (int i = 0; i < 6; i++) {
image.addData(BufferUtils.createByteBuffer(totalSize));
}
mipMapRaster = new MipMapImageRaster(image, 0);
}
}

@ -0,0 +1,947 @@
/*
* Copyright (c) 2009-2015 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.util;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace;
import com.jme3.ui.Picture;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import static com.jme3.math.FastMath.*;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.util.TempVars;
/**
*
* This class holds several utility method unseful for Physically Based
* Rendering. It alloaws to compute useful pre filtered maps from an env map.
*
* @author Nehon
*/
public class EnvMapUtils {
public final static int NUM_SH_COEFFICIENT = 9;
// See Peter-Pike Sloan paper for these coefficients
//http://www.ppsloan.org/publications/StupidSH36.pdf
public static float[] shBandFactor = {1.0f,
2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f,
1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f};
public static enum FixSeamsMethod {
/**
* wrap texture coordinates
*/
Wrap,
/**
* stretch texture coordinates
*/
Stretch,
/**
* No seams fix
*/
None;
}
/**
* Creates a cube map from 6 images
*
* @param leftImg the west side image, also called negative x (negX) or left
* image
* @param rightImg the east side image, also called positive x (posX) or
* right image
* @param downImg the bottom side image, also called negative y (negY) or
* down image
* @param upImg the up side image, also called positive y (posY) or up image
* @param backImg the south side image, also called positive z (posZ) or
* back image
* @param frontImg the north side image, also called negative z (negZ) or
* front image
* @param format the format of the image
* @return a cube map
*/
public static TextureCubeMap makeCubeMap(Image rightImg, Image leftImg, Image upImg, Image downImg, Image backImg, Image frontImg, Image.Format format) {
Image cubeImage = new Image(format, leftImg.getWidth(), leftImg.getHeight(), null, ColorSpace.Linear);
cubeImage.addData(rightImg.getData(0));
cubeImage.addData(leftImg.getData(0));
cubeImage.addData(upImg.getData(0));
cubeImage.addData(downImg.getData(0));
cubeImage.addData(backImg.getData(0));
cubeImage.addData(frontImg.getData(0));
if (leftImg.getEfficentData() != null) {
// also consilidate efficient data
ArrayList<Object> efficientData = new ArrayList<Object>(6);
efficientData.add(rightImg.getEfficentData());
efficientData.add(leftImg.getEfficentData());
efficientData.add(upImg.getEfficentData());
efficientData.add(downImg.getEfficentData());
efficientData.add(backImg.getEfficentData());
efficientData.add(frontImg.getEfficentData());
cubeImage.setEfficentData(efficientData);
}
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(0);
cubeMap.setMagFilter(Texture.MagFilter.Bilinear);
cubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
cubeMap.setWrap(Texture.WrapMode.EdgeClamp);
return cubeMap;
}
/**
* Make a duplicate of this cube Map. That means that it's another instant
* od TextureCubeMap, but the underlying buffers are duplicates of the
* original ones. see {@link ByteBuffer#duplicate()}
*
* Use this if you need to read from the map from multiple threads, it
* should garanty the thread safety. Note that if you want to write to the
* cube map you have to make sure that the different thread do not write to
* the same area of the buffer. The position, limit and mark are not an
* issue.
*
* @param sourceMap
* @return
*/
public static TextureCubeMap duplicateCubeMap(TextureCubeMap sourceMap) {
Image srcImg = sourceMap.getImage();
Image cubeImage = new Image(srcImg.getFormat(), srcImg.getWidth(), srcImg.getHeight(), null, srcImg.getColorSpace());
for (ByteBuffer d : srcImg.getData()) {
cubeImage.addData(d.duplicate());
}
if (srcImg.getEfficentData() != null) {
// also consilidate efficient data
ArrayList<Object> efficientData = new ArrayList<Object>(6);
efficientData.add(srcImg.getEfficentData());
cubeImage.setEfficentData(efficientData);
}
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter());
cubeMap.setMagFilter(sourceMap.getMagFilter());
cubeMap.setMinFilter(sourceMap.getMinFilter());
cubeMap.setWrap(sourceMap.getWrap(Texture.WrapAxis.S));
return cubeMap;
}
/**
* Computes the vector coordinates, for the given x,y texture coordinates
* and the given cube map face.
*
* Also computes the solid angle for those coordinates and returns it.
*
* To know what the solid angle is please read this.
* http://www.codinglabs.net/article_physically_based_rendering.aspx
*
*
* Original solid angle calculation code is from Ignacio Castaño. This
* formula is from Manne Öhrström's thesis. It takes two coordiantes in the
* range [-1, 1] that define a portion of a cube face and return the area of
* the projection of that portion on the surface of the sphere.
*
* @param x texture coordinate from 0 to 1 in the given cube map face
* @param y texture coordinate from 0 to 1 in the given cube map face
* @param mapSize the size of the cube map
* @param face the face id of the cube map
* @param store the vector3f where the vector will be stored. don't provide
* null for this param
* @return the solid angle for the give parameters
*/
static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
if (store == null) {
throw new IllegalArgumentException("the store parameter ust not be null");
}
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
(+ 0.5f is for texel center addressing) */
float u = (2.0f * ((float) x + 0.5f) / (float) mapSize) - 1.0f;
float v = (2.0f * ((float) y + 0.5f) / (float) mapSize) - 1.0f;
getVectorFromCubemapFaceTexCoord(x, y, mapSize, face, store, fixSeamsMethod);
/* Solid angle weight approximation :
* U and V are the -1..1 texture coordinate on the current face.
* Get projected area for this texel */
float x0, y0, x1, y1;
float invRes = 1.0f / (float) mapSize;
x0 = u - invRes;
y0 = v - invRes;
x1 = u + invRes;
y1 = v + invRes;
return areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1);
}
/**
* used to compute the solid angle
*
* @param x tex coordinates
* @param y tex coordinates
* @return
*/
private static float areaElement(float x, float y) {
return (float) Math.atan2(x * y, sqrt(x * x + y * y + 1));
}
/**
*
* Computes the 3 component vector coordinates for the given face and coords
*
* @param x the x texture coordinate
* @param y the y texture coordinate
* @param mapSize the size of a face of the cube map
* @param face the face to consider
* @param store a vector3f where the resulting vector will be stored
* @param fixSeamsMethod the method to fix the seams
* @return
*/
public static Vector3f getVectorFromCubemapFaceTexCoord(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
if (store == null) {
store = new Vector3f();
}
float u;
float v;
if (fixSeamsMethod == FixSeamsMethod.Stretch) {
/* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
* transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f;
v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f;
} else {
//Done if any other fix method or no fix method is set
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
* (+ 0.5f is for texel center addressing) */
u = (2.0f * ((float) x + 0.5f) / (float) (mapSize)) - 1.0f;
v = (2.0f * ((float) y + 0.5f) / (float) (mapSize)) - 1.0f;
}
if (fixSeamsMethod == FixSeamsMethod.Wrap) {
// Warp texel centers in the proximity of the edges.
float a = pow((float) mapSize, 2.0f) / pow(((float) mapSize - 1f), 3.0f);
u = a * pow(u, 3f) + u;
v = a * pow(v, 3f) + v;
}
//compute vector depending on the face
// Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
switch (face) {
case 0:
store.set(1f, -v, -u);
break;
case 1:
store.set(-1f, -v, u);
break;
case 2:
store.set(u, 1f, v);
break;
case 3:
store.set(u, -1f, -v);
break;
case 4:
store.set(u, -v, 1f);
break;
case 5:
store.set(-u, -v, -1.0f);
break;
}
return store.normalizeLocal();
}
/**
*
* Computes the texture coortinates and the face of the cube map from the
* given vector
*
* @param texelVect the vector to fetch texelt from the cube map
* @param fixSeamsMethod the method to fix the seams
* @param mapSize the size of one face of the cube map
* @param store a Vector2f where the texture coordinates will be stored
* @return the face from which to fetch the texel
*/
public static int getCubemapFaceTexCoordFromVector(Vector3f texelVect, int mapSize, Vector2f store, FixSeamsMethod fixSeamsMethod) {
float u = 0, v = 0, bias = 0;
int face;
float absX = abs(texelVect.x);
float absY = abs(texelVect.y);
float absZ = abs(texelVect.z);
float max = Math.max(Math.max(absX, absY), absZ);
if (max == absX) {
face = texelVect.x > 0 ? 0 : 1;
} else if (max == absY) {
face = texelVect.y > 0 ? 2 : 3;
} else {
face = texelVect.z > 0 ? 4 : 5;
}
//compute vector depending on the face
// Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
switch (face) {
case 0:
//store.set(1f, -v, -u, 0);
bias = 1f / texelVect.x;
u = -texelVect.z;
v = -texelVect.y;
break;
case 1:
// store.set(-1f, -v, u, 0);
bias = -1f / texelVect.x;
u = texelVect.z;
v = -texelVect.y;
break;
case 2:
//store.set(u, 1f, v, 0);
bias = 1f / texelVect.y;
u = texelVect.x;
v = texelVect.z;
break;
case 3:
//store.set(u, -1f, -v, 0);
bias = -1f / texelVect.y;
u = texelVect.x;
v = -texelVect.z;
break;
case 4:
//store.set(u, -v, 1f, 0);
bias = 1f / texelVect.z;
u = texelVect.x;
v = -texelVect.y;
break;
case 5:
//store.set(-u, -v, -1.0f, 0);
bias = -1f / texelVect.z;
u = -texelVect.x;
v = -texelVect.y;
break;
}
u *= bias;
v *= bias;
if (fixSeamsMethod == FixSeamsMethod.Stretch) {
/* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
* transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
u = Math.round((u + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
v = Math.round((v + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
} else {
//Done if any other fix method or no fix method is set
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
* (+ 0.5f is for texel center addressing) */
u = Math.round((u + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
v = Math.round((v + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
}
store.set(u, v);
return face;
}
/*
public static void main(String... argv) {
// for (int givenFace = 0; givenFace < 6; givenFace++) {
//
// //int givenFace = 1;
// for (int x = 0; x < 128; x++) {
// for (int y = 0; y < 128; y++) {
// Vector3f v = EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, 128, givenFace, null, FixSeamsMethod.None);
// Vector2f uvs = new Vector2f();
// int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(v, 128, uvs, FixSeamsMethod.None);
//
// if ((int) uvs.x != x || (int) uvs.y != y) {
// System.err.println("error " + uvs + " should be " + x + "," + y + " vect was " + v);
// }
// if (givenFace != face) {
// System.err.println("error face: " + face + " should be " + givenFace);
// }
// }
// }
// }
// System.err.println("done ");
int total = 0;
for (int i = 0; i < 6; i++) {
int size = (int) pow(2, 7 - i);
int samples = EnvMapUtils.getSampleFromMip(i, 6);
int iterations = (samples * size * size);
total += iterations;
float roughness = EnvMapUtils.getRoughnessFromMip(i, 6);
System.err.println("roughness " + i + " : " + roughness + " , map : " + size + " , samples : " + samples + " , iterations : " + iterations);
System.err.println("reverse " + EnvMapUtils.getMipFromRoughness(roughness, 6));
}
System.err.println("total " + total);
System.err.println(128 * 128 * 1024);
System.err.println("test " + EnvMapUtils.getMipFromRoughness(0.9999f, 6));
System.err.println("nb mip = " + (Math.log(128) / Math.log(2) - 1));
}*/
public static int getSampleFromMip(int mipLevel, int miptot) {
return mipLevel==0?1:Math.min(1 << (miptot - 1 + (mipLevel) * 2 ), 8192);
}
public static float getRoughnessFromMip(int miplevel, int miptot) {
float mipScale = 1.0f;
float mipOffset = -0.3f;
return pow(2, (miplevel - (miptot - 1) + mipOffset) / mipScale);
}
public static float getMipFromRoughness(float roughness, int miptot) {
float mipScale = 1.0f;
float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;
return (float) Math.max(0.0, Lod);
}
/**
* same as
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap, com.jme3.utils.EnvMapUtils.FixSeamsMethod)}
* the fix method used is {@link FixSeamsMethod#Wrap}
*
* @param cubeMap the environment cube map to compute SH for
* @return an array of 9 vector3f representing thos coefficients for each
* r,g,b channnel
*/
public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap) {
return getSphericalHarmonicsCoefficents(cubeMap, FixSeamsMethod.Wrap);
}
/**
* Returns the Spherical Harmonics coefficients for this cube map.
*
* The method used is the one from this article :
* http://graphics.stanford.edu/papers/envmap/envmap.pdf
*
* Also good resources on spherical harmonics
* http://dickyjim.wordpress.com/2013/09/04/spherical-harmonics-for-beginners/
*
* @param cubeMap the environment cube map to compute SH for
* @param fixSeamsMethod method to fix seams when computing the SH
* coefficients
* @return an array of 9 vector3f representing thos coefficients for each
* r,g,b channnel
*/
public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap, FixSeamsMethod fixSeamsMethod) {
Vector3f[] shCoef = new Vector3f[NUM_SH_COEFFICIENT];
float[] shDir = new float[9];
float weightAccum = 0.0f;
float weight;
if (cubeMap.getImage().getData(0) == null) {
throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU plase use renderer.readFrameBuffer, to create a CPU image");
}
int width = cubeMap.getImage().getWidth();
int height = cubeMap.getImage().getHeight();
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA();
CubeMapWrapper envMapReader = new CubeMapWrapper(cubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
weight = getSolidAngleAndVector(x, y, width, face, texelVect, fixSeamsMethod);
evalShBasis(texelVect, shDir);
envMapReader.getPixel(x, y, face, color);
for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
if (shCoef[i] == null) {
shCoef[i] = new Vector3f();
}
shCoef[i].setX(shCoef[i].x + color.r * shDir[i] * weight);
shCoef[i].setY(shCoef[i].y + color.g * shDir[i] * weight);
shCoef[i].setZ(shCoef[i].z + color.b * shDir[i] * weight);
}
weightAccum += weight;
}
}
}
/* Normalization - The sum of solid angle should be equal to the solid angle of the sphere (4 PI), so
* normalize in order our weightAccum exactly match 4 PI. */
for (int i = 0; i < NUM_SH_COEFFICIENT; ++i) {
shCoef[i].multLocal(4.0f * PI / weightAccum);
}
return shCoef;
}
/**
* Computes SH coefficient for a given textel dir The method used is the one
* from this article : http://graphics.stanford.edu/papers/envmap/envmap.pdf
*
* @param texelVect
* @param shDir
*/
public static void evalShBasis(Vector3f texelVect, float[] shDir) {
float xV = texelVect.x;
float yV = texelVect.y;
float zV = texelVect.z;
float pi = PI;
float sqrtPi = sqrt(pi);
float sqrt3Pi = sqrt(3f / pi);
float sqrt5Pi = sqrt(5f / pi);
float sqrt15Pi = sqrt(15f / pi);
float x2 = xV * xV;
float y2 = yV * yV;
float z2 = zV * zV;
shDir[0] = (1f / (2f * sqrtPi));
shDir[1] = -(sqrt3Pi * yV) / 2f;
shDir[2] = (sqrt3Pi * zV) / 2f;
shDir[3] = -(sqrt3Pi * xV) / 2f;
shDir[4] = (sqrt15Pi * xV * yV) / 2f;
shDir[5] = -(sqrt15Pi * yV * zV) / 2f;
shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f;
shDir[7] = -(sqrt15Pi * xV * zV) / 2f;
shDir[8] = sqrt15Pi * (x2 - y2) / 4f;
// shDir[0] = (1f/(2.f*sqrtPi));
//
// shDir[1] = -(sqrt(3f/pi)*yV)/2.f;
// shDir[2] = (sqrt(3/pi)*zV)/2.f;
// shDir[3] = -(sqrt(3/pi)*xV)/2.f;
//
// shDir[4] = (sqrt(15f/pi)*xV*yV)/2.f;
// shDir[5] = -(sqrt(15f/pi)*yV*zV)/2.f;
// shDir[6] = (sqrt(5f/pi)*(-1 + 3f*z2))/4.f;
// shDir[7] = -(sqrt(15f/pi)*xV*zV)/2.f;
// shDir[8] = sqrt(15f/pi)*(x2 - y2)/4.f;
}
/**
* {@link EnvMapUtils#generateIrradianceMap(com.jme3.math.Vector3f[], com.jme3.texture.TextureCubeMap, int, com.jme3.utils.EnvMapUtils.FixSeamsMethod)
* }
*
* @param shCoeffs the spherical harmonics coefficients to use
* @param targetMapSize the size of the target map
* @return the irradiance map.
*/
public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize) {
return generateIrradianceMap(shCoeffs, targetMapSize, FixSeamsMethod.Wrap, null);
}
/**
* Generates the Irradiance map (used for image based difuse lighting) from
* Spherical Harmonics coefficients previously computed with
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
* Note that the output cube map is in RGBA8 format.
*
* @param shCoeffs the SH coeffs
* @param targetMapSize the size of the irradiance map to generate
* @param fixSeamsMethod the method to fix seams
* @param store
* @return The irradiance cube map for the given coefficients
*/
public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap irrCubeMap = store;
if (irrCubeMap == null) {
irrCubeMap = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
irrCubeMap.setMagFilter(Texture.MagFilter.Bilinear);
irrCubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
irrCubeMap.getImage().setColorSpace(ColorSpace.Linear);
}
for (int i = 0; i < 6; i++) {
ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * irrCubeMap.getImage().getFormat().getBitsPerPixel()/8);
irrCubeMap.getImage().setData(i, buf);
}
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
float[] shDir = new float[9];
CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < targetMapSize; y++) {
for (int x = 0; x < targetMapSize; x++) {
getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
evalShBasis(texelVect, shDir);
color.set(0, 0, 0, 0);
for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
1.0f);
}
//clamping the color because very low value close to zero produce artifacts
color.r = Math.max(0.0001f, color.r);
color.g = Math.max(0.0001f, color.g);
color.b = Math.max(0.0001f, color.b);
envMapWriter.setPixel(x, y, face, color);
}
}
}
return irrCubeMap;
}
/**
* Generates the prefiltered env map (used for image based specular
* lighting) With the GGX/Shlick brdf
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
* Note that the output cube map is in RGBA8 format.
*
* @param sourceEnvMap
* @param targetMapSize the size of the irradiance map to generate
* @param store
* @param fixSeamsMethod the method to fix seams
* @return The irradiance cube map for the given coefficients
*/
public static TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap pem = store;
if (pem == null) {
pem = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
pem.setMagFilter(Texture.MagFilter.Bilinear);
pem.setMinFilter(Texture.MinFilter.Trilinear);
pem.getImage().setColorSpace(ColorSpace.Linear);
}
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
targetWrapper.initMipMaps(nbMipMap);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
System.err.println("mip level " + mipLevel);
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
for (int face = 0; face < 6; face++) {
System.err.println("face " + face);
for (int y = 0; y < targetMipMapSize; y++) {
for (int x = 0; x < targetMipMapSize; x++) {
color.set(0, 0, 0);
getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, FixSeamsMethod.Wrap);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
outColor.set(color.x, color.y, color.z, 1.0f);
// System.err.println("coords " + x + "," + y);
targetWrapper.setPixel(x, y, face, mipLevel, outColor);
}
}
}
}
return pem;
}
public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f store) {
if (store == null) {
store = new Vector4f();
}
float phi;
long ui = i;
store.setX((float) i / (float) nbrSample);
/* From http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
* Radical Inverse : Van der Corput */
ui = (ui << 16) | (ui >> 16);
ui = ((ui & 0x55555555) << 1) | ((ui & 0xAAAAAAAA) >>> 1);
ui = ((ui & 0x33333333) << 2) | ((ui & 0xCCCCCCCC) >>> 2);
ui = ((ui & 0x0F0F0F0F) << 4) | ((ui & 0xF0F0F0F0) >>> 4);
ui = ((ui & 0x00FF00FF) << 8) | ((ui & 0xFF00FF00) >>> 8);
ui = ui & 0xffffffff;
store.setY(2.3283064365386963e-10f * (float) (ui)); /* 0x100000000 */
phi = 2.0f * PI * store.y;
store.setZ(cos(phi));
store.setW(sin(phi));
return store;
}
private static Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
Vector3f prefilteredColor = store;
float totalWeight = 0.0f;
TempVars vars = TempVars.get();
Vector4f Xi = vars.vect4f1;
Vector3f H = vars.vect1;
Vector3f tmp = vars.vect2;
ColorRGBA c = vars.color;
// a = roughness² and a2 = a²
float a2 = roughness * roughness;
a2 *= a2;
a2 *= 10;
for (int i = 0; i < numSamples; i++) {
Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H, vars);
H.normalizeLocal();
tmp.set(H);
float NoH = N.dot(tmp);
Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
float NoL = clamp(N.dot(L), 0.0f, 1.0f);
if (NoL > 0) {
envMapReader.getPixel(L, c);
prefilteredColor.setX(prefilteredColor.x + c.r * NoL);
prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
prefilteredColor.setZ(prefilteredColor.z + c.b * NoL);
totalWeight += NoL;
}
}
vars.release();
return prefilteredColor.divideLocal(totalWeight);
}
public static Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store, TempVars vars) {
if (store == null) {
store = new Vector3f();
}
float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
float sinTheta = sqrt(1f - cosTheta * cosTheta);
float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
Vector3f upVector = Vector3f.UNIT_X;
if (abs(normal.z) < 0.999) {
upVector = Vector3f.UNIT_Y;
}
Vector3f tangentX = vars.vect3.set(upVector).crossLocal(normal).normalizeLocal();
Vector3f tangentY = vars.vect4.set(normal).crossLocal(tangentX);
// Tangent to world space
tangentX.multLocal(sinThetaCosPhi);
tangentY.multLocal(sinThetaSinPhi);
vars.vect5.set(normal).multLocal(cosTheta);
// Tangent to world space
store.set(tangentX).addLocal(tangentY).addLocal(vars.vect5);
return store;
}
/**
* Creates a debug Node of the given cube map to attach to the gui node
*
* the cube map is layered this way :
* <pre>
* _____
* | |
* | +Y |
* _____|_____|_____ _____
* | | | | |
* | -X | +Z | +X | -Z |
* |_____|_____|_____|_____|
* | |
* | -Y |
* |_____|
*
*</pre>
*
* @param cubeMap the cube map
* @param assetManager the asset Manager
* @return
*/
public static Node getCubeMapCrossDebugView(TextureCubeMap cubeMap, AssetManager assetManager) {
Node n = new Node("CubeMapDebug" + cubeMap.getName());
int size = cubeMap.getImage().getWidth();
Picture[] pics = new Picture[6];
float ratio = 128f / (float) size;
for (int i = 0; i < 6; i++) {
pics[i] = new Picture("bla");
Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, cubeMap.getImage().getData(i), cubeMap.getImage().getColorSpace()));
pics[i].setTexture(assetManager, tex, true);
pics[i].setWidth(size);
pics[i].setHeight(size);
n.attachChild(pics[i]);
}
pics[0].setLocalTranslation(size, size * 2, 1);
pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[1].setLocalTranslation(size * 3, size * 2, 1);
pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[2].setLocalTranslation(size * 2, size * 3, 1);
pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[3].setLocalTranslation(size * 2, size, 1);
pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[4].setLocalTranslation(size * 2, size * 2, 1);
pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[5].setLocalTranslation(size * 4, size * 2, 1);
pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
Quad q = new Quad(size * 4, size * 3);
Geometry g = new Geometry("bg", q);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
g.setMaterial(mat);
g.setLocalTranslation(0, 0, 0);
n.attachChild(g);
n.setLocalScale(ratio);
return n;
}
public static Node getCubeMapCrossDebugViewWithMipMaps(TextureCubeMap cubeMap, AssetManager assetManager) {
Node n = new Node("CubeMapDebug" + cubeMap.getName());
int size = cubeMap.getImage().getWidth();
int nbMips = cubeMap.getImage().getMipMapSizes().length;
Picture[] pics = new Picture[6*nbMips];
float ratio = 1f;// 128f / (float) size;
int offset = 0;
int guiOffset = 0;
for (int mipLevel = 0; mipLevel < nbMips; mipLevel++) {
size = Math.max(1, cubeMap.getImage().getWidth() >> mipLevel);
int dataSize = cubeMap.getImage().getMipMapSizes()[mipLevel];
byte[] dataArray = new byte[dataSize];
for (int i = 0; i < 6; i++) {
ByteBuffer bb = cubeMap.getImage().getData(i);
bb.rewind();
bb.position(offset);
bb.get(dataArray, 0, dataSize);
ByteBuffer data = BufferUtils.createByteBuffer(dataArray);
pics[i] = new Picture("bla");
Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, data, cubeMap.getImage().getColorSpace()));
pics[i].setTexture(assetManager, tex, true);
pics[i].setWidth(size);
pics[i].setHeight(size);
n.attachChild(pics[i]);
}
pics[0].setLocalTranslation(guiOffset + size, guiOffset + size * 2, 1);
pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[1].setLocalTranslation(guiOffset + size * 3, guiOffset + size * 2, 1);
pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[2].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 3, 1);
pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[3].setLocalTranslation(guiOffset + size * 2, guiOffset + size, 1);
pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[4].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 2, 1);
pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[5].setLocalTranslation(guiOffset + size * 4, guiOffset + size * 2, 1);
pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
guiOffset+=size *2+1;
offset += dataSize;
}
Quad q = new Quad(cubeMap.getImage().getWidth() * 4 + nbMips, guiOffset + size);
Geometry g = new Geometry("bg", q);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
g.setMaterial(mat);
g.setLocalTranslation(0, 0, 0);
n.attachChild(g);
n.setLocalScale(ratio);
return n;
}
/**
* initialize the Irradiancemap
* @param size the size of the map
* @param imageFormat the format of the image
* @return the initialized Irradiance map
*/
public static TextureCubeMap createIrradianceMap(int size, Image.Format imageFormat) {
TextureCubeMap irrMap = new TextureCubeMap(size, size, imageFormat);
irrMap.setMagFilter(Texture.MagFilter.Bilinear);
irrMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
irrMap.getImage().setColorSpace(ColorSpace.Linear);
return irrMap;
}
/**
* initialize the pem map
* @param size the size of the map
* @param imageFormat the format of the image
* @return the initialized prefiltered env map
*/
public static TextureCubeMap createPrefilteredEnvMap(int size, Image.Format imageFormat) {
TextureCubeMap pem = new TextureCubeMap(size, size, imageFormat);
pem.setMagFilter(Texture.MagFilter.Bilinear);
pem.setMinFilter(Texture.MinFilter.Trilinear);
pem.getImage().setColorSpace(ColorSpace.Linear);
int nbMipMap = (int) (Math.log(size) / Math.log(2) - 1);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
targetWrapper.initMipMaps(nbMipMap);
return pem;
}
}

@ -0,0 +1,214 @@
/*
* Copyright (c) 2009-2015 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.util;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.bounding.BoundingSphere;
import com.jme3.material.Material;
import com.jme3.light.LightProbe;
import com.jme3.light.Light;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A debug state that will display LIght gizmos on screen.
* Still a wip and for now it only displays light probes.
*
* @author nehon
*/
public class LightsDebugState extends BaseAppState {
private Node debugNode;
private final Map<LightProbe, Node> probeMapping = new HashMap<LightProbe, Node>();
private final List<LightProbe> garbage = new ArrayList<LightProbe>();
private Geometry debugGeom;
private Geometry debugBounds;
private Material debugMaterial;
private DebugMode debugMode = DebugMode.PrefilteredEnvMap;
private float probeScale = 1.0f;
private Spatial scene = null;
private final List<LightProbe> probes = new ArrayList<LightProbe>();
/**
* Debug mode for light probes
*/
public enum DebugMode {
/**
* Displays the prefiltered env maps on the debug sphere
*/
PrefilteredEnvMap,
/**
* displays the Irradiance map on the debug sphere
*/
IrradianceMap
}
@Override
protected void initialize(Application app) {
debugNode = new Node("Environment debug Node");
Sphere s = new Sphere(16, 16, 1);
debugGeom = new Geometry("debugEnvProbe", s);
debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
debugGeom.setMaterial(debugMaterial);
debugBounds = BoundingSphereDebug.createDebugSphere(app.getAssetManager());
if (scene == null) {
scene = app.getViewPort().getScenes().get(0);
}
}
@Override
public void update(float tpf) {
for (Light light : scene.getWorldLightList()) {
switch (light.getType()) {
case Probe:
LightProbe probe = (LightProbe) light;
probes.add(probe);
Node n = probeMapping.get(probe);
if (n == null) {
n = new Node("DebugProbe");
n.attachChild(debugGeom.clone(true));
n.attachChild(debugBounds.clone(false));
debugNode.attachChild(n);
probeMapping.put(probe, n);
}
Geometry probeGeom = ((Geometry) n.getChild(0));
Material m = probeGeom.getMaterial();
probeGeom.setLocalScale(probeScale);
if (probe.isReady()) {
if (debugMode == DebugMode.IrradianceMap) {
m.setTexture("CubeMap", probe.getIrradianceMap());
} else {
m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
}
}
n.setLocalTranslation(probe.getPosition());
n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
break;
default:
break;
}
}
debugNode.updateLogicalState(tpf);
debugNode.updateGeometricState();
cleanProbes();
}
/**
* Set the scenes for wich to render light gizmos.
* @param scene
*/
public void setScene(Spatial scene) {
this.scene = scene;
}
private void cleanProbes() {
if (probes.size() != probeMapping.size()) {
for (LightProbe probe : probeMapping.keySet()) {
if (!probes.contains(probe)) {
garbage.add(probe);
}
}
for (LightProbe probe : garbage) {
probeMapping.remove(probe);
}
garbage.clear();
probes.clear();
}
}
@Override
public void render(RenderManager rm) {
rm.renderScene(debugNode, getApplication().getViewPort());
}
/**
*
* @see DebugMode
* @return the debug mode
*/
public DebugMode getDebugMode() {
return debugMode;
}
/**
* sets the debug mode
* @see DebugMode
* @param debugMode the debug mode
*/
public void setDebugMode(DebugMode debugMode) {
this.debugMode = debugMode;
}
/**
* returns the scale of the probe's debug sphere
* @return
*/
public float getProbeScale() {
return probeScale;
}
/**
* sets the scale of the probe's debug sphere
* @param probeScale
*/
public void setProbeScale(float probeScale) {
this.probeScale = probeScale;
}
@Override
protected void cleanup(Application app) {
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}

@ -0,0 +1,70 @@
/*
* Copyright (c) 2009-2015 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.scene.Geometry;
import java.util.ArrayList;
import java.util.List;
/**
* This strategy returns the closest probe from the rendered object.
*
* This is the most basic strategy : The fastest and the easiest.
* Though it has severe graphical draw backs as there might be very visible seams
* on static object and some "poping" on dynamic objects.
*
* @author Nehon
*/
public class BasicProbeBlendingStrategy implements LightProbeBlendingStrategy {
List<LightProbe> lightProbes = new ArrayList<LightProbe>();
@Override
public void registerProbe(LightProbe probe) {
lightProbes.add(probe);
}
@Override
public void populateProbes(Geometry g, LightList lightList) {
if (!lightProbes.isEmpty()) {
//The first probe is actually the closest to the geometry since the
//light list is sorted according to the distance to the geom.
LightProbe p = lightProbes.get(0);
if (p.isReady()) {
lightList.add(p);
}
//clearing the list for next pass.
lightProbes.clear();
}
}
}

@ -43,6 +43,15 @@ public final class DefaultLightFilter implements LightFilter {
private Camera camera;
private final HashSet<Light> processedLights = new HashSet<Light>();
private final LightProbeBlendingStrategy probeBlendStrat;
public DefaultLightFilter() {
probeBlendStrat = new BasicProbeBlendingStrategy();
}
public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) {
this.probeBlendStrat = probeBlendStrat;
}
@Override
public void setCamera(Camera camera) {
@ -57,6 +66,7 @@ public final class DefaultLightFilter implements LightFilter {
TempVars vars = TempVars.get();
try {
LightList worldLights = geometry.getWorldLightList();
for (int i = 0; i < worldLights.size(); i++) {
Light light = worldLights.get(i);
@ -88,9 +98,17 @@ public final class DefaultLightFilter implements LightFilter {
}
}
}
filteredLightList.add(light);
if (light.getType() == Light.Type.Probe) {
probeBlendStrat.registerProbe((LightProbe) light);
} else {
filteredLightList.add(light);
}
}
probeBlendStrat.populateProbes(geometry, filteredLightList);
} finally {
vars.release();
}

@ -78,7 +78,14 @@ public abstract class Light implements Savable, Cloneable {
*
* @see AmbientLight
*/
Ambient(3);
Ambient(3),
/**
* Light probe
* @see LightProbe
*/
Probe(4);
private int typeId;

@ -0,0 +1,266 @@
/*
* Copyright (c) 2009-2015 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.asset.AssetManager;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
import com.jme3.util.TempVars;
import java.io.IOException;
/**
* A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting.
* This is used for indirect lighting in the Physically Based Rendering pipeline.
*
* A light probe has a position in world space. This is the position from where the Environment Map are rendered.
* There are two environment maps held by the LightProbe :
* - The irradiance map (used for indirect diffuse lighting in the PBR pipeline).
* - The prefiltered environment map (used for indirect specular lighting and reflection in the PBE pipeline).
* Note that when instanciating the LightProbe, both those maps are null.
* To render them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* and {@link EnvironmentCamera}.
*
* The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported).
*
* A LightProbe will only be taken into account when it's marked as ready.
* A light probe is ready when it has valid environment map data set.
* Note that you should never call setReady yourself.
*
* @see LightProbeFactory
* @see EnvironmentCamera
* @author nehon
*/
public class LightProbe extends Light implements Savable {
private TextureCubeMap irradianceMap;
private TextureCubeMap prefilteredEnvMap;
private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
private boolean ready = false;
private Vector3f position = new Vector3f();
private Node debugNode;
/**
* Empty constructor used for serialization.
* You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead
*/
public LightProbe() {
}
/**
* returns the irradiance map texture of this Light probe.
* Note that this Texture may not have image data yet if the LightProbe is not ready
* @return the irradiance map
*/
public TextureCubeMap getIrradianceMap() {
return irradianceMap;
}
/**
* Sets the irradiance map
* @param irradianceMap the irradiance map
*/
public void setIrradianceMap(TextureCubeMap irradianceMap) {
this.irradianceMap = irradianceMap;
}
/**
* returns the prefiltered environment map texture of this light probe
* Note that this Texture may not have image data yet if the LightProbe is not ready
* @return the prefiltered environment map
*/
public TextureCubeMap getPrefilteredEnvMap() {
return prefilteredEnvMap;
}
/**
* Sets the prefiltered environment map
* @param prefileteredEnvMap the prefiltered environment map
*/
public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) {
this.prefilteredEnvMap = prefileteredEnvMap;
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(irradianceMap, "irradianceMap", null);
oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
oc.write(position, "position", null);
oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
oc.write(ready, "ready", false);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null);
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
position = (Vector3f) ic.readSavable("position", this);
bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
ready = ic.readBoolean("ready", false);
}
/**
* returns the bounding volume of this LightProbe
* @return a bounding volume.
*/
public BoundingVolume getBounds() {
return bounds;
}
/**
* Sets the bounds of this LightProbe
* Note that for now only BoundingSphere is supported and this method will
* throw an UnsupportedOperationException with any other BoundingVolume type
* @param bounds the bounds of the LightProbe
*/
public void setBounds(BoundingVolume bounds) {
if( bounds.getType()!= BoundingVolume.Type.Sphere){
throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe");
}
this.bounds = bounds;
}
/**
* return true if the LightProbe is ready, meaning the Environment maps have
* been loaded or rnedered and are ready to be used by a material
* @return the LightProbe ready state
*/
public boolean isReady() {
return ready;
}
/**
* Don't call this method directly.
* It's meant to be called by additional systems that will load or render
* the Environment maps of the LightProbe
* @param ready the ready state of the LightProbe.
*/
public void setReady(boolean ready) {
this.ready = ready;
}
/**
* 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 Node getDebugGui(AssetManager manager) {
if (!ready) {
throw new UnsupportedOperationException("This EnvProbeis not ready yet, try to test isReady()");
}
if (debugNode == null) {
debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
Node debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(getIrradianceMap(), manager);
debugNode.attachChild(debugIrrCm);
debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0);
}
return debugNode;
}
/**
* Returns the position of the LightProbe in world space
* @return the wolrd space position
*/
public Vector3f getPosition() {
return position;
}
/**
* Sets the position of the LightProbe in world space
* @param position the wolrd space position
*/
public void setPosition(Vector3f position) {
this.position.set(position);
getBounds().setCenter(position);
}
@Override
public boolean intersectsBox(BoundingBox box, TempVars vars) {
return getBounds().intersectsBoundingBox(box);
}
@Override
public boolean intersectsFrustum(Camera camera, TempVars vars) {
return camera.contains(bounds) != Camera.FrustumIntersect.Outside;
}
@Override
protected void computeLastDistance(Spatial owner) {
if (owner.getWorldBound() != null) {
BoundingVolume bv = owner.getWorldBound();
lastDistance = bv.distanceSquaredTo(position);
} else {
lastDistance = owner.getWorldTranslation().distanceSquared(position);
}
}
@Override
public Type getType() {
return Type.Probe;
}
@Override
public String toString() {
return "Light Probe : " + name + " at " + position + " / " + bounds;
}
@Override
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
return getBounds().intersectsSphere(sphere);
}
}

@ -0,0 +1,208 @@
/*
* Copyright (c) 2009-2015 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.BoundingSphere;
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.util.TempVars;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* this processor allows to blend several light probes maps together according to a Point of Interest.
* This is all based on this article by Sebastien lagarde
* https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
* @author Nehon
*/
public class LightProbeBlendingProcessor implements SceneProcessor {
private ViewPort viewPort;
private LightFilter prevFilter;
private RenderManager renderManager;
private LightProbe probe = new LightProbe();
private Spatial poi;
public LightProbeBlendingProcessor(Spatial poi) {
this.poi = poi;
}
@Override
public void initialize(RenderManager rm, ViewPort vp) {
viewPort = vp;
renderManager = rm;
prevFilter = rm.getLightFilter();
rm.setLightFilter(new PoiLightProbeLightFilter(this));
}
@Override
public void reshape(ViewPort vp, int w, int h) {
}
@Override
public boolean isInitialized() {
return viewPort != null;
}
@Override
public void preFrame(float tpf) {
}
/** 1. For POI take a spatial in the constructor and make all calculation against its world pos
* - Alternatively compute an arbitrary POI by casting rays from the camera
* (one in the center and one for each corner and take the median point)
* 2. Take the 4 most weighted probes for default. Maybe allow the user to change this
* 3. For the inner influence radius take half of the radius for a start we'll see then how to change this.
*
*/
@Override
public void postQueue(RenderQueue rq) {
List<BlendFactor> blendFactors = new ArrayList<BlendFactor>();
float sumBlendFactors = computeBlendFactors(blendFactors);
//Sort blend factors according to their weight
Collections.sort(blendFactors);
//normalize blend factors;
float normalizer = 1f / sumBlendFactors;
for (BlendFactor blendFactor : blendFactors) {
blendFactor.ndf *= normalizer;
// System.err.println(blendFactor);
}
//for now just pick the first probe.
if(!blendFactors.isEmpty()){
probe = blendFactors.get(0).lightProbe;
}else{
probe = null;
}
}
private float computeBlendFactors(List<BlendFactor> blendFactors) {
float sumBlendFactors = 0;
for (Spatial scene : viewPort.getScenes()) {
for (Light light : scene.getWorldLightList()) {
if(light.getType() == Light.Type.Probe){
LightProbe p = (LightProbe)light;
TempVars vars = TempVars.get();
boolean intersect = p.intersectsFrustum(viewPort.getCamera(), vars);
vars.release();
//check if the probe is inside the camera frustum
if(intersect){
//is the poi inside the bounds of this probe
if(poi.getWorldBound().intersects(p.getBounds())){
//computing the distance as we need it to check if th epoi in in the inner radius and later to compute the weight
float outerRadius = ((BoundingSphere)p.getBounds()).getRadius();
float innerRadius = outerRadius * 0.5f;
float distance = p.getBounds().getCenter().distance(poi.getWorldTranslation());
// if the poi in inside the inner range of this probe, then this probe is the only one that matters.
if( distance < innerRadius ){
blendFactors.clear();
blendFactors.add(new BlendFactor(p, 1.0f));
return 1.0f;
}
//else we need to compute the weight of this probe and collect it for blending
float ndf = (distance - innerRadius) / (outerRadius - innerRadius);
sumBlendFactors += ndf;
blendFactors.add(new BlendFactor(p, ndf));
}
}
}
}
}
return sumBlendFactors;
}
@Override
public void postFrame(FrameBuffer out) {
}
@Override
public void cleanup() {
viewPort = null;
renderManager.setLightFilter(prevFilter);
}
public void populateProbe(LightList lightList){
if(probe != null && probe.isReady()){
lightList.add(probe);
}
}
public Spatial getPoi() {
return poi;
}
public void setPoi(Spatial poi) {
this.poi = poi;
}
private class BlendFactor implements Comparable<BlendFactor>{
LightProbe lightProbe;
float ndf;
public BlendFactor(LightProbe lightProbe, float ndf) {
this.lightProbe = lightProbe;
this.ndf = ndf;
}
@Override
public String toString() {
return "BlendFactor{" + "lightProbe=" + lightProbe + ", ndf=" + ndf + '}';
}
@Override
public int compareTo(BlendFactor o) {
if(o.ndf > ndf){
return -1;
}else if(o.ndf < ndf){
return 1;
}
return 0;
}
}
}

@ -0,0 +1,55 @@
/*
* Copyright (c) 2009-2015 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.scene.Geometry;
/**
* This is the interface to implement if you want to make your own LightProbe blending strategy.
* The strategy sets the way multiple LightProbes will be handled for a given object.
*
* @author Nehon
*/
public interface LightProbeBlendingStrategy {
/**
* Registers a probe with this strategy
* @param probe
*/
public void registerProbe(LightProbe probe);
/**
* Populates the resulting light probes into the given light list.
* @param g the geometry for wich the light list is computed
* @param lightList the result light list
*/
public void populateProbes(Geometry g, LightList lightList);
}

@ -0,0 +1,107 @@
/*
* Copyright (c) 2009-2015 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 PoiLightProbeLightFilter implements LightFilter {
private Camera camera;
private final HashSet<Light> processedLights = new HashSet<Light>();
private final LightProbeBlendingProcessor processor;
public PoiLightProbeLightFilter(LightProbeBlendingProcessor processor) {
this.processor = processor;
}
@Override
public void setCamera(Camera camera) {
this.camera = camera;
for (Light light : processedLights) {
light.frustumCheckNeeded = true;
}
}
@Override
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.getType() == Light.Type.Probe) {
continue;
}
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())) {
if (!light.intersectsSphere((BoundingSphere) bv, vars)) {
continue;
}
}
}
filteredLightList.add(light);
}
processor.populateProbe(filteredLightList);
} finally {
vars.release();
}
}
}

@ -807,6 +807,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
}
}
//TODO HACKY HACK remove this when texture unit is handled by the uniform.
return unit;
}
private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
@ -959,13 +961,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
renderManager.updateUniformBindings(shader);
// Set material parameters
updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
//TODO RRemove the unit when texture units are handled in the Uniform
int unit = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
// Clear any uniforms not changed by material.
resetUniformsNotSetByCurrent(shader);
// Delegate rendering to the technique
technique.render(renderManager, shader, geometry, lights);
technique.render(renderManager, shader, geometry, lights, unit);
}
/**

@ -161,9 +161,9 @@ public final class Technique {
* @param geometry The geometry to render
* @param lights Lights which influence the geometry.
*/
void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
TechniqueDefLogic logic = def.getLogic();
logic.render(renderManager, shader, geometry, lights);
logic.render(renderManager, shader, geometry, lights, lastTexUnit);
}
/**

@ -89,6 +89,16 @@ public class TechniqueDef implements Savable {
*/
MultiPass,
/**
* Enable light rendering by using a single pass, and also uses Image based lighting for global lighting
* Usually used for PBR
* <p>
* An array of light positions and light colors is passed to the shader
* containing the world light list for the geometry being rendered.
* Also Light probes are passed to the shader.
*/
SinglePassAndImageBased,
/**
* @deprecated OpenGL1 is not supported anymore
*/
@ -112,6 +122,15 @@ public class TechniqueDef implements Savable {
InPass,
PostPass,
}
/**
* Define in what space the light data should be sent to the shader.
*/
public enum LightSpace {
World,
View,
Legacy
}
private final EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
private String name;
@ -139,6 +158,8 @@ public class TechniqueDef implements Savable {
private TechniqueDefLogic logic;
private ArrayList<UniformBinding> worldBinds;
//The space in which the light should be transposed before sending to the shader.
private LightSpace lightSpace;
/**
* Creates a new technique definition.
@ -202,6 +223,14 @@ public class TechniqueDef implements Savable {
*/
public void setLightMode(LightMode lightMode) {
this.lightMode = lightMode;
//if light space is not specified we set it to Legacy
if(lightSpace == null){
if(lightMode== LightMode.MultiPass){
lightSpace = LightSpace.Legacy;
}else{
lightSpace = LightSpace.World;
}
}
}
public void setLogic(TechniqueDefLogic logic) {
@ -714,4 +743,20 @@ public class TechniqueDef implements Savable {
+ ", renderState=" + renderState
+ ", forcedRenderState=" + forcedRenderState + "]";
}
/**
* Returns the space in which the light data should be passed to the shader.
* @return the light space
*/
public LightSpace getLightSpace() {
return lightSpace;
}
/**
* Sets the space in which the light data should be passed to the shader.
* @param lightSpace the light space
*/
public void setLightSpace(LightSpace lightSpace) {
this.lightSpace = lightSpace;
}
}

@ -32,9 +32,7 @@
package com.jme3.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.light.AmbientLight;
import com.jme3.light.Light;
import com.jme3.light.LightList;
import com.jme3.light.*;
import com.jme3.material.TechniqueDef;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.Caps;
@ -88,8 +86,10 @@ public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
return ambientLightColor;
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
Renderer renderer = renderManager.getRenderer();
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);

@ -73,7 +73,7 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
Renderer r = renderManager.getRenderer();
Uniform lightDir = shader.getUniform("g_LightDirection");
Uniform lightColor = shader.getUniform("g_LightColor");
@ -156,6 +156,8 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
lightDir.setValue(VarType.Vector4, tmpLightDirection);
break;
case Probe:
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());

@ -0,0 +1,251 @@
/*
* Copyright (c) 2009-2015 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.material.logic;
import com.jme3.asset.AssetManager;
import com.jme3.bounding.BoundingSphere;
import com.jme3.light.*;
import com.jme3.material.*;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.*;
import com.jme3.renderer.*;
import com.jme3.scene.Geometry;
import com.jme3.shader.*;
import com.jme3.util.TempVars;
import java.util.EnumSet;
public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic {
private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING";
private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
private static final RenderState ADDITIVE_LIGHT = new RenderState();
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
static {
ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
ADDITIVE_LIGHT.setDepthWrite(false);
}
private final int singlePassLightingDefineId;
private final int nbLightsDefineId;
public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) {
super(techniqueDef);
singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
}
@Override
public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
defines.set(singlePassLightingDefineId, true);
return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
}
/**
* Uploads the lights in the light list as two uniform arrays.<br/><br/> *
* <p>
* <code>uniform vec4 g_LightColor[numLights];</code><br/> //
* g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
* g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
* 2 = Spot. <br/> <br/>
* <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
* g_LightPosition.xyz is the position of the light (for point lights)<br/>
* // or the direction of the light (for directional lights).<br/> //
* g_LightPosition.w is the inverse radius (1/r) of the light (for
* attenuation) <br/> </p>
*/
protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, int lastTexUnit) {
if (numLights == 0) { // this shader does not do lighting, ignore.
return 0;
}
Uniform lightData = shader.getUniform("g_LightData");
lightData.setVector4Length(numLights * 3);//8 lights * max 3
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
lightProbeData.setVector4Length(1);
Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
LightProbe lightProbe = null;
if (startIndex != 0) {
// apply additive blending for 2nd and future passes
rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
}else{
lightProbe = extractIndirectLights(lightList,true);
ambientColor.setValue(VarType.Vector4, ambientLightColor);
}
//If there is a lightProbe in the list we force it's render on the first pass
if(lightProbe != null){
BoundingSphere s = (BoundingSphere)lightProbe.getBounds();
lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f/s.getRadius(), 0);
//assigning new texture indexes
int irrUnit = lastTexUnit++;
int pemUnit = lastTexUnit++;
rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap());
lightProbeIrrMap.setValue(VarType.Int, irrUnit);
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
lightProbePemMap.setValue(VarType.Int, pemUnit);
} else {
//Disable IBL for this pass
lightProbeData.setVector4InArray(0,0,0,-1, 0);
}
int lightDataIndex = 0;
TempVars vars = TempVars.get();
Vector4f tmpVec = vars.vect4f1;
int curIndex;
int endIndex = numLights + startIndex;
boolean useIBL = false;
for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
Light l = lightList.get(curIndex);
if(l.getType() == Light.Type.Ambient){
endIndex++;
continue;
}
ColorRGBA color = l.getColor();
//Color
if(l.getType() != Light.Type.Probe){
lightData.setVector4InArray(color.getRed(),
color.getGreen(),
color.getBlue(),
l.getType().getId(),
lightDataIndex);
lightDataIndex++;
}
switch (l.getType()) {
case Directional:
DirectionalLight dl = (DirectionalLight) l;
Vector3f dir = dl.getDirection();
//Data directly sent in view space to avoid a matrix mult for each pixel
tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0,0,0,0, lightDataIndex);
lightDataIndex++;
break;
case Point:
PointLight pl = (PointLight) l;
Vector3f pos = pl.getPosition();
float invRadius = pl.getInvRadius();
tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
lightDataIndex++;
//PADDING
lightData.setVector4InArray(0,0,0,0, lightDataIndex);
lightDataIndex++;
break;
case Spot:
SpotLight sl = (SpotLight) l;
Vector3f pos2 = sl.getPosition();
Vector3f dir2 = sl.getDirection();
float invRange = sl.getInvSpotRange();
float spotAngleCos = sl.getPackedAngleCos();
tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
lightDataIndex++;
tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f);
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
lightDataIndex++;
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}
}
vars.release();
//Padding of unsued buffer space
while(lightDataIndex < numLights * 3) {
lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
lightDataIndex++;
}
return curIndex;
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
int nbRenderedLights = 0;
Renderer renderer = renderManager.getRenderer();
int batchSize = renderManager.getSinglePassLightBatchSize();
if (lights.size() == 0) {
updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit);
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
} else {
while (nbRenderedLights < lights.size()) {
nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit);
renderer.setShader(shader);
renderMeshFromGeometry(renderer, geometry);
}
}
return;
}
protected LightProbe extractIndirectLights(LightList lightList, boolean removeLights) {
ambientLightColor.set(0, 0, 0, 1);
LightProbe probe = null;
for (int j = 0; j < lightList.size(); j++) {
Light l = lightList.get(j);
if (l instanceof AmbientLight) {
ambientLightColor.addLocal(l.getColor());
if(removeLights){
lightList.remove(l);
j--;
}
}
if (l instanceof LightProbe) {
probe = (LightProbe)l;
if(removeLights){
lightList.remove(l);
j--;
}
}
}
ambientLightColor.a = 1.0f;
return probe;
}
}

@ -185,6 +185,8 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
lightDataIndex++;
break;
case Probe:
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}
@ -199,7 +201,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
int nbRenderedLights = 0;
Renderer renderer = renderManager.getRenderer();
int batchSize = renderManager.getSinglePassLightBatchSize();

@ -171,7 +171,7 @@ public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic {
}
@Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
Renderer renderer = renderManager.getRenderer();
Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix();
updateLightListUniforms(viewMatrix, shader, lights);

@ -93,5 +93,5 @@ public interface TechniqueDefLogic {
* @param geometry The geometry to render
* @param lights Lights which influence the geometry.
*/
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights);
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit);
}

@ -804,6 +804,15 @@ public class RenderManager {
public void setLightFilter(LightFilter lightFilter) {
this.lightFilter = lightFilter;
}
/**
* Returns the current LightFilter.
*
* @return the current light filter
*/
public LightFilter getLightFilter() {
return this.lightFilter;
}
/**
* Defines what light mode will be selected when a technique offers several light modes.

@ -319,6 +319,13 @@ public class Geometry extends Spatial {
worldLights.sort(true);
}
@Override
protected void updateWorldLightList() {
super.updateWorldLightList();
// geometry requires lights to be sorted
worldLights.sort(true);
}
/**
* Associate this <code>Geometry</code> with a {@link GeometryGroupNode}.
*

@ -119,7 +119,7 @@ MaterialDef Phong Lighting {
Technique {
LightMode SinglePass
VertexShader GLSL100: Common/MatDefs/Light/SPLighting.vert
FragmentShader GLSL100: Common/MatDefs/Light/SPLighting.frag

@ -0,0 +1,238 @@
#import "Common/ShaderLib/Parallax.glsllib"
#import "Common/ShaderLib/PBR.glsllib"
#import "Common/ShaderLib/Lighting.glsllib"
varying vec2 texCoord;
#ifdef SEPARATE_TEXCOORD
varying vec2 texCoord2;
#endif
varying vec4 Color;
uniform vec4 g_LightData[NB_LIGHTS];
uniform vec3 g_CameraPosition;
uniform float m_Roughness;
uniform float m_Metallic;
varying vec3 wPosition;
//#ifdef INDIRECT_LIGHTING
// uniform sampler2D m_IntegrateBRDF;
uniform samplerCube g_PrefEnvMap;
uniform samplerCube g_IrradianceMap;
uniform vec4 g_LightProbeData;
//#endif
#ifdef BASECOLORMAP
uniform sampler2D m_BaseColorMap;
#endif
#ifdef METALLICMAP
uniform sampler2D m_MetallicMap;
#endif
#ifdef ROUGHNESSMAP
uniform sampler2D m_RoughnessMap;
#endif
#ifdef EMISSIVE
uniform vec4 m_Emissive;
#endif
#ifdef EMISSIVEMAP
uniform sampler2D m_EmissiveMap;
#endif
#if defined(EMISSIVE) || defined(EMISSIVEMAP)
uniform float m_EmissivePower;
uniform float m_EmissiveIntensity;
#endif
#ifdef SPECGLOSSPIPELINE
uniform sampler2D m_SpecularMap;
uniform sampler2D m_GlossMap;
#endif
#ifdef PARALLAXMAP
uniform sampler2D m_ParallaxMap;
#endif
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
uniform float m_ParallaxHeight;
#endif
#ifdef LIGHTMAP
uniform sampler2D m_LightMap;
#endif
#if defined(NORMALMAP) || defined(PARALLAXMAP)
uniform sampler2D m_NormalMap;
varying vec4 wTangent;
#endif
varying vec3 wNormal;
#ifdef DISCARD_ALPHA
uniform float m_AlphaDiscardThreshold;
#endif
void main(){
vec2 newTexCoord;
vec3 viewDir = normalize(g_CameraPosition - wPosition);
#if defined(NORMALMAP) || defined(PARALLAXMAP)
mat3 tbnMat = mat3(wTangent.xyz, wTangent.w * cross( (wNormal), (wTangent.xyz)), wNormal.xyz);
#endif
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
vec3 vViewDir = viewDir * tbnMat;
#ifdef STEEP_PARALLAX
#ifdef NORMALMAP_PARALLAX
//parallax map is stored in the alpha channel of the normal map
newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
#else
//parallax map is a texture
newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
#endif
#else
#ifdef NORMALMAP_PARALLAX
//parallax map is stored in the alpha channel of the normal map
newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
#else
//parallax map is a texture
newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
#endif
#endif
#else
newTexCoord = texCoord;
#endif
#ifdef BASECOLORMAP
vec4 albedo = texture2D(m_BaseColorMap, newTexCoord);
#else
vec4 albedo = Color;
#endif
#ifdef ROUGHNESSMAP
float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-8);
#else
float Roughness = max(m_Roughness, 1e-8);
#endif
#ifdef METALLICMAP
float Metallic = texture2D(m_MetallicMap, newTexCoord).r;
#else
float Metallic = max(m_Metallic, 0.0);
#endif
float alpha = Color.a * albedo.a;
#ifdef DISCARD_ALPHA
if(alpha < m_AlphaDiscardThreshold){
discard;
}
#endif
// ***********************
// Read from textures
// ***********************
#if defined(NORMALMAP)
vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);
//Note the -2.0 and -1.0. We invert the green channel of the normal map,
//as it's complient with normal maps generated with blender.
//see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898
//for more explanation.
vec3 normal = normalize((normalHeight.xyz * vec3(2.0,-2.0,2.0) - vec3(1.0,-1.0,1.0)));
normal = normalize(tbnMat * normal);
//normal = normalize(normal * inverse(tbnMat));
#else
vec3 normal = normalize(wNormal);
#endif
#ifdef LIGHTMAP
vec3 lightMapColor;
#ifdef SEPARATE_TEXCOORD
lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
#else
lightMapColor = texture2D(m_LightMap, texCoord).rgb;
#endif
specularColor.rgb *= lightMapColor;
albedo.rgb *= lightMapColor;
#endif
float specular = 0.5;
#ifdef SPECGLOSSPIPELINE
vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
vec4 diffuseColor = albedo;
Roughness = 1.0 - texture2D(m_GlossMap, newTexCoord).r;
#else
float nonMetalSpec = 0.08 * specular;
vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic;
vec4 diffuseColor = albedo - albedo * Metallic;
#endif
gl_FragColor.rgb = vec3(0.0);
float ndotv = max( dot( normal, viewDir ),0.0);
for( int i = 0;i < NB_LIGHTS; i+=3){
vec4 lightColor = g_LightData[i];
vec4 lightData1 = g_LightData[i+1];
vec4 lightDir;
vec3 lightVec;
lightComputeDir(wPosition, lightColor.w, lightData1, lightDir, lightVec);
float fallOff = 1.0;
#if __VERSION__ >= 110
// allow use of control flow
if(lightColor.w > 1.0){
#endif
fallOff = computeSpotFalloff(g_LightData[i+2], lightVec);
#if __VERSION__ >= 110
}
#endif
//point light attenuation
fallOff *= lightDir.w;
lightDir.xyz = normalize(lightDir.xyz);
vec3 directDiffuse;
vec3 directSpecular;
PBR_ComputeDirectLight(normal, lightDir.xyz, viewDir,
lightColor.rgb,specular, Roughness, ndotv,
directDiffuse, directSpecular);
vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular * specularColor.rgb;
gl_FragColor.rgb += directLighting * fallOff;
}
vec3 rv = reflect(-viewDir.xyz, normal.xyz);
//prallax fix for spherical bounds from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
// g_LightProbeData.w is 1/probe radius, g_LightProbeData.xyz is the position of the lightProbe.
rv = g_LightProbeData.w * (wPosition - g_LightProbeData.xyz) +rv;
//horizon fade from http://marmosetco.tumblr.com/post/81245981087
float horiz = dot(rv, wNormal.xyz);
float horizFadePower= 1.0 - Roughness;
horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 );
horiz *= horiz;
vec3 indirectDiffuse = vec3(0.0);
vec3 indirectSpecular = vec3(0.0);
indirectDiffuse = textureCube(g_IrradianceMap, normal.xyz).rgb * diffuseColor.rgb;
indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, rv.xyz);
indirectSpecular *= vec3(horiz);
vec3 indirectLighting = indirectDiffuse + indirectSpecular;
gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting * step( 0.0, g_LightProbeData.w);
#if defined(EMISSIVE) || defined (EMISSIVEMAP)
#ifdef EMISSIVEMAP
vec4 emissive = texture2D(m_EmissiveMap, newTexCoord);
#else
vec4 emissive = m_Emissive;
#endif
gl_FragColor += emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
#endif
gl_FragColor.a = alpha;
}

@ -0,0 +1,307 @@
MaterialDef PBR Lighting {
MaterialParameters {
// Alpha threshold for fragment discarding
Float AlphaDiscardThreshold (AlphaTestFallOff)
//metalness of the material
Float Metallic : 0.0
//Roughness of the material
Float Roughness : 1.0
// Base material color
Color BaseColor
// The emissive color of the object
Color Emissive
// the emissive power
Float EmissivePower : 3.0
// the emissive intensity
Float EmissiveIntensity : 1.0
// BaseColor map
Texture2D BaseColorMap
// Specular/gloss map
Texture2D MetallicMap -LINEAR
// Roughness Map
Texture2D RoughnessMap -LINEAR
// Texture of the emissive parts of the material
Texture2D EmissiveMap
// Normal map
Texture2D NormalMap -LINEAR
// For Spec gloss pipeline
Texture2D SpecularMap
Texture2D GlossMap
Vector4 ProbeData
// Prefiltered Env Map for indirect specular lighting
TextureCubeMap PrefEnvMap -LINEAR
// Irradiance map for indirect diffuse lighting
TextureCubeMap IrradianceMap -LINEAR
//integrate BRDF map for indirect Lighting
Texture2D IntegrateBRDF -LINEAR
// Parallax/height map
Texture2D ParallaxMap -LINEAR
//Set to true is parallax map is stored in the alpha channel of the normal map
Boolean PackedNormalParallax
//Sets the relief height for parallax mapping
Float ParallaxHeight : 0.05
//Set to true to activate Steep Parallax mapping
Boolean SteepParallax
// Set to Use Lightmap
Texture2D LightMap
// Set to use TexCoord2 for the lightmap sampling
Boolean SeparateTexCoord
//shadows
Int FilterMode
Boolean HardwareShadows
Texture2D ShadowMap0
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Float ShadowIntensity
Vector4 Splits
Vector2 FadeInfo
Matrix4 LightViewProjectionMatrix0
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Vector3 LightDir
Float PCFEdge
Float ShadowMapSize
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
//For instancing
Boolean UseInstancing
//For Vertex Color
Boolean UseVertexColor
}
Technique {
LightMode SinglePassAndImageBased
VertexShader GLSL100: Common/MatDefs/Light/PBRLighting.vert
FragmentShader GLSL100: Common/MatDefs/Light/PBRLighting.frag
WorldParameters {
WorldViewProjectionMatrix
CameraPosition
WorldMatrix
}
Defines {
BASECOLORMAP : BaseColorMap
NORMALMAP : NormalMap
METALLICMAP : MetallicMap
ROUGHNESSMAP : RoughnessMap
EMISSIVEMAP : EmissiveMap
EMISSIVE : Emissive
SPECGLOSSPIPELINE : SpecularMap
PARALLAXMAP : ParallaxMap
NORMALMAP_PARALLAX : PackedNormalParallax
STEEP_PARALLAX : SteepParallax
LIGHTMAP : LightMap
SEPARATE_TEXCOORD : SeparateTexCoord
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
//INDIRECT_LIGHTING : IntegrateBRDF
VERTEX_COLOR : UseVertexColor
}
}
Technique PreShadow {
VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert
FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
COLOR_MAP : ColorMap
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
FaceCull Off
DepthTest On
DepthWrite On
PolyOffset 5 3
ColorWrite Off
}
}
Technique PostShadow15{
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique PostShadow{
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
ForcedRenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique PreNormalPass {
VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert
FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
DIFFUSEMAP_ALPHA : DiffuseMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}
Technique PreNormalPassDerivative {
VertexShader GLSL100 : Common/MatDefs/MSSAO/normal.vert
FragmentShader GLSL100 : Common/MatDefs/MSSAO/normal.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
DIFFUSEMAP_ALPHA : DiffuseMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
}
Technique GBuf {
VertexShader GLSL100: Common/MatDefs/Light/GBuf.vert
FragmentShader GLSL100: Common/MatDefs/Light/GBuf.frag
WorldParameters {
WorldViewProjectionMatrix
NormalMatrix
WorldViewMatrix
WorldMatrix
}
Defines {
VERTEX_COLOR : UseVertexColor
MATERIAL_COLORS : UseMaterialColors
V_TANGENT : VTangent
MINNAERT : Minnaert
WARDISO : WardIso
DIFFUSEMAP : DiffuseMap
NORMALMAP : NormalMap
SPECULARMAP : SpecularMap
PARALLAXMAP : ParallaxMap
}
}
}

@ -0,0 +1,65 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
uniform vec4 m_BaseColor;
uniform vec4 g_AmbientLightColor;
varying vec2 texCoord;
#ifdef SEPARATE_TEXCOORD
varying vec2 texCoord2;
attribute vec2 inTexCoord2;
#endif
varying vec4 Color;
attribute vec3 inPosition;
attribute vec2 inTexCoord;
attribute vec3 inNormal;
#ifdef VERTEX_COLOR
attribute vec4 inColor;
#endif
varying vec3 wNormal;
varying vec3 wPosition;
#if defined(NORMALMAP) || defined(PARALLAXMAP)
attribute vec4 inTangent;
varying vec4 wTangent;
#endif
void main(){
vec4 modelSpacePos = vec4(inPosition, 1.0);
vec3 modelSpaceNorm = inNormal;
#if ( defined(NORMALMAP) || defined(PARALLAXMAP)) && !defined(VERTEX_LIGHTING)
vec3 modelSpaceTan = inTangent.xyz;
#endif
#ifdef NUM_BONES
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Skinning_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
gl_Position = TransformWorldViewProjection(modelSpacePos);
texCoord = inTexCoord;
#ifdef SEPARATE_TEXCOORD
texCoord2 = inTexCoord2;
#endif
wPosition = TransformWorld(modelSpacePos).xyz;
wNormal = TransformWorldNormal(modelSpaceNorm);
#if defined(NORMALMAP) || defined(PARALLAXMAP)
wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w);
#endif
Color = m_BaseColor;
#ifdef VERTEX_COLOR
Color *= inColor;
#endif
}

@ -0,0 +1,45 @@
MaterialDef Simple {
MaterialParameters {
TextureCubeMap CubeMap
}
Technique {
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
CameraPosition
}
VertexShaderNodes {
ShaderNode Reflect {
Definition : Reflect : Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
InputMappings {
normal = Attr.inNormal
position = Global.position.xyz
worldMatrix = WorldParam.WorldMatrix
camPosition = WorldParam.CameraPosition
}
}
ShaderNode CommonVert {
Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
InputMappings {
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
modelPosition = Global.position.xyz
}
OutputMappings {
Global.position = projPosition
}
}
}
FragmentShaderNodes {
ShaderNode EnvMapping {
Definition : EnvMapping : Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
InputMappings {
refVec = Reflect.refVec
cubeMap = MatParam.CubeMap
}
OutputMappings {
Global.color = color
}
}
}
}
}

@ -0,0 +1,22 @@
ShaderNodeDefinitions{
ShaderNodeDefinition EnvMapping {
Type: Fragment
Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
Shader GLSL130: Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
Documentation{
fetches a texel in a cube map
@input vec3 refVec the reflection vector
@input samplerCube cubeMap the cube map
@output vec4 color the output color
}
Input {
vec3 refVec
samplerCube cubeMap
}
Output {
vec4 color
}
}
}

@ -0,0 +1,8 @@
void main(){
//@input vec3 refVec the reflection vector
//@input samplerCube cubeMap the cube map
//@output vec4 color the output color
color = textureCubeLod(cubeMap, refVec, 0.0);
}

@ -0,0 +1,8 @@
void main(){
//@input vec3 refVec the reflection vector
//@input samplerCube cubeMap the cube map
//@output vec4 color the output color
color = textureLod(cubeMap, refVec, 0.0);
}

@ -0,0 +1,25 @@
ShaderNodeDefinitions{
ShaderNodeDefinition Reflect {
Type: Vertex
Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/reflect100.vert
Documentation{
Computes the relfection vector necessary to do some environment mapping
@input vec3 position position in model space
@input vec3 normal the normal of the vertex
@input vec3 camPosition camera position in world space
@input mat4 worldMatrix the world matrix
@output vec3 refVec the reflection vector
}
Input {
vec3 position
vec3 normal
vec3 camPosition
mat4 worldMatrix
}
Output {
vec3 refVec
}
}
}

@ -0,0 +1,14 @@
void main(){
//@input vec3 position position in model space
//@input vec3 normal the normal of the vertex
//@input vec3 camPosition camera position in world space
//@input mat4 worldMatrix the world view matrix
//@output vec3 refVec the reflection vector
vec3 worldPos = (worldMatrix * vec4(position, 1.0)).xyz;
vec3 N = normalize((worldMatrix * vec4(normal, 0.0)).xyz);
vec3 I = normalize( camPosition - worldPos ).xyz;
refVec.xyz = reflect(-I, N);
}

@ -60,14 +60,16 @@ vec4 TransformWorldViewProjection(vec4 position)
return g_ViewProjectionMatrix * TransformWorld(position);
}
vec3 TransformNormal(vec3 vec)
{
vec3 TransformWorldNormal(vec3 vec) {
vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
inInstanceData[2].w, inInstanceData[3].w);
vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz;
return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
}
vec3 TransformNormal(vec3 vec)
{
return (g_ViewMatrix * vec4(TransformWorldNormal(vec), 0.0)).xyz;
}
// Prevent user from using g_** matrices which will have invalid data in this case.
@ -97,4 +99,9 @@ vec3 TransformNormal(vec3 normal) {
return g_NormalMatrix * normal;
}
vec3 TransformWorldNormal(vec3 normal) {
return normalize((g_WorldMatrix * vec4(normal,0.0)).xyz);
}
#endif

@ -0,0 +1,154 @@
#ifndef PI
#define PI 3.14159265358979323846264
#endif
//Specular fresnel computation
vec3 F_Shlick(float vh, vec3 F0){
float fresnelFact = pow(2.0, (-5.55473*vh - 6.98316) * vh);
return mix(F0, vec3(1.0, 1.0, 1.0), fresnelFact);
}
void PBR_ComputeDirectLightSpecWF(vec3 normal, vec3 lightDir, vec3 viewDir,
vec3 lightColor, vec3 specColor, float roughness, float ndotv,
out vec3 outDiffuse, out vec3 outSpecular){
// Compute halfway vector.
vec3 halfVec = normalize(lightDir + viewDir);
// Compute ndotl, ndoth, vdoth terms which are needed later.
float ndotl = max( dot(normal, lightDir), 0.0);
float ndoth = max( dot(normal, halfVec), 0.0);
float hdotv = max( dot(viewDir, halfVec), 0.0);
// Compute diffuse using energy-conserving Lambert.
// Alternatively, use Oren-Nayar for really rough
// materials or if you have lots of processing power ...
outDiffuse = vec3(ndotl) * lightColor;
//cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
float alpha = roughness * roughness;
//D, GGX normaal Distribution function
float alpha2 = alpha * alpha;
float sum = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0);
float denom = PI * sum * sum;
float D = alpha2 / denom;
// Compute Fresnel function via Schlick's approximation.
vec3 fresnel = F_Shlick(hdotv, specColor);
//G Shchlick GGX Gometry shadowing term, k = alpha/2
float k = alpha * 0.5;
// UE4 way to optimise shlick GGX Gometry shadowing term
//http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html
float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k );
float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k );
// the max here is to avoid division by 0 that may cause some small glitches.
float G = 1.0/max( G_V * G_L ,0.01);
float specular = D * G * ndotl;
outSpecular = fresnel * vec3(specular) * lightColor;
}
void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
vec3 lightColor, float fZero, float roughness, float ndotv,
out vec3 outDiffuse, out vec3 outSpecular){
// Compute halfway vector.
vec3 halfVec = normalize(lightDir + viewDir);
// Compute ndotl, ndoth, vdoth terms which are needed later.
float ndotl = max( dot(normal, lightDir), 0.0);
float ndoth = max( dot(normal, halfVec), 0.0);
float hdotv = max( dot(viewDir, halfVec), 0.0);
// Compute diffuse using energy-conserving Lambert.
// Alternatively, use Oren-Nayar for really rough
// materials or if you have lots of processing power ...
outDiffuse = vec3(ndotl) * lightColor;
//cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
float alpha = roughness * roughness;
//D, GGX normaal Distribution function
float alpha2 = alpha * alpha;
float sum = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0);
float denom = PI * sum * sum;
float D = alpha2 / denom;
// Compute Fresnel function via Schlick's approximation.
float fresnel = fZero + ( 1.0 - fZero ) * pow( 2.0, (-5.55473 * hdotv - 6.98316) * hdotv);
//G Shchlick GGX Gometry shadowing term, k = alpha/2
float k = alpha * 0.5;
/*
//classic Schlick ggx
float G_V = ndotv / (ndotv * (1.0 - k) + k);
float G_L = ndotl / (ndotl * (1.0 - k) + k);
float G = ( G_V * G_L );
float specular =(D* fresnel * G) /(4 * ndotv);
*/
// UE4 way to optimise shlick GGX Gometry shadowing term
//http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html
float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k );
float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k );
// the max here is to avoid division by 0 that may cause some small glitches.
float G = 1.0/max( G_V * G_L ,0.01);
float specular = D * fresnel * G * ndotl;
outSpecular = vec3(specular) * lightColor;
}
//https://knarkowicz.wordpress.com/2014/12/27/analytical-dfg-term-for-ibl/
vec3 EnvDFGPolynomial( vec3 specularColor, float roughness, float ndotv ){
float x = 1.0 - roughness;
float y = ndotv;
float b1 = -0.1688;
float b2 = 1.895;
float b3 = 0.9903;
float b4 = -4.853;
float b5 = 8.404;
float b6 = -5.069;
float bias = clamp( min( b1 * x + b2 * x * x, b3 + b4 * y + b5 * y * y + b6 * y * y * y ), 0.0, 1.0 );
float d0 = 0.6045;
float d1 = 1.699;
float d2 = -0.5228;
float d3 = -3.603;
float d4 = 1.404;
float d5 = 0.1939;
float d6 = 2.661;
float delta = clamp(( d0 + d1 * x + d2 * y + d3 * x * x + d4 * x * y + d5 * y * y + d6 * x * x * x ), 0.0, 1.0);
float scale = delta - bias;
bias *= clamp( 2.5 / (roughness) * specularColor.y, 0.0, 1.0 );
return specularColor * scale + bias;
}
vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
//TODO magic values should be replaced by defines.
float Lod = log2(Roughness) * 1.1 + 6.0 - 2.0;
vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz,Lod).rgb;
vec2 EnvBRDF = texture2D(integrateBRDF,vec2(Roughness, ndotv)).rg;
return PrefilteredColor * ( SpecularColor * EnvBRDF.x+ EnvBRDF.y );
}
vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
//TODO magic values should be replaced by defines.
float Lod = log2(Roughness) * 1.5 + 6.0 - 1.0;
vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz, Lod).rgb;
return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv);
}

@ -31,9 +31,7 @@
*/
package com.jme3.material.plugins;
import com.jme3.material.logic.MultiPassLightingLogic;
import com.jme3.material.logic.SinglePassLightingLogic;
import com.jme3.material.logic.DefaultTechniqueDefLogic;
import com.jme3.material.logic.*;
import com.jme3.asset.*;
import com.jme3.material.*;
import com.jme3.material.RenderState.BlendEquation;
@ -121,9 +119,21 @@ public class J3MLoader implements AssetLoader {
if (split.length != 2){
throw new IOException("LightMode statement syntax incorrect");
}
LightMode lm = LightMode.valueOf(split[1]);
technique.setLightMode(lm);
}
// LightMode <SPACE>
private void readLightSpace(String statement) throws IOException{
String[] split = statement.split(whitespacePattern);
if (split.length != 2){
throw new IOException("LightSpace statement syntax incorrect");
}
TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]);
technique.setLightSpace(ls);
}
// ShadowMode <MODE>
private void readShadowMode(String statement) throws IOException{
@ -543,6 +553,8 @@ public class J3MLoader implements AssetLoader {
readShaderStatement(statement.getLine());
}else if (split[0].equals("LightMode")){
readLightMode(statement.getLine());
}else if (split[0].equals("LightSpace")){
readLightSpace(statement.getLine());
}else if (split[0].equals("ShadowMode")){
readShadowMode(statement.getLine());
}else if (split[0].equals("WorldParameters")){
@ -650,6 +662,9 @@ public class J3MLoader implements AssetLoader {
case StaticPass:
technique.setLogic(new StaticPassLightingLogic(technique));
break;
case SinglePassAndImageBased:
technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique));
break;
default:
throw new UnsupportedOperationException();
}

@ -296,6 +296,12 @@ public class DDSLoader implements AssetLoader {
// exit here, the rest of the structure is not valid
// the real format will be available in the DX10 header
return;
case 113:
compressed = false;
bpp = 64;
pixelFormat = Image.Format.RGBA16F;
break;
default:
throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc));
}

@ -0,0 +1,354 @@
/*
* Copyright (c) 2009-2015 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.texture.plugins.ktx;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.TextureKey;
import com.jme3.renderer.Caps;
import com.jme3.renderer.opengl.GLImageFormat;
import com.jme3.renderer.opengl.GLImageFormats;
import com.jme3.texture.Image;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import com.jme3.util.LittleEndien;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* A KTX file loader
* KTX file format is an image container defined by the Kronos group
* See specs here https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
*
* This loader doesn't support compressed files yet.
*
* @author Nehon
*/
public class KTXLoader implements AssetLoader {
private final static Logger log = Logger.getLogger(KTXLoader.class.getName());
private final static byte[] fileIdentifier = {
(byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A
};
private boolean slicesInside = false;
@Override
public Object load(AssetInfo info) throws IOException {
if (!(info.getKey() instanceof TextureKey)) {
throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
}
InputStream in = null;
try {
in = info.openStream();
Image img = load(in);
return img;
} finally {
if (in != null) {
in.close();
}
}
}
private Image load(InputStream stream) {
byte[] fileId = new byte[12];
DataInput in = new DataInputStream(stream);
try {
stream.read(fileId, 0, 12);
if (!checkFileIdentifier(fileId)) {
throw new IllegalArgumentException("Unrecognized ktx file identifier : " + new String(fileId) + " should be " + new String(fileIdentifier));
}
int endianness = in.readInt();
//opposite endianness
if (endianness == 0x01020304) {
in = new LittleEndien(stream);
}
int glType = in.readInt();
int glTypeSize = in.readInt();
int glFormat = in.readInt();
int glInternalFormat = in.readInt();
int glBaseInternalFormat = in.readInt();
int pixelWidth = in.readInt();
int pixelHeight = in.readInt();
int pixelDepth = in.readInt();
int numberOfArrayElements = in.readInt();
int numberOfFaces = in.readInt();
int numberOfMipmapLevels = in.readInt();
int bytesOfKeyValueData = in.readInt();
log.log(Level.FINE, "glType = {0}", glType);
log.log(Level.FINE, "glTypeSize = {0}", glTypeSize);
log.log(Level.FINE, "glFormat = {0}", glFormat);
log.log(Level.FINE, "glInternalFormat = {0}", glInternalFormat);
log.log(Level.FINE, "glBaseInternalFormat = {0}", glBaseInternalFormat);
log.log(Level.FINE, "pixelWidth = {0}", pixelWidth);
log.log(Level.FINE, "pixelHeight = {0}", pixelHeight);
log.log(Level.FINE, "pixelDepth = {0}", pixelDepth);
log.log(Level.FINE, "numberOfArrayElements = {0}", numberOfArrayElements);
log.log(Level.FINE, "numberOfFaces = {0}", numberOfFaces);
log.log(Level.FINE, "numberOfMipmapLevels = {0}", numberOfMipmapLevels);
log.log(Level.FINE, "bytesOfKeyValueData = {0}", bytesOfKeyValueData);
if((numberOfFaces >1 && pixelDepth >1) || (numberOfFaces >1 && numberOfArrayElements >1) || (pixelDepth >1 && numberOfArrayElements >1)){
throw new UnsupportedOperationException("jME doesn't support cube maps of 3D textures or arrays of 3D texture or arrays of cube map of 3d textures");
}
PixelReader pixelReader = parseMetaData(bytesOfKeyValueData, in);
if (pixelReader == null){
pixelReader = new SrTuRoPixelReader();
}
//some of the values may be 0 we need them at least to be 1
pixelDepth = Math.max(1, pixelDepth);
numberOfArrayElements = Math.max(1, numberOfArrayElements);
numberOfFaces = Math.max(1, numberOfFaces);
numberOfMipmapLevels = Math.max(1, numberOfMipmapLevels);
int nbSlices = Math.max(numberOfFaces,numberOfArrayElements);
Image.Format imgFormat = getImageFormat(glFormat, glInternalFormat, glType);
log.log(Level.FINE, "img format {0}", imgFormat.toString());
int bytePerPixel = imgFormat.getBitsPerPixel() / 8;
int byteBuffersSize = computeBuffersSize(numberOfMipmapLevels, pixelWidth, pixelHeight, bytePerPixel, pixelDepth);
log.log(Level.FINE, "data size {0}", byteBuffersSize);
int[] mipMapSizes = new int[numberOfMipmapLevels];
Image image = createImage(nbSlices, byteBuffersSize, imgFormat, pixelWidth, pixelHeight, pixelDepth);
byte[] pixelData = new byte[bytePerPixel];
int offset = 0;
//iterate over data
for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
//size of the image in byte.
//this value is bogus in many example, when using mipmaps.
//instead we compute the theorical size and display a warning when it does not match.
int fileImageSize = in.readInt();
int width = Math.max(1, pixelWidth >> mipLevel);
int height = Math.max(1, pixelHeight >> mipLevel);
int imageSize = width * height * bytePerPixel;
mipMapSizes[mipLevel] = imageSize;
log.log(Level.FINE, "current mip size {0}", imageSize);
if(fileImageSize != imageSize){
log.log(Level.WARNING, "Mip map size is wrong in the file for mip level {0} size is {1} should be {2}", new Object[]{mipLevel, fileImageSize, imageSize});
}
for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) {
for (int face = 0; face < numberOfFaces; face++) {
int nbPixelRead = 0;
for (int depth = 0; depth < pixelDepth; depth++) {
ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem));
log.log(Level.FINE, "position {0}", byteBuffer.position());
byteBuffer.position(offset);
nbPixelRead = pixelReader.readPixels(width, height, pixelData, byteBuffer, in);
}
//cube padding
if (numberOfFaces == 6 && numberOfArrayElements == 0) {
in.skipBytes(3 - ((nbPixelRead + 3) % 4));
}
}
}
//mip padding
log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4)));
in.skipBytes(3 - ((imageSize + 3) % 4));
offset+=imageSize;
}
//there are loaded mip maps we set the sizes
if(numberOfMipmapLevels >1){
image.setMipMapSizes(mipMapSizes);
}
//if 3D texture and slices' orientation is inside, we reverse the data array.
if(pixelDepth > 1 && slicesInside){
Collections.reverse(image.getData());
}
return image;
} catch (IOException ex) {
Logger.getLogger(KTXLoader.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
/**
* returns the slice from the face and the array index
* @param face the face
* @param arrayElem the array index
* @return
*/
private static int getSlice(int face, int arrayElem) {
return Math.max(face, arrayElem);
}
/**
* Computes a buffer size from given parameters
* @param numberOfMipmapLevels
* @param pixelWidth
* @param pixelHeight
* @param bytePerPixel
* @param pixelDepth
* @return
*/
private int computeBuffersSize(int numberOfMipmapLevels, int pixelWidth, int pixelHeight, int bytePerPixel, int pixelDepth) {
int byteBuffersSize = 0;
for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
int width = Math.max(1, pixelWidth >> mipLevel);
int height = Math.max(1, pixelHeight >> mipLevel);
byteBuffersSize += width * height * bytePerPixel;
log.log(Level.FINE, "mip level size : {0} : {1}", new Object[]{mipLevel, width * height * bytePerPixel});
}
return byteBuffersSize * pixelDepth;
}
/**
* Create an image with given parameters
* @param nbSlices
* @param byteBuffersSize
* @param imgFormat
* @param pixelWidth
* @param pixelHeight
* @param depth
* @return
*/
private Image createImage(int nbSlices, int byteBuffersSize, Image.Format imgFormat, int pixelWidth, int pixelHeight, int depth) {
ArrayList<ByteBuffer> imageData = new ArrayList<ByteBuffer>(nbSlices);
for (int i = 0; i < nbSlices; i++) {
imageData.add(BufferUtils.createByteBuffer(byteBuffersSize));
}
Image image = new Image(imgFormat, pixelWidth, pixelHeight, depth, imageData, ColorSpace.sRGB);
return image;
}
/**
* Parse the file metaData to select the PixelReader that suits the file
* coordinates orientation
* @param bytesOfKeyValueData
* @param in
* @return
* @throws IOException
*/
private PixelReader parseMetaData(int bytesOfKeyValueData, DataInput in) throws IOException {
PixelReader pixelReader = null;
for (int i = 0; i < bytesOfKeyValueData;) {
//reading key values
int keyAndValueByteSize = in.readInt();
byte[] keyValue = new byte[keyAndValueByteSize];
in.readFully(keyValue);
//parsing key values
String[] kv = new String(keyValue).split("\0");
for (int j = 0; j < kv.length; j += 2) {
System.err.println("key : " + kv[j]);
System.err.println("value : " + kv[j + 1]);
if(kv[j].equalsIgnoreCase("KTXorientation")){
if(kv[j + 1].startsWith("S=r,T=d") ){
pixelReader = new SrTdRiPixelReader();
}else{
pixelReader = new SrTuRoPixelReader();
}
if(kv[j + 1].contains("R=i")){
slicesInside = true;
}
}
}
//padding
int padding = 3 - ((keyAndValueByteSize + 3) % 4);
if (padding > 0) {
in.skipBytes(padding);
}
i += 4 + keyAndValueByteSize + padding;
}
return pixelReader;
}
/**
* Chacks the file id
* @param b
* @return
*/
private boolean checkFileIdentifier(byte[] b) {
boolean check = true;
for (int i = 0; i < 12; i++) {
if (b[i] != fileIdentifier[i]) {
check = false;
}
}
return check;
}
/**
* returns the JME image format from gl formats and types.
* @param glFormat
* @param glInternalFormat
* @param glType
* @return
*/
private Image.Format getImageFormat(int glFormat, int glInternalFormat, int glType) {
EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps);
for (GLImageFormat[] format : formats) {
for (int j = 0; j < format.length; j++) {
GLImageFormat glImgFormat = format[j];
if (glImgFormat != null) {
if (glImgFormat.format == glFormat && glImgFormat.dataType == glType) {
if (glFormat == glInternalFormat || glImgFormat.internalFormat == glInternalFormat) {
return Image.Format.values()[j];
}
}
}
}
}
return null;
}
}

@ -0,0 +1,279 @@
/*
* Copyright (c) 2009-2015 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.texture.plugins.ktx;
import com.jme3.renderer.Caps;
import com.jme3.renderer.opengl.GLImageFormat;
import com.jme3.renderer.opengl.GLImageFormats;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.Texture3D;
import com.jme3.texture.TextureArray;
import com.jme3.texture.TextureCubeMap;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* This class allows one to write a KTX file.
* It doesn't support compressed data yet.
*
* @author Nehon
*/
public class KTXWriter {
private final static Logger log = Logger.getLogger(KTXWriter.class.getName());
private final String filePath;
private final static byte[] fileIdentifier = {
(byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A
};
/**
* Creates a KTXWriter that will write files in the given path
* @param path
*/
public KTXWriter(String path) {
filePath = path;
}
/**
* Writes a 2D image from the given image in a KTX file named from the fileName param
* Note that the fileName should contain the extension (.ktx sounds like a wise choice)
* @param image the image to write
* @param fileName the name of the file to write
*/
public void write(Image image, String fileName) {
write(image, Texture2D.class, fileName);
}
/**
* Writes an image with the given params
*
* textureType, allows one to write textureArrays, Texture3D, and TextureCubeMaps.
* Texture2D will write a 2D image.
* Note that the fileName should contain the extension (.ktx sounds like a wise choice)
* @param image the image to write
* @param textureType the texture type
* @param fileName the name of the file to write
*/
public void write(Image image, Class<? extends Texture> textureType, String fileName) {
FileOutputStream outs = null;
try {
File file = new File(filePath + "/" + fileName);
outs = new FileOutputStream(file);
DataOutput out = new DataOutputStream(outs);
//fileID
out.write(fileIdentifier);
//endianness
out.writeInt(0x04030201);
GLImageFormat format = getGlFormat(image.getFormat());
//glType
out.writeInt(format.dataType);
//glTypeSize
out.writeInt(1);
//glFormat
out.writeInt(format.format);
//glInernalFormat
out.writeInt(format.internalFormat);
//glBaseInternalFormat
out.writeInt(format.format);
//pixelWidth
out.writeInt(image.getWidth());
//pixelHeight
out.writeInt(image.getHeight());
int pixelDepth = 1;
int numberOfArrayElements = 1;
int numberOfFaces = 1;
if (image.getDepth() > 1) {
//pixelDepth
if (textureType == Texture3D.class) {
pixelDepth = image.getDepth();
}
}
if(image.getData().size()>1){
//numberOfArrayElements
if (textureType == TextureArray.class) {
numberOfArrayElements = image.getData().size();
}
//numberOfFaces
if (textureType == TextureCubeMap.class) {
numberOfFaces = image.getData().size();
}
}
out.writeInt(pixelDepth);
out.writeInt(numberOfArrayElements);
out.writeInt(numberOfFaces);
int numberOfMipmapLevels = 1;
//numberOfMipmapLevels
if (image.hasMipmaps()) {
numberOfMipmapLevels = image.getMipMapSizes().length;
}
out.writeInt(numberOfMipmapLevels);
//bytesOfKeyValueData
String keyValues = "KTXorientation\0S=r,T=u\0";
int bytesOfKeyValueData = keyValues.length() + 4;
int padding = 3 - ((bytesOfKeyValueData + 3) % 4);
bytesOfKeyValueData += padding;
out.writeInt(bytesOfKeyValueData);
//keyAndValueByteSize
out.writeInt(bytesOfKeyValueData - 4 - padding);
//values
out.writeBytes(keyValues);
pad(padding, out);
int offset = 0;
//iterate over data
for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
int width = Math.max(1, image.getWidth() >> mipLevel);
int height = Math.max(1, image.getHeight() >> mipLevel);
int imageSize;
if (image.hasMipmaps()) {
imageSize = image.getMipMapSizes()[mipLevel];
} else {
imageSize = width * height * image.getFormat().getBitsPerPixel() / 8;
}
out.writeInt(imageSize);
for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) {
for (int face = 0; face < numberOfFaces; face++) {
int nbPixelWritten = 0;
for (int depth = 0; depth < pixelDepth; depth++) {
ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem));
// BufferUtils.ensureLargeEnough(byteBuffer, imageSize);
log.log(Level.FINE, "position {0}", byteBuffer.position());
byteBuffer.position(offset);
byte[] b = getByteBufferArray(byteBuffer, imageSize);
out.write(b);
nbPixelWritten = b.length;
}
//cube padding
if (numberOfFaces == 6 && numberOfArrayElements == 0) {
padding = 3 - ((nbPixelWritten + 3) % 4);
pad(padding, out);
}
}
}
//mip padding
log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4)));
padding = 3 - ((imageSize + 3) % 4);
pad(padding, out);
offset += imageSize;
}
} catch (FileNotFoundException ex) {
Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
if(outs != null){
outs.close();
}
} catch (IOException ex) {
Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/**
* writes padding data to the output padding times.
* @param padding
* @param out
* @throws IOException
*/
private void pad(int padding, DataOutput out) throws IOException {
//padding
for (int i = 0; i < padding; i++) {
out.write('\0');
}
}
/**
* Get a byte array from a byte buffer.
* @param byteBuffer the byte buffer
* @param size the size of the resulting array
* @return
*/
private byte[] getByteBufferArray(ByteBuffer byteBuffer, int size) {
byte[] b;
if (byteBuffer.hasArray()) {
b = byteBuffer.array();
} else {
b = new byte[size];
byteBuffer.get(b, 0, size);
}
return b;
}
/**
* get the glformat from JME image Format
* @param format
* @return
*/
private GLImageFormat getGlFormat(Image.Format format) {
EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps);
return formats[0][format.ordinal()];
}
/**
* get a slice from the face and the array index
* @param face
* @param arrayElem
* @return
*/
private static int getSlice(int face,int arrayElem) {
return Math.max(face, arrayElem);
}
}

@ -0,0 +1,46 @@
/*
* Copyright (c) 2009-2015 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.texture.plugins.ktx;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
*
* Interface used to read a set of pixels in a KTX file
* @author Nehon
*/
public interface PixelReader {
public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException;
}

@ -0,0 +1,60 @@
/*
* Copyright (c) 2009-2015 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.texture.plugins.ktx;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* reads the pixels of an image whose origin is the top left corner
*
* @author Nehon
*/
public class SrTdRiPixelReader implements PixelReader {
@Override
public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException {
int pixelRead = 0;
for (int row = pixelHeight - 1; row >= 0; row--) {
for (int pixel = 0; pixel < pixelWidth; pixel++) {
in.readFully(pixelData);
for (int i = 0; i < pixelData.length; i++) {
buffer.put((row * pixelWidth + pixel) * pixelData.length + i, pixelData[i]);
}
pixelRead += pixelData.length;
}
}
return pixelRead;
}
}

@ -0,0 +1,58 @@
/*
* Copyright (c) 2009-2015 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.texture.plugins.ktx;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* reads the pixels of an image whose origin is the bottom left corner
*
* @author Nehon
*/
public class SrTuRoPixelReader implements PixelReader {
@Override
public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException {
int pixelRead = 0;
for (int row = 0; row < pixelHeight; row++) {
for (int pixel = 0; pixel < pixelWidth; pixel++) {
in.readFully(pixelData);
buffer.put(pixelData);
pixelRead += pixelData.length;
}
}
return pixelRead;
}
}

@ -60,7 +60,7 @@ public class TestBatchNodeCluster extends SimpleApplication {
settingst.setVSync(false);
settingst.setFullscreen(false);
app.setSettings(settingst);
app.setShowSettings(false);
app.setShowSettings(false);
app.start();
}
private ActionListener al = new ActionListener() {

@ -0,0 +1,90 @@
package jme3test.light;
import com.jme3.app.SimpleApplication;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.light.DirectionalLight;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.DirectionalLightShadowRenderer;
public class TestColorApp extends SimpleApplication {
public static void main(String[] args) {
TestColorApp app = new TestColorApp();
app.start();
}
@Override
public void simpleInitApp() {
// Lights
DirectionalLight sun = new DirectionalLight();
Vector3f sunPosition = new Vector3f(1, -1, 1);
sun.setDirection(sunPosition);
sun.setColor(new ColorRGBA(1f,1f,1f,1f));
rootNode.addLight(sun);
//DirectionalLightShadowFilter sun_renderer = new DirectionalLightShadowFilter(assetManager, 2048, 4);
DirectionalLightShadowRenderer sun_renderer = new DirectionalLightShadowRenderer(assetManager, 2048, 1);
sun_renderer.setLight(sun);
viewPort.addProcessor(sun_renderer);
// FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
// fpp.addFilter(sun_renderer);
// viewPort.addProcessor(fpp);
rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
// Camera
viewPort.setBackgroundColor(new ColorRGBA(.6f, .6f, .6f, 1f));
ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
// Objects
// Ground Object
final Geometry groundBoxWhite = new Geometry("Box", new Box(7.5f, 7.5f, .25f));
float[] f = {-FastMath.PI / 2, 3 * FastMath.PI / 2, 0f};
groundBoxWhite.setLocalRotation(new Quaternion(f));
groundBoxWhite.move(7.5f, -.75f, 7.5f);
final Material groundMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
groundMaterial.setColor("Diffuse", new ColorRGBA(.9f, .9f, .9f, .9f));
groundBoxWhite.setMaterial(groundMaterial);
groundBoxWhite.addControl(chaseCam);
rootNode.attachChild(groundBoxWhite);
// Planter
Geometry planterBox = new Geometry("Box", new Box(.5f, .5f, .5f));
final Material planterMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
planterMaterial.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
planterBox.setMaterial(groundMaterial);
planterBox.setLocalTranslation(10, 0, 9);
rootNode.attachChild(planterBox);
// Action!
inputManager.addMapping("on", new KeyTrigger(KeyInput.KEY_Z));
inputManager.addMapping("off", new KeyTrigger(KeyInput.KEY_X));
inputManager.addListener(new AnalogListener() {
@Override
public void onAnalog(String s, float v, float v1) {
if (s.equals("on")) {
groundBoxWhite.setMaterial(planterMaterial);
}
if (s.equals("off")) {
groundBoxWhite.setMaterial(groundMaterial);
}
}
}, "on", "off");
inputEnabled = true;
}
}

@ -0,0 +1,131 @@
/*
* Copyright (c) 2009-2015 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 jme3test.light;
import com.jme3.app.SimpleApplication;
import com.jme3.light.DirectionalLight;
import com.jme3.light.PointLight;
import com.jme3.light.SpotLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.shadow.PointLightShadowRenderer;
import com.jme3.shadow.SpotLightShadowRenderer;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
public class TestShadowBug extends SimpleApplication {
public static void main(String[] args) {
TestShadowBug app = new TestShadowBug();
app.start();
}
@Override
public void simpleInitApp() {
flyCam.setMoveSpeed(100f);
rootNode.attachChild(makeFloor());
Node characters = new Node("Characters");
characters.setShadowMode(ShadowMode.Cast);
rootNode.attachChild(characters);
Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
golem.scale(0.5f);
golem.setLocalTranslation(200.0f, -6f, 200f);
golem.setShadowMode(ShadowMode.CastAndReceive);
characters.attachChild(golem);
DirectionalLight sun = new DirectionalLight();
sun.setDirection(new Vector3f(-1f, -1f, 1f));
sun.setColor(ColorRGBA.White.mult(1.3f));
rootNode.addLight(sun);
characters.addLight(sun);
SpotLight spot = new SpotLight();
spot.setSpotRange(13f); // distance
spot.setSpotInnerAngle(15f * FastMath.DEG_TO_RAD); // inner light cone (central beam)
spot.setSpotOuterAngle(20f * FastMath.DEG_TO_RAD); // outer light cone (edge of the light)
spot.setColor(ColorRGBA.White.mult(1.3f)); // light color
spot.setPosition(new Vector3f(192.0f, -1f, 192f));
spot.setDirection(new Vector3f(1, -0.5f, 1));
rootNode.addLight(spot);
PointLight lamp_light = new PointLight();
lamp_light.setColor(ColorRGBA.Yellow);
lamp_light.setRadius(20f);
lamp_light.setPosition(new Vector3f(210.0f, 0f, 210f));
rootNode.addLight(lamp_light);
SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 512);
slsr.setLight(spot);
slsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
slsr.setShadowIntensity(0.6f);
slsr.setFlushQueues(false);
viewPort.addProcessor(slsr);
PointLightShadowRenderer plsr = new PointLightShadowRenderer(assetManager, 512);
plsr.setLight(lamp_light);
plsr.setShadowIntensity(0.6f);
plsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
plsr.setFlushQueues(false);
viewPort.addProcessor(plsr);
viewPort.getCamera().setLocation(new Vector3f(192.0f, 10f, 192f));
float[] angles = new float[]{3.14f/2, 3.14f/2, 0};
viewPort.getCamera().setRotation(new Quaternion(angles));
}
protected Geometry makeFloor() {
Box box = new Box(220, .2f, 220);
box.scaleTextureCoordinates(new Vector2f(10, 10));
Geometry floor = new Geometry("the Floor", box);
floor.setLocalTranslation(200, -9, 200);
Material matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
grass.setWrap(WrapMode.Repeat);
matGroundL.setTexture("DiffuseMap", grass);
floor.setMaterial(matGroundL);
floor.setShadowMode(ShadowMode.CastAndReceive);
return floor;
}
}

@ -0,0 +1,96 @@
/*
* Copyright (c) 2009-2015 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 jme3test.light;
import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
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.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.util.TangentBinormalGenerator;
/**
*
* @author Nehon
*/
public class TestTangentCube extends SimpleApplication {
public static void main(String... args) {
TestTangentCube app = new TestTangentCube();
app.start();
}
@Override
public void simpleInitApp() {
Box aBox = new Box(1, 1, 1);
Geometry aGeometry = new Geometry("Box", aBox);
TangentBinormalGenerator.generate(aBox);
Material aMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
aMaterial.setTexture("DiffuseMap",
assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
aMaterial.setTexture("NormalMap",
assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall_normal.jpg"));
aMaterial.setBoolean("UseMaterialColors", false);
aMaterial.setColor("Diffuse", ColorRGBA.White);
aMaterial.setColor("Specular", ColorRGBA.White);
aMaterial.setFloat("Shininess", 64f);
aGeometry.setMaterial(aMaterial);
// Rotate 45 degrees to see multiple faces
aGeometry.rotate(FastMath.QUARTER_PI, FastMath.QUARTER_PI, 0.0f);
rootNode.attachChild(aGeometry);
/**
* Must add a light to make the lit object visible!
*/
PointLight aLight = new PointLight();
aLight.setPosition(new Vector3f(0, 3, 3));
aLight.setColor(ColorRGBA.Red);
rootNode.addLight(aLight);
//
// AmbientLight bLight = new AmbientLight();
// bLight.setColor(ColorRGBA.Gray);
// rootNode.addLight(bLight);
ChaseCameraAppState chaser = new ChaseCameraAppState();
chaser.setTarget(aGeometry);
getStateManager().attach(chaser);
}
}

@ -0,0 +1,71 @@
/*
* Copyright (c) 2009-2015 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 jme3test.light.pbr;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.light.LightProbe;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A basic logger for environment map rendering progress.
* @author nehon
*/
public class ConsoleProgressReporter extends JobProgressAdapter<LightProbe>{
private static final Logger logger = Logger.getLogger(ConsoleProgressReporter.class.getName());
long time;
@Override
public void start() {
time = System.currentTimeMillis();
logger.log(Level.INFO,"Starting generation");
}
@Override
public void progress(double value) {
logger.log(Level.INFO, "Progress : {0}%", (value * 100));
}
@Override
public void step(String message) {
logger.info(message);
}
@Override
public void done(LightProbe result) {
long end = System.currentTimeMillis();
logger.log(Level.INFO, "Generation done in {0}", ((float)(end - time) / 1000f));
}
}

@ -0,0 +1,141 @@
package jme3test.light.pbr;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingSphere;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.LightProbe;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.ui.Picture;
import com.jme3.util.MaterialDebugAppState;
/**
* test
*
* @author nehon
*/
public class RefEnv extends SimpleApplication {
private Node tex;
private Node ref;
private Picture refDE;
private Picture refM;
public static void main(String[] args) {
RefEnv app = new RefEnv();
app.start();
}
@Override
public void simpleInitApp() {
cam.setLocation(new Vector3f(-2.3324413f, 2.9567573f, 4.6054406f));
cam.setRotation(new Quaternion(0.06310794f, 0.9321281f, -0.29613864f, 0.1986369f));
Spatial sc = assetManager.loadModel("Scenes/PBR/spheres.j3o");
rootNode.attachChild(sc);
rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Always);
ref = new Node("reference pictures");
refDE = new Picture("refDE");
refDE.setHeight(cam.getHeight());
refDE.setWidth(cam.getWidth());
refDE.setImage(assetManager,"jme3test/light/pbr/spheresRefDE.png", false);
refM = new Picture("refM");
refM.setImage(assetManager,"jme3test/light/pbr/spheresRefM.png", false);
refM.setHeight(cam.getHeight());
refM.setWidth(cam.getWidth());
ref.attachChild(refDE);
stateManager.attach(new EnvironmentCamera());
inputManager.addMapping("tex", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping("switch", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("ref", new KeyTrigger(KeyInput.KEY_R));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("tex") && isPressed) {
if (tex == null) {
return;
}
if (tex.getParent() == null) {
guiNode.attachChild(tex);
} else {
tex.removeFromParent();
}
}
if (name.equals("switch") && isPressed) {
switchMat(rootNode.getChild("Scene"));
}
if (name.equals("ref") && isPressed) {
if (ref.getParent() == null) {
guiNode.attachChild(ref);
} else {
ref.removeFromParent();
}
}
}
}, "tex", "switch", "ref");
}
private void switchMat(Spatial s) {
if (s instanceof Node) {
Node n = (Node) s;
for (Spatial children : n.getChildren()) {
switchMat(children);
}
} else if (s instanceof Geometry) {
Geometry g = (Geometry) s;
Material mat = g.getMaterial();
if (((Float) mat.getParam("Metallic").getValue()) == 1f) {
mat.setFloat("Metallic", 0);
mat.setColor("BaseColor", ColorRGBA.Black);
ref.attachChild(refDE);
refM.removeFromParent();
} else {
mat.setFloat("Metallic", 1);
mat.setColor("BaseColor", ColorRGBA.White);
ref.attachChild(refM);
refDE.removeFromParent();
}
}
}
private int frame = 0;
@Override
public void simpleUpdate(float tpf) {
frame++;
if (frame == 2) {
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
@Override
public void done(LightProbe result) {
System.err.println("Done rendering env maps");
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
// guiNode.attachChild(tex);
rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Dynamic);
}
});
((BoundingSphere) probe.getBounds()).setRadius(100);
rootNode.addLight(probe);
}
}
}

@ -0,0 +1,199 @@
/*
* Copyright (c) 2009-2015 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 jme3test.light.pbr;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingSphere;
import com.jme3.light.LightProbe;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.environment.util.LightsDebugState;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
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.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.plugins.ktx.KTXLoader;
import com.jme3.util.MaterialDebugAppState;
import com.jme3.util.SkyFactory;
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
/**
* A test case for PBR lighting.
* Still experimental.
*
* @author nehon
*/
public class TestPBRLighting extends SimpleApplication {
public static void main(String[] args) {
TestPBRLighting app = new TestPBRLighting();
app.start();
}
private Geometry model;
private DirectionalLight dl;
private Node modelNode;
private int frame = 0;
private Material pbrMat;
@Override
public void simpleInitApp() {
assetManager.registerLoader(KTXLoader.class, "ktx");
viewPort.setBackgroundColor(ColorRGBA.White);
modelNode = (Node) new Node("modelNode");
model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o");
MikktspaceTangentGenerator.generate(model);
modelNode.attachChild(model);
dl = new DirectionalLight();
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
rootNode.addLight(dl);
dl.setColor(ColorRGBA.White);
rootNode.attachChild(modelNode);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
// fpp.addFilter(new FXAAFilter());
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
// fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f));
viewPort.addProcessor(fpp);
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Sky_Cloudy.hdr", SkyFactory.EnvMapType.EquirectMap);
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap);
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/road.hdr", SkyFactory.EnvMapType.EquirectMap);
rootNode.attachChild(sky);
pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
model.setMaterial(pbrMat);
final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
stateManager.attach(envCam);
// EnvironmentManager envManager = new EnvironmentManager();
// stateManager.attach(envManager);
// envManager.setScene(rootNode);
LightsDebugState debugState = new LightsDebugState();
stateManager.attach(debugState);
ChaseCamera chaser = new ChaseCamera(cam, modelNode, inputManager);
chaser.setDragToRotate(true);
chaser.setMinVerticalRotation(-FastMath.HALF_PI);
chaser.setMaxDistance(1000);
chaser.setSmoothMotion(true);
chaser.setRotationSensitivity(10);
chaser.setZoomSensitivity(5);
flyCam.setEnabled(false);
//flyCam.setMoveSpeed(100);
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("debug") && isPressed) {
//envCam.toggleDebug();
}
if (name.equals("up") && isPressed) {
model.move(0, tpf * 100f, 0);
}
if (name.equals("down") && isPressed) {
model.move(0, -tpf * 100f, 0);
}
if (name.equals("left") && isPressed) {
model.move(0, 0, tpf * 100f);
}
if (name.equals("right") && isPressed) {
model.move(0, 0, -tpf * 100f);
}
if (name.equals("light") && isPressed) {
dl.setDirection(cam.getDirection().normalize());
}
}
}, "toggle", "light", "up", "down", "left", "right", "debug");
inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F));
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_D));
MaterialDebugAppState debug = new MaterialDebugAppState();
debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode);
getStateManager().attach(debug);
}
@Override
public void simpleUpdate(float tpf) {
frame++;
if (frame == 2) {
modelNode.removeFromParent();
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
@Override
public void done(LightProbe result) {
System.err.println("Done rendering env maps");
}
});
((BoundingSphere)probe.getBounds()).setRadius(100);
rootNode.addLight(probe);
//getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
}
if (frame > 10 && modelNode.getParent() == null) {
rootNode.attachChild(modelNode);
}
}
}

@ -0,0 +1,387 @@
/*
* Copyright (c) 2009-2015 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 jme3test.light.pbr;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingSphere;
import com.jme3.input.CameraInput;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.util.LightsDebugState;
import com.jme3.light.LightProbe;
import com.jme3.material.TechniqueDef;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
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.texture.plugins.ktx.KTXLoader;
import com.jme3.util.SkyFactory;
import com.jme3.util.TangentBinormalGenerator;
public class TestPbrEnv extends SimpleApplication implements ActionListener {
public static final int SHADOWMAP_SIZE = 1024;
private Spatial[] obj;
private Material[] mat;
private DirectionalLightShadowRenderer dlsr;
private LightsDebugState debugState;
private EnvironmentCamera envCam;
private Geometry ground;
private Material matGroundU;
private Material matGroundL;
private Geometry camGeom;
public static void main(String[] args) {
TestPbrEnv app = new TestPbrEnv();
app.start();
}
public void loadScene() {
renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass);
renderManager.setSinglePassLightBatchSize(3);
obj = new Spatial[2];
// Setup first view
mat = new Material[2];
mat[0] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat.j3m");
//mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
mat[1] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat2.j3m");
// mat[1].setBoolean("UseMaterialColors", true);
// mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
// mat[1].setColor("Diffuse", ColorRGBA.White.clone());
obj[0] = new Geometry("sphere", new Sphere(30, 30, 2));
obj[0].setShadowMode(ShadowMode.CastAndReceive);
obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f));
obj[1].setShadowMode(ShadowMode.CastAndReceive);
TangentBinormalGenerator.generate(obj[1]);
TangentBinormalGenerator.generate(obj[0]);
// for (int i = 0; i < 60; i++) {
// Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
// t.setName("Cube" + i);
// t.setLocalScale(FastMath.nextRandomFloat() * 10f);
// t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
// rootNode.attachChild(t);
// t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f));
// }
for (int i = 0; i < 2; i++) {
Spatial t = obj[0].clone(false);
t.setName("Cube" + i);
t.setLocalScale( 10f);
t.setMaterial(mat[1].clone());
rootNode.attachChild(t);
t.setLocalTranslation(i * 200f+ 100f, 50, 800f * (i));
}
Box b = new Box(1000, 2, 1000);
b.scaleTextureCoordinates(new Vector2f(20, 20));
ground = new Geometry("soil", b);
TangentBinormalGenerator.generate(ground);
ground.setLocalTranslation(0, 10, 550);
matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matGroundU.setColor("Color", ColorRGBA.Green);
// matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
// Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
// grass.setWrap(WrapMode.Repeat);
// matGroundL.setTexture("DiffuseMap", grass);
matGroundL = assetManager.loadMaterial("jme3test/light/pbr/pbrMat4.j3m");
ground.setMaterial(matGroundL);
//ground.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(ground);
l = new DirectionalLight();
l.setColor(ColorRGBA.White);
//l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal());
l.setDirection(new Vector3f(-0.2823181f, -0.41889593f, 0.863031f).normalizeLocal());
rootNode.addLight(l);
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(0.5f));
// rootNode.addLight(al);
//Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap);
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
sky.setLocalScale(350);
rootNode.attachChild(sky);
}
DirectionalLight l;
@Override
public void simpleInitApp() {
assetManager.registerLoader(KTXLoader.class, "ktx");
// put the camera in a bad position
cam.setLocation(new Vector3f(-52.433647f, 68.69636f, -118.60924f));
cam.setRotation(new Quaternion(0.10294232f, 0.25269797f, -0.027049713f, 0.96167296f));
flyCam.setMoveSpeed(100);
loadScene();
dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
dlsr.setLight(l);
//dlsr.setLambda(0.55f);
dlsr.setShadowIntensity(0.5f);
dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
//dlsr.displayDebug();
// viewPort.addProcessor(dlsr);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(6.0f)));
SSAOFilter ssao = new SSAOFilter();
ssao.setIntensity(5);
fpp.addFilter(ssao);
BloomFilter bloomFilter = new BloomFilter();
fpp.addFilter(bloomFilter);
fpp.addFilter(new FXAAFilter());
//viewPort.addProcessor(fpp);
initInputs();
// envManager = new EnvironmentManager();
// getStateManager().attach(envManager);
//
envCam = new EnvironmentCamera();
getStateManager().attach(envCam);
debugState = new LightsDebugState();
debugState.setProbeScale(5);
getStateManager().attach(debugState);
camGeom = new Geometry("camGeom", new Sphere(16, 16, 2));
// Material m = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md");
// m.setColor("Color", ColorRGBA.Green);
Material m = assetManager.loadMaterial("jme3test/light/pbr/pbrMat3.j3m");
camGeom.setMaterial(m);
camGeom.setLocalTranslation(0, 20, 0);
camGeom.setLocalScale(5);
rootNode.attachChild(camGeom);
// envManager.setScene(rootNode);
// MaterialDebugAppState debug = new MaterialDebugAppState();
// debug.registerBinding("MatDefs/PBRLighting.frag", rootNode);
// getStateManager().attach(debug);
flyCam.setDragToRotate(true);
setPauseOnLostFocus(false);
// cam.lookAt(camGeom.getWorldTranslation(), Vector3f.UNIT_Y);
}
private void fixFLyCamInputs() {
inputManager.deleteMapping(CameraInput.FLYCAM_LEFT);
inputManager.deleteMapping(CameraInput.FLYCAM_RIGHT);
inputManager.deleteMapping(CameraInput.FLYCAM_UP);
inputManager.deleteMapping(CameraInput.FLYCAM_DOWN);
inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true));
inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false));
inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
inputManager.addListener(flyCam, CameraInput.FLYCAM_LEFT, CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_UP, CameraInput.FLYCAM_DOWN);
}
private void initInputs() {
inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
inputManager.addMapping("snapshot", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping("fc", new KeyTrigger(KeyInput.KEY_F));
inputManager.addMapping("debugProbe", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("debugTex", new KeyTrigger(KeyInput.KEY_T));
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
inputManager.addMapping("delete", new KeyTrigger(KeyInput.KEY_DELETE));
inputManager.addListener(this, "delete","switchGroundMat", "snapshot", "debugTex", "debugProbe", "fc", "up", "down", "left", "right");
}
private LightProbe lastProbe;
private Node debugGui ;
@Override
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("switchGroundMat") && keyPressed) {
if (ground.getMaterial() == matGroundL) {
ground.setMaterial(matGroundU);
} else {
ground.setMaterial(matGroundL);
}
}
if (name.equals("snapshot") && keyPressed) {
envCam.setPosition(camGeom.getWorldTranslation());
lastProbe = LightProbeFactory.makeProbe(envCam, rootNode, new ConsoleProgressReporter());
((BoundingSphere)lastProbe.getBounds()).setRadius(200);
rootNode.addLight(lastProbe);
}
if (name.equals("delete") && keyPressed) {
System.err.println(rootNode.getWorldLightList().size());
rootNode.removeLight(lastProbe);
System.err.println("deleted");
System.err.println(rootNode.getWorldLightList().size());
}
if (name.equals("fc") && keyPressed) {
flyCam.setEnabled(true);
}
if (name.equals("debugProbe") && keyPressed) {
//getStateManager().getState(EnvironmentCamera.class).toggleDebug();
if (!debugState.isEnabled()) {
debugState.setEnabled(true);
debugState.setDebugMode(LightsDebugState.DebugMode.IrradianceMap);
} else if (debugState.getDebugMode() == LightsDebugState.DebugMode.IrradianceMap) {
debugState.setDebugMode(LightsDebugState.DebugMode.PrefilteredEnvMap);
} else if (debugState.getDebugMode() == LightsDebugState.DebugMode.PrefilteredEnvMap) {
debugState.setEnabled(false);
}
}
if (name.equals("debugTex") && keyPressed) {
if(debugGui == null || debugGui.getParent() == null){
debugGui = lastProbe.getDebugGui(assetManager);
debugGui.setLocalTranslation(10, 200, 0);
guiNode.attachChild(debugGui);
} else if(debugGui != null){
debugGui.removeFromParent();
}
}
if (name.equals("up")) {
up = keyPressed;
}
if (name.equals("down")) {
down = keyPressed;
}
if (name.equals("right")) {
right = keyPressed;
}
if (name.equals("left")) {
left = keyPressed;
}
if (name.equals("fwd")) {
fwd = keyPressed;
}
if (name.equals("back")) {
back = keyPressed;
}
}
boolean up = false;
boolean down = false;
boolean left = false;
boolean right = false;
boolean fwd = false;
boolean back = false;
float time = 0;
float s = 50f;
boolean initialized = false;
@Override
public void simpleUpdate(float tpf) {
if (!initialized) {
fixFLyCamInputs();
initialized = true;
}
float val = tpf * s;
if (up) {
camGeom.move(0, 0, val);
}
if (down) {
camGeom.move(0, 0, -val);
}
if (right) {
camGeom.move(-val, 0, 0);
}
if (left) {
camGeom.move(val, 0, 0);
}
}
}

@ -0,0 +1,157 @@
/*
* Copyright (c) 2009-2012 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 jme3test.material;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import com.jme3.util.SkyFactory;
import com.jme3.util.TangentBinormalGenerator;
public class TestParallaxPBR extends SimpleApplication {
private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal();
public static void main(String[] args) {
TestParallaxPBR app = new TestParallaxPBR();
app.start();
}
public void setupSkyBox() {
rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false));
}
DirectionalLight dl;
public void setupLighting() {
dl = new DirectionalLight();
dl.setDirection(lightDir);
dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1));
rootNode.addLight(dl);
}
Material mat;
public void setupFloor() {
mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR.j3m");
//mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR2.j3m");
Node floorGeom = new Node("floorGeom");
Quad q = new Quad(100, 100);
q.scaleTextureCoordinates(new Vector2f(10, 10));
Geometry g = new Geometry("geom", q);
g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
floorGeom.attachChild(g);
TangentBinormalGenerator.generate(floorGeom);
floorGeom.setLocalTranslation(-50, 22, 60);
//floorGeom.setLocalScale(100);
floorGeom.setMaterial(mat);
rootNode.attachChild(floorGeom);
}
public void setupSignpost() {
Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
TangentBinormalGenerator.generate(signpost);
signpost.setMaterial(mat);
signpost.rotate(0, FastMath.HALF_PI, 0);
signpost.setLocalTranslation(12, 23.5f, 30);
signpost.setLocalScale(4);
signpost.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(signpost);
}
@Override
public void simpleInitApp() {
cam.setLocation(new Vector3f(-15.445636f, 30.162927f, 60.252777f));
cam.setRotation(new Quaternion(0.05173137f, 0.92363626f, -0.13454558f, 0.35513034f));
flyCam.setMoveSpeed(30);
setupLighting();
setupSkyBox();
setupFloor();
setupSignpost();
inputManager.addListener(new AnalogListener() {
public void onAnalog(String name, float value, float tpf) {
if ("heightUP".equals(name)) {
parallaxHeigh += 0.01;
mat.setFloat("ParallaxHeight", parallaxHeigh);
}
if ("heightDown".equals(name)) {
parallaxHeigh -= 0.01;
parallaxHeigh = Math.max(parallaxHeigh, 0);
mat.setFloat("ParallaxHeight", parallaxHeigh);
}
}
}, "heightUP", "heightDown");
inputManager.addMapping("heightUP", new KeyTrigger(KeyInput.KEY_I));
inputManager.addMapping("heightDown", new KeyTrigger(KeyInput.KEY_K));
inputManager.addListener(new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (isPressed && "toggleSteep".equals(name)) {
steep = !steep;
mat.setBoolean("SteepParallax", steep);
}
}
}, "toggleSteep");
inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE));
}
float parallaxHeigh = 0.05f;
float time = 0;
boolean steep = false;
@Override
public void simpleUpdate(float tpf) {
// time+=tpf;
// lightDir.set(FastMath.sin(time), -1, FastMath.cos(time));
// bsr.setDirection(lightDir);
// dl.setDirection(lightDir);
}
}

@ -0,0 +1,180 @@
/*
* Copyright (c) 2009-2015 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 jme3test.post;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.post.filters.BloomFilter.GlowMode;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.WireFrustum;
import com.jme3.scene.shape.Box;
import com.jme3.util.SkyFactory;
public class TestBloomAlphaThreshold extends SimpleApplication
{
float angle;
Spatial lightMdl;
Spatial teapot;
Geometry frustumMdl;
WireFrustum frustum;
boolean active = true;
FilterPostProcessor fpp;
public static void main(String[] args)
{
TestBloomAlphaThreshold app = new TestBloomAlphaThreshold();
app.start();
}
@Override
public void simpleInitApp()
{
// put the camera in a bad position
cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10));
cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f));
// cam.setFrustumFar(1000);
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setFloat("Shininess", 15f);
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f));
mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f));
mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f));
mat.setColor("GlowColor", ColorRGBA.Green);
Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
matSoil.setFloat("Shininess", 15f);
matSoil.setBoolean("UseMaterialColors", true);
matSoil.setColor("Ambient", ColorRGBA.Gray);
matSoil.setColor("Diffuse", ColorRGBA.Black);
matSoil.setColor("Specular", ColorRGBA.Gray);
teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
teapot.setLocalTranslation(0, 0, 10);
teapot.setMaterial(mat);
teapot.setShadowMode(ShadowMode.CastAndReceive);
teapot.setLocalScale(10.0f);
rootNode.attachChild(teapot);
Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
soil.setMaterial(matSoil);
soil.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(soil);
Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
matBox.setFloat("AlphaDiscardThreshold", 0.5f);
Geometry box = new Geometry("box", new Box(new Vector3f(-3.5f, 10, -2), 2, 2, 2));
box.setMaterial(matBox);
box.setQueueBucket(RenderQueue.Bucket.Translucent);
// box.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(box);
DirectionalLight light = new DirectionalLight();
light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
light.setColor(ColorRGBA.White.mult(1.5f));
rootNode.addLight(light);
// load sky
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
sky.setCullHint(Spatial.CullHint.Never);
rootNode.attachChild(sky);
fpp = new FilterPostProcessor(assetManager);
int numSamples = getContext().getSettings().getSamples();
if (numSamples > 0)
{
fpp.setNumSamples(numSamples);
}
BloomFilter bloom = new BloomFilter(GlowMode.Objects);
bloom.setDownSamplingFactor(2);
bloom.setBlurScale(1.37f);
bloom.setExposurePower(3.30f);
bloom.setExposureCutOff(0.2f);
bloom.setBloomIntensity(2.45f);
BloomUI ui = new BloomUI(inputManager, bloom);
viewPort.addProcessor(fpp);
fpp.addFilter(bloom);
initInputs();
}
private void initInputs()
{
inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
ActionListener acl = new ActionListener()
{
@Override
public void onAction(String name, boolean keyPressed, float tpf)
{
if (name.equals("toggle") && keyPressed)
{
if (active)
{
active = false;
viewPort.removeProcessor(fpp);
}
else
{
active = true;
viewPort.addProcessor(fpp);
}
}
}
};
inputManager.addListener(acl, "toggle");
}
}

@ -0,0 +1,112 @@
/*
* Copyright (c) 2009-2015 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 jme3test.texture.ktx;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.plugins.ktx.KTXLoader;
import com.jme3.ui.Picture;
/**
* test
* @author nehon
*/
public class TestLoadKtx extends SimpleApplication {
public static void main(String[] args) {
TestLoadKtx app = new TestLoadKtx();
//app.setShowSettings(false);
app.start();
}
@Override
public void simpleInitApp() {
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
assetManager.registerLoader(KTXLoader.class, "ktx");
Texture2D t = (Texture2D)assetManager.loadTexture("Textures/ktx/down-reference.ktx");
Picture p = new Picture("bla", false);
p.setTexture(assetManager, t, false);
p.setLocalTranslation(200, 200, 0);
p.setWidth(t.getImage().getWidth());
p.setHeight(t.getImage().getHeight());
guiNode.attachChild(p);
Texture2D t2 = (Texture2D)assetManager.loadTexture("Textures/ktx/up-reference.ktx");
Picture p2 = new Picture("bla", false);
p2.setTexture(assetManager, t2, false);
p2.setLocalTranslation(400, 200, 0);
p2.setWidth(t2.getImage().getWidth());
p2.setHeight(t2.getImage().getHeight());
guiNode.attachChild(p2);
Texture2D t3 = (Texture2D)assetManager.loadTexture("Textures/ktx/rgba-reference.ktx");
Picture p3 = new Picture("bla", false);
p3.setTexture(assetManager, t3, false);
p3.setLocalTranslation(200, 400, 0);
p3.setWidth(t3.getImage().getWidth());
p3.setHeight(t3.getImage().getHeight());
guiNode.attachChild(p3);
Texture2D t4 = (Texture2D)assetManager.loadTexture("Textures/ktx/rgb-amg-reference.ktx");
Picture p4 = new Picture("bla", false);
p4.setTexture(assetManager, t4, false);
p4.setLocalTranslation(400, 400, 0);
p4.setWidth(t4.getImage().getWidth());
p4.setHeight(t4.getImage().getHeight());
guiNode.attachChild(p4);
flyCam.setDragToRotate(true);
}
@Override
public void simpleUpdate(float tpf) {
//TODO: add update code
}
@Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
}

@ -0,0 +1,13 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
Roughness : 0.1
BaseColor : 1.0 0.2 0.2 1.0
Metallic : 0.0
EmissivePower : 0.0
EmissiveIntensity : 0.0
Emissive : 1.0 0.8 0.8 1.0
ParallaxHeight : 0.0
}
AdditionalRenderState {
}
}

@ -0,0 +1,12 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
Metallic : 0.0
Roughness : 0.0
BaseColor : 0.8 0.81960785 0.39607844 1.0
EmissiveIntensity : 5.0
EmissivePower : 2.0
Emissive : 1.0 0.0 0.0 1.0
}
AdditionalRenderState {
}
}

@ -0,0 +1,9 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
BaseColor : 0.6 0.6 0.6 1.0
Metallic : 1.0
Roughness : 0.05
}
AdditionalRenderState {
}
}

@ -0,0 +1,11 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
Roughness : 0.01
NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal.jpg
Metallic : 1.0
BaseColor : 1.0 1.0 1.0 1.0
}
AdditionalRenderState {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

@ -0,0 +1,14 @@
Material Tank : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
MetallicMap : Flip Models/Tank/Tank_Metallic.png
RoughnessMap : Flip Models/Tank/Tank_Roughness.png
NormalMap : Flip Models/Tank/Tank_Normal.png
BaseColorMap : Flip Models/Tank/Tank_Base_Color.png
EmissiveMap : Flip Models/Tank/Tank_Emissive.png
EmissiveIntensity : 2.0
}
AdditionalRenderState {
}
}

@ -0,0 +1,3 @@
#
#Sat Apr 11 15:27:27 CEST 2015
ORIGINAL_PATH=Models/Tank/tank.obj

@ -0,0 +1,9 @@
Material Pong Rock PBR : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
Roughness : 1.0
Metallic : 0.0
BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds
PackedNormalParallax: true
}
}

@ -0,0 +1,8 @@
Material Pong Rock PBR : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
Roughness : 1.0
Metallic : 0.0
BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
ParallaxMap : Repeat Textures/Terrain/BrickWall/BrickWall_height.jpg
}
}

@ -0,0 +1,131 @@
/*
* Copyright (c) 2009-2010 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.gde.core.sceneexplorer.nodes;
import com.jme3.light.LightProbe;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.beans.PropertyEditor;
import java.lang.reflect.InvocationTargetException;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
/**
*
* @author normenhansen
*/
@org.openide.util.lookup.ServiceProvider(service=SceneExplorerNode.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public class JmeLightProbe extends JmeLight{
protected LightProbe lightProbe;
public JmeLightProbe() {
}
public JmeLightProbe(Spatial spatial, LightProbe lightProbe) {
super(spatial, lightProbe);
this.lightProbe = lightProbe;
lookupContents.add(lightProbe);
setName("LightProbe");
}
@Override
protected Sheet createSheet() {
//TODO: multithreading..
Sheet sheet = super.createSheet();
Sheet.Set set = Sheet.createPropertiesSet();
set.setDisplayName("LightProbe");
set.setName(LightProbe.class.getName());
LightProbe obj = lightProbe;
if (obj == null) {
return sheet;
}
set.put(makeProperty(obj, Vector3f.class, "getPosition", "setPosition", "Position"));
set.put(makeEmbedProperty(obj.getBounds(), obj.getBounds().getClass(), float.class, "getRadius", "setRadius", "Radius"));
set.put(createButtonProperty());
sheet.put(set);
return sheet;
}
public LightProbe getLightProbe() {
return lightProbe;
}
@Override
public Class getExplorerObjectClass() {
return LightProbe.class;
}
@Override
public Class getExplorerNodeClass() {
return JmeLightProbe.class;
}
protected void setModified(){
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
fireSave(true);
}
});
}
private Property createButtonProperty() {
return new PropertySupport.ReadWrite<Object>("update", Object.class, "Refresh maps", "Click here to refresh environment maps ") {
JmeLightProbeButtonProperty pe;
@Override
public Object getValue() throws IllegalAccessException, InvocationTargetException {
return "";
}
@Override
public PropertyEditor getPropertyEditor() {
if (pe == null) {
pe = new JmeLightProbeButtonProperty(JmeLightProbe.this, (Node)getSpatial());
pe.attachEnv(pe.env);
}
return pe;
}
@Override
public void setValue(Object t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
}
};
}
}

@ -0,0 +1,99 @@
package com.jme3.gde.core.sceneexplorer.nodes;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.gde.core.scene.SceneApplication;
import com.jme3.gde.core.scene.controller.SceneToolController;
import com.jme3.gde.core.util.ButtonInplaceEditor;
import com.jme3.light.LightProbe;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyEditorSupport;
import java.util.concurrent.Callable;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.explorer.propertysheet.ExPropertyEditor;
import org.openide.explorer.propertysheet.InplaceEditor;
import org.openide.explorer.propertysheet.PropertyEnv;
/**
*
* @author Nehon
*/
public class JmeLightProbeButtonProperty extends PropertyEditorSupport implements ExPropertyEditor, InplaceEditor.Factory {
JmeLightProbe probe;
Node node;
public JmeLightProbeButtonProperty(JmeLightProbe pe, Node node) {
super();
this.node = node;
this.probe = pe;
}
PropertyEnv env;
@Override
public void attachEnv(PropertyEnv env) {
this.env = env;
env.registerInplaceEditorFactory(this);
}
private ButtonInplaceEditor ed = null;
@Override
public InplaceEditor getInplaceEditor() {
if (ed == null) {
ed = new ButtonInplaceEditor("Refresh");
ed.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SceneApplication.getApplication().enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
EnvironmentCamera envCam = SceneApplication.getApplication().getStateManager().getState(EnvironmentCamera.class);
SceneToolController toolController = SceneApplication.getApplication().getStateManager().getState(SceneToolController.class);
if (toolController != null) {
envCam.setPosition(toolController.getCursorLocation());
} else {
envCam.setPosition(new Vector3f(0, 0, 0));
}
LightProbeFactory.updateProbe(probe.getLightProbe(), envCam, node, new JmeLightProbeProgressHandler());
probe.setModified();
return null;
}
});
}
});
}
return ed;
}
@Override
public boolean isPaintable() {
return true;
}
@Override
public void paintValue(Graphics gfx, Rectangle box) {
if (ed == null) {
getInplaceEditor();
}
ed.setSize(box.width, box.height);
ed.doLayout();
Graphics g = gfx.create(box.x, box.y, box.width, box.height);
ed.setOpaque(false);
ed.paint(g);
g.dispose();
probe.refresh(false);
}
}

@ -0,0 +1,42 @@
package com.jme3.gde.core.sceneexplorer.nodes;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.light.LightProbe;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
/**
*
* @author Nehon
*/
public class JmeLightProbeProgressHandler extends JobProgressAdapter<LightProbe> {
int lastProgress;
ProgressHandle handle = ProgressHandleFactory.createHandle("Generating environment maps");
@Override
public void start() {
handle.start(100);
}
@Override
public void progress(double value) {
lastProgress = (int) (value * 100);
handle.progress(lastProgress);
}
@Override
public void step(String message) {
handle.progress(message, lastProgress);
}
@Override
public void done(LightProbe t) {
handle.finish();
}
}

@ -0,0 +1,57 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.gde.scenecomposer.gizmo.light;
import com.jme3.light.Light;
import com.jme3.math.Vector3f;
import com.jme3.scene.control.BillboardControl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.openide.util.Exceptions;
/**
* Updates the marker's position whenever the light has moved. It is also a
* BillboardControl, so this marker always faces the camera
*/
public class LightGizmoControl extends BillboardControl {
private final Vector3f lastPos = new Vector3f();
private Vector3f lightPos;
LightGizmoControl(Light light) {
super();
try {
Method getPosition = light.getClass().getMethod("getPosition");
lightPos = (Vector3f) getPosition.invoke(light);
} catch (NoSuchMethodException ex) {
//light type doesn't have a get position method, silancing the exception
} catch (SecurityException ex) {
Exceptions.printStackTrace(ex);
} catch (IllegalAccessException ex) {
Exceptions.printStackTrace(ex);
} catch (IllegalArgumentException ex) {
Exceptions.printStackTrace(ex);
} catch (InvocationTargetException ex) {
Exceptions.printStackTrace(ex);
}
}
@Override
protected void controlUpdate(float f) {
super.controlUpdate(f);
if (!lightPos.equals(lastPos)) {
if (getSpatial() != null) {
lastPos.set(lightPos);
getSpatial().setLocalTranslation(lastPos);
}
}
}
}

@ -0,0 +1,68 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.gde.scenecomposer.gizmo.light;
import com.jme3.asset.AssetManager;
import com.jme3.environment.util.BoundingSphereDebug;
import com.jme3.gde.scenecomposer.gizmo.light.shape.ProbeRadiusShape;
import com.jme3.light.Light;
import com.jme3.light.LightProbe;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.BillboardControl;
import com.jme3.scene.shape.Quad;
import com.jme3.scene.shape.Sphere;
import com.jme3.texture.Texture;
/**
* Handles the creation of the appropriate light gizmo according to the light type.
* @author Nehon
*/
public class LightGizmoFactory {
public static Spatial createGizmo(AssetManager assetManager, Light light){
switch (light.getType()){
case Probe:
return createLightProbeGizmo(assetManager, light);
default:
return createDefaultGizmo(assetManager, light);
}
}
private static Spatial createDefaultGizmo(AssetManager assetManager, Light light){
Quad q = new Quad(0.5f, 0.5f);
Geometry g = new Geometry(light.getName(), q);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
Texture tex = assetManager.loadTexture("com/jme3/gde/scenecomposer/lightbulb32.png");
mat.setTexture("ColorMap", tex);
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
g.setMaterial(mat);
g.addControl(new LightGizmoControl(light));
g.setQueueBucket(RenderQueue.Bucket.Transparent);
return g;
}
private static Spatial createLightProbeGizmo(AssetManager assetManager, Light light){
Node debugNode = new Node("Environment debug Node");
Sphere s = new Sphere(16, 16, 0.5f);
Geometry debugGeom = new Geometry(light.getName(), s);
Material debugMaterial = new Material(assetManager, "Common/MatDefs/Misc/reflect.j3md");
debugGeom.setMaterial(debugMaterial);
Spatial debugBounds = ProbeRadiusShape.createShape(assetManager);
debugNode.attachChild(debugGeom);
debugNode.attachChild(debugBounds);
debugNode.addControl(new LightProbeGizmoControl((LightProbe)light));
return debugNode;
}
}

@ -0,0 +1,59 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.gde.scenecomposer.gizmo.light;
import com.jme3.bounding.BoundingSphere;
import com.jme3.environment.util.LightsDebugState;
import com.jme3.light.LightProbe;
import com.jme3.material.Material;
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.control.AbstractControl;
/**
* Updates the marker's position whenever the light probe has moved.
* Also update the gizmo radius according to the probe radius.
*/
public class LightProbeGizmoControl extends AbstractControl{
private final Vector3f lastPos = new Vector3f();
private final LightProbe lightProbe;
LightProbeGizmoControl(LightProbe light) {
lightProbe = light;
}
@Override
protected void controlUpdate(float f) {
if (!lightProbe.getPosition().equals(lastPos)) {
if (getSpatial() != null) {
lastPos.set(lightProbe.getPosition());
getSpatial().setLocalTranslation(lastPos);
}
}
Geometry probeGeom = (Geometry) ((Node) getSpatial()).getChild(0);
Material m = probeGeom.getMaterial();
if (lightProbe.isReady()) {
m.setTexture("CubeMap", lightProbe.getPrefilteredEnvMap());
}
Geometry probeRadius = (Geometry) ((Node) getSpatial()).getChild(1);
probeRadius.setLocalScale(((BoundingSphere) lightProbe.getBounds()).getRadius());
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
}

@ -0,0 +1,23 @@
ShaderNodeDefinitions{
ShaderNodeDefinition Dashed {
Type: Fragment
Shader GLSL100: com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag
Documentation{
Renders dashed lines
@input vec2 texCoord The texture coordinates
@input float size the size of the dashes
@input vec4 inColor the color of the fragment so far
@outColor vec4 color the output color
}
Input {
vec2 texCoord
vec4 inColor
float size
}
Output {
vec4 outColor
}
}
}

@ -0,0 +1,11 @@
void main(){
//@input vec2 texCoord The texture coordinates
//@input float size the size of the dashes
//@output vec4 color the output color
//insert glsl code here
outColor = inColor;
outColor.a = step(1.0 - size, texCoord.x);
}

@ -0,0 +1,37 @@
MaterialDef Simple {
MaterialParameters {
Float DashSize
}
Technique {
WorldParameters {
WorldViewProjectionMatrix
}
VertexShaderNodes {
ShaderNode CommonVert {
Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
InputMappings {
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
modelPosition = Global.position.xyz
texCoord1 = Attr.inTexCoord
vertColor = Attr.inColor
}
OutputMappings {
Global.position = projPosition
}
}
}
FragmentShaderNodes {
ShaderNode Dashed {
Definition : Dashed : com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn
InputMappings {
texCoord = CommonVert.texCoord1
inColor = CommonVert.vertColor
size = MatParam.DashSize
}
OutputMappings {
Global.color = outColor
}
}
}
}
}

@ -0,0 +1,168 @@
/*
* Copyright (c) 2009-2015 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.gde.scenecomposer.gizmo.light.shape;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.BillboardControl;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
*
* A debuging shape for a BoundingSphere
* Consists of 3 axis aligned circles.
*
* @author nehon
*/
public class ProbeRadiusShape extends Mesh {
protected int vertCount;
protected int triCount;
protected int radialSamples = 256;
protected boolean useEvenSlices;
protected boolean interior;
/**
* the distance from the center point each point falls on
*/
public float radius;
public float getRadius() {
return radius;
}
public ProbeRadiusShape() {
setGeometryData();
setIndexData();
}
/**
* builds the vertices based on the radius
*/
private void setGeometryData() {
setMode(Mode.Lines);
FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1));
FloatBuffer colBuf = BufferUtils.createFloatBuffer((radialSamples + 1) * 4);
FloatBuffer texBuf = BufferUtils.createVector2Buffer(radialSamples + 1);
setBuffer(Type.Position, 3, posBuf);
setBuffer(Type.Color, 4, colBuf);
setBuffer(Type.TexCoord, 2, texBuf);
// generate geometry
float fInvRS = 1.0f / radialSamples;
// Generate points on the unit circle to be used in computing the mesh
// points on a sphere slice.
float[] afSin = new float[(radialSamples + 1)];
float[] afCos = new float[(radialSamples + 1)];
for (int iR = 0; iR < radialSamples; iR++) {
float fAngle = FastMath.TWO_PI * fInvRS * iR;
afCos[iR] = FastMath.cos(fAngle);
afSin[iR] = FastMath.sin(fAngle);
}
afSin[radialSamples] = afSin[0];
afCos[radialSamples] = afCos[0];
for (int iR = 0; iR <= radialSamples; iR++) {
posBuf.put(afCos[iR])
.put(afSin[iR])
.put(0);
colBuf.put(ColorRGBA.Orange.r)
.put(ColorRGBA.Orange.g)
.put(ColorRGBA.Orange.b)
.put(ColorRGBA.Orange.a);
texBuf.put(iR % 2f)
.put(iR % 2f);
}
updateBound();
setStatic();
}
/**
* sets the indices for rendering the sphere.
*/
private void setIndexData() {
// allocate connectivity
int nbSegments = (radialSamples);// * 3;
ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments);
setBuffer(Type.Index, 2, idxBuf);
int idx = 0;
int segDone = 0;
while (segDone < nbSegments) {
idxBuf.put((short) idx);
idxBuf.put((short) (idx + 1));
idx++;
segDone++;
}
}
/**
* Convenience factory method that creates a debuging bounding sphere geometry
* @param assetManager the assetManager
* @return the bounding sphere debug geometry.
*/
public static Geometry createShape(AssetManager assetManager) {
ProbeRadiusShape b = new ProbeRadiusShape();
Geometry geom = new Geometry("BoundingDebug", b);
Material mat = new Material(assetManager, "com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md");
mat.getAdditionalRenderState().setWireframe(true);
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
mat.getAdditionalRenderState().setDepthWrite(false);
mat.getAdditionalRenderState().setDepthTest(false);
mat.setFloat("DashSize", 0.5f);
geom.setQueueBucket(RenderQueue.Bucket.Transparent);
geom.addControl(new BillboardControl());
geom.setMaterial(mat);
return geom;
}
}
Loading…
Cancel
Save