Compare commits

...

4 Commits

  1. 104
      jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java
  2. 97
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory2.java
  3. 19
      jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java
  4. 171
      jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java
  5. 19
      jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java
  6. 17
      jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java
  7. 182
      jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java
  8. 104
      jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java
  9. 10
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  10. 4
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  11. 149
      jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java
  12. 101
      jme3-core/src/main/resources/Common/IBL/IBLKernels.frag
  13. 37
      jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md
  14. 29
      jme3-core/src/main/resources/Common/IBL/IBLKernels.vert
  15. 66
      jme3-core/src/main/resources/Common/IBL/Math.glsllib
  16. 15
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
  17. 40
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java

@ -0,0 +1,104 @@
package com.jme3.environment;
import java.util.function.Function;
import java.util.function.Predicate;
import com.jme3.asset.AssetManager;
import com.jme3.environment.baker.IBLGLEnvBakerLight;
import com.jme3.light.LightProbe;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.texture.Image.Format;
/**
* SmartLightProbe
*/
public class EnvironmentProbeControl extends LightProbe implements Control {
RenderManager renderManager;
AssetManager assetManager;
int envMapSize;
Spatial spatial;
boolean BAKE_NEEDED=true;
Function<Geometry,Boolean> filter=(s)->{
return s.getUserData("tags.env")!=null;
};
public static void tag(Spatial s){
if(s instanceof Node){
Node n=(Node)s;
for(Spatial sx:n.getChildren()){
tag(sx);
}
}else if(s instanceof Geometry){
s.setUserData("tags.env", true);
}
}
public EnvironmentProbeControl(RenderManager rm,AssetManager am, int size){
renderManager=rm;
assetManager=am;
envMapSize=size;
}
@Override
public Control cloneForSpatial(Spatial spatial) {
return null;
}
@Override
public void setSpatial(Spatial spatial) {
spatial.addLight(this);
this.spatial=spatial;
}
@Override
public void update(float tpf) {
}
@Override
public void render(RenderManager rm, ViewPort vp) {
if(BAKE_NEEDED){
BAKE_NEEDED=false;
rebakeNow();
}
}
public void rebake(){
BAKE_NEEDED=true;
}
void rebakeNow() {
System.out.println("BAKE");
IBLGLEnvBakerLight baker = new IBLGLEnvBakerLight(renderManager, assetManager, Format.RGB16F, Format.Depth,
envMapSize, envMapSize);
baker.bakeEnvironment(spatial, Vector3f.ZERO, 0.001f, 1000f,filter);
baker.bakeSpecularIBL();
baker.bakeSphericalHarmonicsCoefficients();
// probe.setPosition(Vector3f.ZERO);
setPrefilteredMap(baker.getSpecularIBL());
setNbMipMaps(getPrefilteredEnvMap().getImage().getMipMapSizes().length);
setShCoeffs(baker.getSphericalHarmonicsCoefficients());
setPosition(Vector3f.ZERO);
setReady(true);
baker.clean();
}
}

@ -0,0 +1,97 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.environment;
import com.jme3.asset.AssetManager;
import com.jme3.environment.baker.IBLGLEnvBakerLight;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.Image.Format;
public class LightProbeFactory2 {
public static LightProbe makeProbe(RenderManager rm,
AssetManager am, int size,Vector3f pos, float frustumNear,float frustumFar,Spatial scene) {
IBLGLEnvBakerLight baker=new IBLGLEnvBakerLight(rm,
am, Format.RGB16F, Format.Depth,
size, size);
baker.bakeEnvironment(scene,pos, frustumNear,frustumFar,null);
baker.bakeSpecularIBL();
baker.bakeSphericalHarmonicsCoefficients();
LightProbe probe = new LightProbe();
probe.setPosition(pos);
probe.setPrefilteredMap(baker.getSpecularIBL());
probe.setNbMipMaps(probe.getPrefilteredEnvMap().getImage().getMipMapSizes().length);
probe.setShCoeffs(baker.getSphericalHarmonicsCoefficients());
probe.setReady(true);
baker.clean();
return probe;
}
/**
* For debuging porpose only
* Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
*
* @param manager the asset manager
* @return a debug node
*/
public static Node getDebugGui(AssetManager manager, LightProbe probe) {
if (!probe.isReady()) {
throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
}
Node debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager);
debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0);
return debugNode;
}
}

@ -0,0 +1,19 @@
package com.jme3.environment.baker;
import java.util.function.Function;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
/**
* And environment baker. It bakes the environment. ( ͡° ͜ʖ ͡°)
*
* @author Riccardo Balbo
*/
public interface EnvBaker {
public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar,Function<Geometry,Boolean> filter);
public TextureCubeMap getEnvMap();
public void clean();
}

