Accelerated env baker that runs on the GPU.

accellbaker
Riccardo Balbo 5 years ago
parent f1ab3be46c
commit b14bb34176
  1. 98
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory2.java
  2. 16
      jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java
  3. 165
      jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java
  4. 19
      jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java
  5. 17
      jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java
  6. 182
      jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java
  7. 104
      jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java
  8. 101
      jme3-core/src/main/resources/Common/IBL/IBLKernels.frag
  9. 37
      jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md
  10. 29
      jme3-core/src/main/resources/Common/IBL/IBLKernels.vert
  11. 66
      jme3-core/src/main/resources/Common/IBL/Math.glsllib
  12. 27
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@ -0,0 +1,98 @@
/*
* 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);
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,16 @@
package com.jme3.environment.baker;
import com.jme3.math.Vector3f;
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);
public TextureCubeMap getEnvMap();
public void clean();
}

@ -0,0 +1,165 @@
package com.jme3.environment.baker;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
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.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) {
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();
renderManager.renderViewPort(viewPort,0.16f);
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;
}
}

@ -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.app.SimpleApplication;
import com.jme3.environment.EnvironmentCamera; import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory; import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.LightProbeFactory2;
import com.jme3.environment.generation.JobProgressAdapter; import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.environment.util.EnvMapUtils; import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.util.LightsDebugState; import com.jme3.environment.util.LightsDebugState;
@ -59,7 +60,8 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
* @author nehon * @author nehon
*/ */
public class TestPBRLighting extends SimpleApplication { 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) { public static void main(String[] args) {
TestPBRLighting app = new TestPBRLighting(); TestPBRLighting app = new TestPBRLighting();
app.start(); app.start();
@ -107,7 +109,7 @@ public class TestPBRLighting extends SimpleApplication {
model.setMaterial(pbrMat); 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); stateManager.attach(envCam);
// EnvironmentManager envManager = new EnvironmentManager(); // EnvironmentManager envManager = new EnvironmentManager();
@ -195,18 +197,21 @@ public class TestPBRLighting extends SimpleApplication {
if (frame == 2) { if (frame == 2) {
modelNode.removeFromParent(); modelNode.removeFromParent();
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() { LightProbe probe;
if(USE_ACCELERATED_BAKING){
@Override probe= LightProbeFactory2.makeProbe(renderManager, assetManager, RESOLUTION, Vector3f.ZERO, 1f, 1000f, rootNode);
public void done(LightProbe result) { }else{
System.err.println("Done rendering env maps"); probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); @Override
} public void done(LightProbe result) {
}); System.err.println("Done rendering env maps");
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
}
});
}
((SphereProbeArea) probe.getArea()).setRadius(100); ((SphereProbeArea) probe.getArea()).setRadius(100);
rootNode.addLight(probe); rootNode.addLight(probe);
//getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe); //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
} }
if (frame > 10 && modelNode.getParent() == null) { if (frame > 10 && modelNode.getParent() == null) {
rootNode.attachChild(modelNode); rootNode.attachChild(modelNode);

Loading…
Cancel
Save