@ -0,0 +1,171 @@
package com.jme3.environment.baker;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.function.Function;
import com.jme3.asset.AssetManager;
import com.jme3.environment.baker.EnvBaker;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
/**
* Render the environment into a cubemap
*
* @author Riccardo Balbo
*/
public abstract class GenericEnvBaker implements EnvBaker{
protected static Vector3f[] axisX=new Vector3f[6];
protected static Vector3f[] axisY=new Vector3f[6];
protected static Vector3f[] axisZ=new Vector3f[6];
static{
//PositiveX axis(left, up, direction)
axisX[0]=Vector3f.UNIT_Z.mult(1.0F);
axisY[0]=Vector3f.UNIT_Y.mult(-1.0F);
axisZ[0]=Vector3f.UNIT_X.mult(1.0F);
//NegativeX
axisX[1]=Vector3f.UNIT_Z.mult(-1.0F);
axisY[1]=Vector3f.UNIT_Y.mult(-1.0F);
axisZ[1]=Vector3f.UNIT_X.mult(-1.0F);
//PositiveY
axisX[2]=Vector3f.UNIT_X.mult(-1.0F);
axisY[2]=Vector3f.UNIT_Z.mult(1.0F);
axisZ[2]=Vector3f.UNIT_Y.mult(1.0F);
//NegativeY
axisX[3]=Vector3f.UNIT_X.mult(-1.0F);
axisY[3]=Vector3f.UNIT_Z.mult(-1.0F);
axisZ[3]=Vector3f.UNIT_Y.mult(-1.0F);
//PositiveZ
axisX[4]=Vector3f.UNIT_X.mult(-1.0F);
axisY[4]=Vector3f.UNIT_Y.mult(-1.0F);
axisZ[4]=Vector3f.UNIT_Z;
//NegativeZ
axisX[5]=Vector3f.UNIT_X.mult(1.0F);
axisY[5]=Vector3f.UNIT_Y.mult(-1.0F);
axisZ[5]=Vector3f.UNIT_Z.mult(-1.0F);
}
protected TextureCubeMap env;
protected Format depthFormat;
protected final RenderManager renderManager;
protected final AssetManager assetManager;
protected final Camera cam;
protected final boolean copyToRam;
public GenericEnvBaker(
RenderManager rm,
AssetManager am,
Format colorFormat,
Format depthFormat,
int env_size,
boolean copyToRam
){
this.copyToRam=copyToRam;
this.depthFormat=depthFormat;
renderManager=rm;
assetManager=am;
cam=new Camera(128,128);
env=new TextureCubeMap(env_size,env_size,colorFormat);
env.setMagFilter(MagFilter.Bilinear);
env.setMinFilter(MinFilter.BilinearNoMipMaps);
env.setWrap(WrapMode.EdgeClamp);
env.getImage().setColorSpace(ColorSpace.Linear);
}
public TextureCubeMap getEnvMap(){
return env;
}
Camera getCam(int id,int w,int h,Vector3f position,float frustumNear,float frustumFar){
cam.resize(w,h,false);
cam.setLocation(position);
cam.setFrustumPerspective(90.0F,1F,frustumNear,frustumFar);
cam.setRotation(new Quaternion().fromAxes(axisX[id],axisY[id],axisZ[id]));
return cam;
}
@Override
public void clean(){
env.getImage().dispose();
System.gc();
System.gc();
}
@Override
public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar,Function<Geometry,Boolean> filter) {
FrameBuffer envbaker=new FrameBuffer(env.getImage().getWidth(),env.getImage().getHeight(),1);
envbaker.setDepthTarget(FrameBuffer.newTarget(depthFormat));
envbaker.setSrgb(false);
for(int i=0;i<6;i++) envbaker.addColorTarget(FrameBuffer.newTarget(env).face(TextureCubeMap.Face.values()[i]));
for(int i=0;i<6;i++){
envbaker.setTargetIndex(i);
ViewPort viewPort=new ViewPort("EnvBaker",getCam(i,envbaker.getWidth(),envbaker.getHeight(),position,frustumNear,frustumFar));
viewPort.setClearFlags(true,true,true);
viewPort.setBackgroundColor(ColorRGBA.Pink);
viewPort.setOutputFrameBuffer(envbaker);
viewPort.clearScenes();
viewPort.attachScene(scene);
scene.updateLogicalState(0);
scene.updateModelBound();
scene.updateGeometricState();
Function<Geometry,Boolean> ofilter= renderManager.getRenderFilter();
renderManager.setRenderFilter(filter);
renderManager.renderViewPort(viewPort,0.16f);
renderManager.setRenderFilter(ofilter);
if(copyToRam){
ByteBuffer face=BufferUtils.createByteBuffer(
(
env.getImage().getWidth()*env.getImage().getHeight()*(
env.getImage().getFormat().getBitsPerPixel()/8
)
)
);
renderManager.getRenderer().readFrameBufferWithFormat(envbaker, face,env.getImage().getFormat());
face.rewind();
env.getImage().setData(i,face);
}
}
env.getImage().clearUpdateNeeded();
envbaker.dispose();
}
}

@ -0,0 +1,19 @@
package com.jme3.environment.baker;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
/**
* An environment baker, but this one is for Imaged Base Lighting
*
* @author Riccardo Balbo
*/
public interface IBLEnvBaker extends EnvBaker{
public Texture2D genBRTF() ;
public void bakeIrradiance();
public void bakeSpecularIBL() ;
public TextureCubeMap getSpecularIBL();
public TextureCubeMap getIrradiance();
}

@ -0,0 +1,17 @@
package com.jme3.environment.baker;
import com.jme3.math.Vector3f;
import com.jme3.texture.TextureCubeMap;
/**
* An environment baker for IBL, that uses spherical harmonics for irradiance.
*
* @author Riccardo Balbo
*/
public interface IBLEnvBakerLight extends EnvBaker{
public void bakeSpecularIBL();
public void bakeSphericalHarmonicsCoefficients();
public TextureCubeMap getSpecularIBL();
public Vector3f[] getSphericalHarmonicsCoefficients();
}

@ -0,0 +1,182 @@
package com.jme3.environment.baker;
import com.jme3.asset.AssetManager;
import com.jme3.environment.baker.IBLEnvBaker;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace;
import com.jme3.ui.Picture;
/**
* An env baker for IBL that runs on the GPU
*
* @author Riccardo Balbo
*/
public class IBLGLEnvBaker extends GenericEnvBaker implements IBLEnvBaker{
protected Texture2D brtf;
protected TextureCubeMap irradiance;
protected TextureCubeMap specular;
public IBLGLEnvBaker(RenderManager rm,AssetManager am,
Format format,
Format depthFormat,
int env_size,int specular_size,
int irradiance_size,
int brtf_size
){
super(rm,am,format,depthFormat,env_size,false);
irradiance=new TextureCubeMap(irradiance_size,irradiance_size,format);
irradiance.setMagFilter(MagFilter.Bilinear);
irradiance.setMinFilter(MinFilter.BilinearNoMipMaps);
irradiance.setWrap(WrapMode.EdgeClamp);
irradiance.getImage().setColorSpace(ColorSpace.Linear);
specular=new TextureCubeMap(specular_size,specular_size,format);
specular.setMagFilter(MagFilter.Bilinear);
specular.setMinFilter(MinFilter.BilinearNoMipMaps);
specular.setWrap(WrapMode.EdgeClamp);
specular.getImage().setColorSpace(ColorSpace.Linear);
int nbMipMaps=(int)(Math.log(specular_size)/Math.log(2)+1);
if(nbMipMaps>6)nbMipMaps=6;
int[] sizes=new int[nbMipMaps];
for(int i=0;i<nbMipMaps;i++){
int size=(int)FastMath.pow(2,nbMipMaps-1-i);
sizes[i]=size*size*(specular.getImage().getFormat().getBitsPerPixel()/8);
}
specular.getImage().setMipMapSizes(sizes);
brtf=new Texture2D(brtf_size,brtf_size,format);
brtf.setMagFilter(MagFilter.Bilinear);
brtf.setMinFilter(MinFilter.BilinearNoMipMaps);
brtf.setWrap(WrapMode.EdgeClamp);
brtf.getImage().setColorSpace(ColorSpace.Linear);
}
public TextureCubeMap getSpecularIBL(){
return specular;
}
public TextureCubeMap getIrradiance(){
return irradiance;
}
@Override
public void bakeSpecularIBL() {
Box boxm=new Box(1,1,1);
Geometry screen=new Geometry("BakeBox",boxm);
Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
mat.setBoolean("UseSpecularIBL",true);
mat.setTexture("EnvMap",env);
screen.setMaterial(mat);
for(int mip=0;mip<specular.getImage().getMipMapSizes().length;mip++){
int mipWidth=(int)(specular.getImage().getWidth()*FastMath.pow(0.5f,mip));
int mipHeight=(int)(specular.getImage().getHeight()*FastMath.pow(0.5f,mip));
FrameBuffer specularbaker=new FrameBuffer(mipWidth,mipHeight,1);
specularbaker.setSrgb(false);
for(int i=0;i<6;i++)specularbaker.addColorTarget( FrameBuffer.newTarget(specular).level(mip).face(i) );
float roughness=(float)mip/(float)(specular.getImage().getMipMapSizes().length-1);
mat.setFloat("Roughness",roughness);
for(int i=0;i<6;i++){
specularbaker.setTargetIndex(i);
mat.setInt("FaceId",i);
screen.updateLogicalState(0);
screen.updateGeometricState();
renderManager.setCamera(getCam(i,specularbaker.getWidth(),specularbaker.getHeight(),Vector3f.ZERO,1,1000),false);
renderManager.getRenderer().setFrameBuffer(specularbaker);
renderManager.renderGeometry(screen);
}
specularbaker.dispose();
}
specular.setMinFilter(MinFilter.Trilinear);
}
@Override
public Texture2D genBRTF() {
Picture screen=new Picture("BakeScreen",true);
screen.setWidth(1);
screen.setHeight(1);
FrameBuffer brtfbaker=new FrameBuffer(brtf.getImage().getWidth(),brtf.getImage().getHeight(),1);
brtfbaker.setSrgb(false);
brtfbaker.addColorTarget(FrameBuffer.newTarget(brtf));
Camera envcam=getCam(0,brtf.getImage().getWidth(),brtf.getImage().getHeight(),Vector3f.ZERO,1,1000);
Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
mat.setBoolean("UseBRDF",true);
screen.setMaterial(mat);
renderManager.getRenderer().setFrameBuffer(brtfbaker);
renderManager.setCamera(envcam,false);
screen.updateLogicalState(0);
screen.updateGeometricState();
renderManager.renderGeometry(screen);
brtfbaker.dispose();
return brtf;
}
@Override
public void bakeIrradiance() {
Box boxm=new Box(1,1,1);
Geometry screen=new Geometry("BakeBox",boxm);
FrameBuffer irradiancebaker=new FrameBuffer(irradiance.getImage().getWidth(),irradiance.getImage().getHeight(),1);
irradiancebaker.setSrgb(false);
for(int i=0;i<6;i++) irradiancebaker.addColorTarget(FrameBuffer.newTarget(irradiance).face(TextureCubeMap.Face.values()[i]));
Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
mat.setBoolean("UseIrradiance",true);
mat.setTexture("EnvMap",env);
screen.setMaterial(mat);
for(int i=0;i<6;i++){
irradiancebaker.setTargetIndex(i);
mat.setInt("FaceId",i);
screen.updateLogicalState(0);
screen.updateGeometricState();
renderManager.setCamera(
getCam(i,irradiancebaker.getWidth(),irradiancebaker.getHeight(),Vector3f.ZERO,1,1000)
,false);
renderManager.getRenderer().setFrameBuffer(irradiancebaker);
renderManager.renderGeometry(screen);
}
irradiancebaker.dispose();
}
}

@ -0,0 +1,104 @@
package com.jme3.environment.baker;
import com.jme3.asset.AssetManager;
import com.jme3.environment.baker.IBLEnvBakerLight;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.image.ColorSpace;
/**
* An env baker for IBL that runs on the GPU
*
* @author Riccardo Balbo
*/
public class IBLGLEnvBakerLight extends GenericEnvBaker implements IBLEnvBakerLight{
protected TextureCubeMap specular;
protected Vector3f[] shCoef;
public IBLGLEnvBakerLight(RenderManager rm,AssetManager am,
Format format,
Format depthFormat,
int env_size,
int specular_size
){
super(rm,am,format,depthFormat,env_size,true);
specular=new TextureCubeMap(specular_size,specular_size,format);
specular.setMagFilter(MagFilter.Bilinear);
specular.setMinFilter(MinFilter.BilinearNoMipMaps);
specular.setWrap(WrapMode.EdgeClamp);
specular.getImage().setColorSpace(ColorSpace.Linear);
int nbMipMaps=(int)(Math.log(specular_size)/Math.log(2)+1);
if(nbMipMaps>6)nbMipMaps=6;
int[] sizes=new int[nbMipMaps];
for(int i=0;i<nbMipMaps;i++){
int size=(int)FastMath.pow(2,nbMipMaps-1-i);
sizes[i]=size*size*(specular.getImage().getFormat().getBitsPerPixel()/8);
}
specular.getImage().setMipMapSizes(sizes);
}
@Override
public void bakeSpecularIBL() {
Box boxm=new Box(1,1,1);
Geometry screen=new Geometry("BakeBox",boxm);
Material mat=new Material(assetManager,"Common/IBL/IBLKernels.j3md");
mat.setBoolean("UseSpecularIBL",true);
mat.setTexture("EnvMap",env);
screen.setMaterial(mat);
for(int mip=0;mip<specular.getImage().getMipMapSizes().length;mip++){
int mipWidth=(int)(specular.getImage().getWidth()*FastMath.pow(0.5f,mip));
int mipHeight=(int)(specular.getImage().getHeight()*FastMath.pow(0.5f,mip));
FrameBuffer specularbaker=new FrameBuffer(mipWidth,mipHeight,1);
specularbaker.setSrgb(false);
for(int i=0;i<6;i++)specularbaker.addColorTarget( FrameBuffer.newTarget(specular).level(mip).face(i) );
float roughness=(float)mip/(float)(specular.getImage().getMipMapSizes().length-1);
mat.setFloat("Roughness",roughness);
for(int i=0;i<6;i++){
specularbaker.setTargetIndex(i);
mat.setInt("FaceId",i);
screen.updateLogicalState(0);
screen.updateGeometricState();
renderManager.setCamera(getCam(i,specularbaker.getWidth(),specularbaker.getHeight(),Vector3f.ZERO,1,1000),false);
renderManager.getRenderer().setFrameBuffer(specularbaker);
renderManager.renderGeometry(screen);
}
specularbaker.dispose();
}
specular.setMinFilter(MinFilter.Trilinear);
}
@Override
public TextureCubeMap getSpecularIBL() {
return specular;
}
@Override
public void bakeSphericalHarmonicsCoefficients() {
shCoef=EnvMapUtils.getSphericalHarmonicsCoefficents(getEnvMap());
}
@Override
public Vector3f[] getSphericalHarmonicsCoefficients(){
return shCoef;
}
}

@ -57,6 +57,7 @@ import com.jme3.util.SafeArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.logging.Logger;
/**
@ -90,7 +91,15 @@ public class RenderManager {
private LightFilter lightFilter = new DefaultLightFilter();
private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass;
private int singlePassLightBatchSize = 1;
private Function<Geometry,Boolean> renderFilter;
public void setRenderFilter(Function<Geometry,Boolean> filter){
renderFilter=filter;
}
public Function<Geometry,Boolean> getRenderFilter(){
return renderFilter;
}
/**
* Create a high-level rendering interface over the
@ -557,6 +566,7 @@ public class RenderManager {
* @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager)
*/
public void renderGeometry(Geometry geom) {
if(renderFilter!=null&&!renderFilter.apply(geom))return;
if (geom.isIgnoreTransform()) {
setWorldMatrix(Matrix4f.IDENTITY);
} else {

@ -1729,12 +1729,12 @@ public final class GLRenderer implements Renderer {
convertAttachmentSlot(rb.getSlot()),
convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()),
image.getId(),
0);
rb.getLevel());
} else {
glfbo.glFramebufferTextureLayerEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
image.getId(),
0,
rb.getLevel(),
rb.getLayer());
}
}

@ -90,7 +90,7 @@ public class FrameBuffer extends NativeObject {
* buffer that will be rendered to. <code>RenderBuffer</code>s
* are attached to an attachment slot on a <code>FrameBuffer</code>.
*/
public class RenderBuffer {
public static class RenderBuffer {
Texture tex;
Image.Format format;
@ -98,6 +98,7 @@ public class FrameBuffer extends NativeObject {
int slot = SLOT_UNDEF;
int face = -1;
int layer = -1;
int level = 0;
/**
* @return The image format of the render buffer.
@ -121,6 +122,10 @@ public class FrameBuffer extends NativeObject {
return id;
}
public int getLevel() {
return this.level;
}
/**
* Do not use.
*/
@ -167,6 +172,60 @@ public class FrameBuffer extends NativeObject {
}
}
public static class FrameBufferTextureTarget extends RenderBuffer {
private FrameBufferTextureTarget(){}
void setTexture(Texture tx){
this.tex=tx;
this.format=tx.getImage().getFormat();
}
void setFormat(Format f){
this.format=f;
}
public FrameBufferTextureTarget layer(int i){
this.layer=i;
return this;
}
public FrameBufferTextureTarget level(int i){
this.level=i;
return this;
}
public FrameBufferTextureTarget face(TextureCubeMap.Face f){
return face(f.ordinal());
}
public FrameBufferTextureTarget face(int f){
this.face=f;
return this;
}
}
public static class FrameBufferBufferTarget extends RenderBuffer {
private FrameBufferBufferTarget(){}
void setFormat(Format f){
this.format=f;
}
}
public static FrameBufferTextureTarget newTarget(Texture tx){
FrameBufferTextureTarget t=new FrameBufferTextureTarget();
t.setTexture(tx);
return t;
}
public static FrameBufferBufferTarget newTarget(Format format){
FrameBufferBufferTarget t=new FrameBufferBufferTarget();
t.setFormat(format);
return t;
}
/**
* <p>
* Creates a new FrameBuffer with the given width, height, and number
@ -207,12 +266,62 @@ public class FrameBuffer extends NativeObject {
*/
}
public void addColorTarget(FrameBufferBufferTarget colorBuf){
colorBuf.slot=colorBufs.size();
colorBufs.add(colorBuf);
}
public void addColorTarget(FrameBufferTextureTarget colorBuf){
// checkSetTexture(colorBuf.getTexture(), false); // TODO: this won't work for levels.
colorBuf.slot=colorBufs.size();
colorBufs.add(colorBuf);
}
public void setDepthTarget(FrameBufferBufferTarget depthBuf){
if (!depthBuf.getFormat().isDepthFormat())
throw new IllegalArgumentException("Depth buffer format must be depth.");
this.depthBuf = depthBuf;
this.depthBuf.slot = this.depthBuf.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH;
}
public void setDepthTarget(FrameBufferTextureTarget depthBuf){
checkSetTexture(depthBuf.getTexture(), true);
this.depthBuf = depthBuf;
this.depthBuf.slot = depthBuf.getTexture().getImage().getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH;
}
public int getNumColorTargets(){
return colorBufs.size();
}
public RenderBuffer getColorTarget(int index){
return colorBufs.get(index);
}
public RenderBuffer getColorTarget() {
if (colorBufs.isEmpty())
return null;
if (colorBufIndex<0 || colorBufIndex>=colorBufs.size()) {
return colorBufs.get(0);
}
return colorBufs.get(colorBufIndex);
}
public RenderBuffer getDepthTarget() {
return depthBuf;
}
/**
* Enables the use of a depth buffer for this <code>FrameBuffer</code>.
*
* @param format The format to use for the depth buffer.
* @throws IllegalArgumentException If <code>format</code> is not a depth format.
* @deprecated Use setDepthTarget
*/
@Deprecated
public void setDepthBuffer(Image.Format format){
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -230,7 +339,9 @@ public class FrameBuffer extends NativeObject {
*
* @param format The format to use for the color buffer.
* @throws IllegalArgumentException If <code>format</code> is not a color format.
* @deprecated Use addColorTarget
*/
@Deprecated
public void setColorBuffer(Image.Format format){
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -323,7 +434,9 @@ public class FrameBuffer extends NativeObject {
* only target.
*
* @param tex The color texture to set.
* @deprecated Use addColorTarget
*/
@Deprecated
public void setColorTexture(Texture2D tex){
clearColorTargets();
addColorTexture(tex);
@ -336,7 +449,9 @@ public class FrameBuffer extends NativeObject {
* only target.
*
* @param tex The color texture array to set.
* @deprecated Use addColorTarget
*/
@Deprecated
public void setColorTexture(TextureArray tex, int layer){
clearColorTargets();
addColorTexture(tex, layer);
@ -350,7 +465,9 @@ public class FrameBuffer extends NativeObject {
*
* @param tex The cube-map texture to set.
* @param face The face of the cube-map to render to.
* @deprecated Use addColorTarget
*/
@Deprecated
public void setColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) {
clearColorTargets();
addColorTexture(tex, face);
@ -372,7 +489,9 @@ public class FrameBuffer extends NativeObject {
*
* @param format the format of the color buffer
* @see #addColorTexture(com.jme3.texture.Texture2D)
* @deprecated Use addColorTarget
*/
@Deprecated
public void addColorBuffer(Image.Format format){
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -387,6 +506,8 @@ public class FrameBuffer extends NativeObject {
colorBufs.add(colorBuf);
}
/**
* Add a color texture to use for this framebuffer.
* If MRT is enabled, then each subsequently added texture can be
@ -396,7 +517,10 @@ public class FrameBuffer extends NativeObject {
*
* @param tex The texture to add.
* @see #addColorBuffer(com.jme3.texture.Image.Format)
* @deprecated Use addColorTarget
*
*/
@Deprecated
public void addColorTexture(Texture2D tex) {
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -420,7 +544,9 @@ public class FrameBuffer extends NativeObject {
* is rendered to by the shader.
*
* @param tex The texture array to add.
* @deprecated Use addColorTarget
*/
@Deprecated
public void addColorTexture(TextureArray tex, int layer) {
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -446,7 +572,9 @@ public class FrameBuffer extends NativeObject {
*
* @param tex The cube-map texture to add.
* @param face The face of the cube-map to render to.
* @deprecated Use addColorTarget
*/
@Deprecated
public void addColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) {
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -467,7 +595,9 @@ public class FrameBuffer extends NativeObject {
* Set the depth texture to use for this framebuffer.
*
* @param tex The color texture to set.
* @deprecated Use setDepthTarget
*/
@Deprecated
public void setDepthTexture(Texture2D tex){
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -480,6 +610,15 @@ public class FrameBuffer extends NativeObject {
depthBuf.tex = tex;
depthBuf.format = img.getFormat();
}
/**
*
* @param tex
* @param layer
* @deprecated Use setDepthTarget
*/
@Deprecated
public void setDepthTexture(TextureArray tex, int layer){
if (id != -1)
throw new UnsupportedOperationException("FrameBuffer already initialized.");
@ -496,7 +635,9 @@ public class FrameBuffer extends NativeObject {
/**
* @return The number of color buffers attached to this texture.
* @deprecated Use getNumColorTargets
*/
@Deprecated
public int getNumColorBuffers(){
return colorBufs.size();
}
@ -504,7 +645,9 @@ public class FrameBuffer extends NativeObject {
/**
* @param index
* @return The color buffer at the given index.
* @deprecated Use getColorTarget(int)
*/
@Deprecated
public RenderBuffer getColorBuffer(int index){
return colorBufs.get(index);
}
@ -513,7 +656,9 @@ public class FrameBuffer extends NativeObject {
* @return The color buffer with the index set by {@link #setTargetIndex(int)}, or null
* if no color buffers are attached.
* If MRT is disabled, the first color buffer is returned.
* @deprecated Use getColorTarget()
*/
@Deprecated
public RenderBuffer getColorBuffer() {
if (colorBufs.isEmpty())
return null;
@ -526,7 +671,9 @@ public class FrameBuffer extends NativeObject {
/**
* @return The depth buffer attached to this FrameBuffer, or null
* if no depth buffer is attached
* @deprecated Use getDepthTarget()
*/
@Deprecated
public RenderBuffer getDepthBuffer() {
return depthBuf;
}

@ -0,0 +1,101 @@
/**
* This code is based on the following articles:
* https://learnopengl.com/PBR/IBL/Diffuse-irradiance
* https://learnopengl.com/PBR/IBL/Specular-IBL
* - Riccardo Balbo
*/
#import "Common/IBL/Math.glsllib"
out vec4 outFragColor;
in vec2 TexCoords;
in vec3 LocalPos;
uniform samplerCube m_EnvMap;
uniform float m_Roughness;
uniform int m_FaceId;
void brdfKernel(){
float NdotV=TexCoords.x;
float m_Roughness=TexCoords.y;
vec3 V;
V.x = sqrt(1.0 - NdotV*NdotV);
V.y = 0.0;
V.z = NdotV;
float A = 0.0;
float B = 0.0;
vec3 N = vec3(0.0, 0.0, 1.0);
const uint SAMPLE_COUNT = 1024u;
for(uint i = 0u; i < SAMPLE_COUNT; i++){
vec2 Xi = Hammersley(i, SAMPLE_COUNT);
vec3 H = ImportanceSampleGGX(Xi, N, m_Roughness);
vec3 L = normalize(2.0 * dot(V, H) * H - V);
float NdotL = max(L.z, 0.0);
float NdotH = max(H.z, 0.0);
float VdotH = max(dot(V, H), 0.0);
if(NdotL > 0.0){
float G = GeometrySmith(N, V, L, m_Roughness);
float G_Vis = (G * VdotH) / (NdotH * NdotV);
float Fc = pow(1.0 - VdotH, 5.0);
A += (1.0 - Fc) * G_Vis;
B += Fc * G_Vis;
}
}
A /= float(SAMPLE_COUNT);
B /= float(SAMPLE_COUNT);
outFragColor.rg=vec2(A, B);
outFragColor.ba=vec2(0);
}
void irradianceKernel(){
// the sample direction equals the hemisphere's orientation
vec3 N = normalize(LocalPos);
vec3 irradiance = vec3(0.0);
vec3 up = vec3(0.0, 1.0, 0.0);
vec3 right = cross(up, N);
up = cross(N, right);
float sampleDelta = 0.025;
float nrSamples = 0.0;
for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta){
for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta){
// spherical to cartesian (in tangent space)
vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
// tangent space to world
vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
irradiance += texture(m_EnvMap, sampleVec).rgb * cos(theta) * sin(theta);
nrSamples++;
}
}
irradiance = PI * irradiance * (1.0 / float(nrSamples));
outFragColor = vec4(irradiance, 1.0);
}
void prefilteredEnvKernel(){
vec3 N = normalize(LocalPos);
vec3 R = N;
vec3 V = R;
const uint SAMPLE_COUNT = 1024u;
float totalWeight = 0.0;
vec3 prefilteredColor = vec3(0.0);
for(uint i = 0u; i < SAMPLE_COUNT; ++i) {
vec2 Xi = Hammersley(i, SAMPLE_COUNT);
vec3 H = ImportanceSampleGGX(Xi, N, m_Roughness);
vec3 L = normalize(2.0 * dot(V, H) * H - V);
float NdotL = max(dot(N, L), 0.0);
if(NdotL > 0.0) {
prefilteredColor += texture(m_EnvMap, L).rgb * NdotL;
totalWeight += NdotL;
}
}
prefilteredColor = prefilteredColor / totalWeight;
outFragColor = vec4(prefilteredColor, 1.0);
}
void main(){
#if defined(SIBL)
prefilteredEnvKernel();
#elif defined(IRRADIANCE)
irradianceKernel();
#else
brdfKernel();
#endif
}

@ -0,0 +1,37 @@
MaterialDef IBLKernels {
MaterialParameters {
TextureCubeMap EnvMap -LINEAR
Float Roughness
Int FaceId : 0
Boolean UseBRDF
Boolean UseIrradiance
Boolean UseSpecularIBL
}
Technique {
VertexShader GLSL150: Common/IBL/IBLKernels.vert
FragmentShader GLSL150: Common/IBL/IBLKernels.frag
WorldParameters {
WorldMatrix
ViewMatrix
ProjectionMatrix
}
RenderState {
DepthWrite Off
DepthTest Off
DepthFunc Equal
FaceCull Off
}
Defines {
BRDF:UseBRDF
IRRADIANCE: UseIrradiance
SIBL: UseSpecularIBL
}
}
}

@ -0,0 +1,29 @@
/**
* This code is based on the following articles:
* https://learnopengl.com/PBR/IBL/Diffuse-irradiance
* https://learnopengl.com/PBR/IBL/Specular-IBL
* - Riccardo Balbo
*/
in vec3 inPosition;
in vec2 inTexCoord;
in vec3 inNormal;
out vec2 TexCoords;
out vec3 LocalPos;
uniform mat4 g_ViewMatrix;
uniform mat4 g_WorldMatrix;
uniform mat4 g_ProjectionMatrix;
void main() {
LocalPos = inPosition.xyz;
TexCoords = inTexCoord.xy;
#ifdef BRDF
vec2 pos = inPosition.xy * 2.0 - 1.0;
gl_Position = vec4(pos, 0.0, 1.0);
#else
mat4 rotView = mat4(mat3(g_ViewMatrix)); // remove translation from the view matrix
vec4 clipPos = g_ProjectionMatrix * rotView * vec4(LocalPos, 1.0);
gl_Position = clipPos.xyww;
#endif
}

@ -0,0 +1,66 @@
/**
* This code is based on the following articles:
* https://learnopengl.com/PBR/IBL/Diffuse-irradiance
* https://learnopengl.com/PBR/IBL/Specular-IBL
* - Riccardo Balbo
*/
const float PI = 3.14159265359;
float RadicalInverse_VdC(uint bits) {
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}
vec2 Hammersley(uint i, uint N){
return vec2(float(i)/float(N), RadicalInverse_VdC(i));
}
vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness){
float a = roughness*roughness;
float phi = 2.0 * PI * Xi.x;
float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));
float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
// from spherical coordinates to cartesian coordinates
vec3 H;
H.x = cos(phi) * sinTheta;
H.y = sin(phi) * sinTheta;
H.z = cosTheta;
// from tangent-space vector to world-space sample vector
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec3 tangent = normalize(cross(up, N));
vec3 bitangent = cross(N, tangent);
vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return normalize(sampleVec);
}
float GeometrySchlickGGX(float NdotV, float roughness){
float a = roughness;
float k = (a * a) / 2.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness){
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}

@ -34,6 +34,7 @@ package jme3test.light.pbr;
import com.jme3.app.SimpleApplication;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.LightProbeFactory2;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.util.LightsDebugState;
@ -59,7 +60,8 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
* @author nehon
*/
public class TestPBRLighting extends SimpleApplication {
private static final boolean USE_ACCELERATED_BAKING=true;
private static final int RESOLUTION=256;
public static void main(String[] args) {
TestPBRLighting app = new TestPBRLighting();
app.start();
@ -107,7 +109,7 @@ public class TestPBRLighting extends SimpleApplication {
model.setMaterial(pbrMat);
final EnvironmentCamera envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0));
final EnvironmentCamera envCam = new EnvironmentCamera(RESOLUTION, new Vector3f(0, 3f, 0));
stateManager.attach(envCam);
// EnvironmentManager envManager = new EnvironmentManager();
@ -195,18 +197,21 @@ public class TestPBRLighting extends SimpleApplication {
if (frame == 2) {
modelNode.removeFromParent();
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
LightProbe probe;
if(USE_ACCELERATED_BAKING){
probe= LightProbeFactory2.makeProbe(renderManager, assetManager, RESOLUTION, Vector3f.ZERO, 1f, 1000f, rootNode);
}else{
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);
}
});
}
((SphereProbeArea) probe.getArea()).setRadius(100);
rootNode.addLight(probe);
//getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
}
if (frame > 10 && modelNode.getParent() == null) {
rootNode.attachChild(modelNode);

@ -0,0 +1,40 @@
package jme3test.light.pbr;
import com.jme3.app.SimpleApplication;
import com.jme3.environment.EnvironmentProbeControl;
import com.jme3.material.Material;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.util.SkyFactory;
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
/**
* TestPBRSimple
*/
public class TestPBRSimple extends SimpleApplication{
public static void main(String[] args) {
new TestPBRSimple().start();
}
@Override
public void simpleInitApp() {
Geometry model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o");
MikktspaceTangentGenerator.generate(model);
Material pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
model.setMaterial(pbrMat);
rootNode.attachChild(model);
EnvironmentProbeControl envProbe=new EnvironmentProbeControl(renderManager,assetManager,256);
rootNode.addControl(envProbe);
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
rootNode.attachChild(sky);
EnvironmentProbeControl.tag(sky);
}
}
Loading…
Cancel
Save