diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java index 1fba5d51d..aff4dd0b9 100644 --- a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java @@ -48,6 +48,7 @@ import com.jme3.texture.Texture2D; import com.jme3.texture.TextureCubeMap; import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; +import com.jme3.util.MipMapGenerator; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -72,6 +73,8 @@ public class EnvironmentCamera extends BaseAppState { protected Image.Format imageFormat = Image.Format.RGB16F; + public TextureCubeMap debugEnv; + //Axis for cameras static { //PositiveX axis(left, up, direction) @@ -188,10 +191,11 @@ public class EnvironmentCamera extends BaseAppState { 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); + MipMapGenerator.generateMipMaps(images[i]); } final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat); - + debugEnv = map; job.callback.done(map); map.getImage().dispose(); jobs.remove(0); diff --git a/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java index b41bbd479..b76629a83 100644 --- a/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java +++ b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java @@ -31,13 +31,14 @@ */ package com.jme3.environment; +import com.jme3.app.Application; import com.jme3.environment.generation.*; -import com.jme3.light.LightProbe; import com.jme3.environment.util.EnvMapUtils; -import com.jme3.app.Application; +import com.jme3.light.LightProbe; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.texture.TextureCubeMap; + import java.util.concurrent.ScheduledThreadPoolExecutor; /** @@ -106,10 +107,11 @@ public class LightProbeFactory { * @param envCam the EnvironmentCamera * @param scene the Scene + * @param genType Fast or HighQuality. Fast may be ok for many types of environment, but you may need high quality when an environment map has very high lighting values. * @param listener the listener of the genration progress. * @return the created LightProbe */ - public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener listener) { + public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final EnvMapUtils.GenerationType genType, final JobProgressListener listener) { final LightProbe probe = new LightProbe(); probe.setPosition(envCam.getPosition()); probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat())); @@ -117,33 +119,37 @@ public class LightProbeFactory { @Override public void done(TextureCubeMap map) { - generatePbrMaps(map, probe, envCam.getApplication(), listener); + generatePbrMaps(map, probe, envCam.getApplication(), genType, listener); } }); return probe; } - - /** - * Updates a LightProbe with the giver EnvironmentCamera in the given scene. - * + + public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener listener) { + return makeProbe(envCam, scene, EnvMapUtils.GenerationType.Fast, listener); + } + + /** + * Updates a LightProbe with the given 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}. - * + *

+ * The JobProgressListener will be notified of the progress of the generation. + * Note that you can also use a {@link JobProgressAdapter}. + * + * @param probe the Light probe to update + * @param envCam the EnvironmentCamera + * @param scene the Scene + * @param genType Fast or HighQuality. Fast may be ok for many types of environment, but you may need high quality when an environment map has very high lighting values. + * @param listener the listener of the genration progress. + * @return the created LightProbe * @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 listener) { + public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final EnvMapUtils.GenerationType genType, final JobProgressListener listener) { envCam.setPosition(probe.getPosition()); @@ -159,23 +165,27 @@ public class LightProbeFactory { @Override public void done(TextureCubeMap map) { - generatePbrMaps(map, probe, envCam.getApplication(), listener); + generatePbrMaps(map, probe, envCam.getApplication(), genType, listener); } }); return probe; } + public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener listener) { + return updateProbe(probe, envCam, scene, EnvMapUtils.GenerationType.Fast, listener); + } + /** * Internally called to generate the maps. * This method will spawn 7 thread (one for the Irradiance spherical harmonics generator, 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 listener) { + private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, EnvMapUtils.GenerationType genType, final JobProgressListener listener) { IrradianceSphericalHarmonicsGenerator irrShGenerator; PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6]; @@ -189,7 +199,7 @@ public class LightProbeFactory { 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.None, probe.getPrefilteredEnvMap()); + pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.None, genType, probe.getPrefilteredEnvMap()); jobState.executor.execute(pemGenerators[i]); } } diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java index 8233b11e5..8fe9cc8aa 100644 --- a/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java +++ b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java @@ -31,27 +31,19 @@ */ package com.jme3.environment.generation; +import com.jme3.app.Application; import com.jme3.environment.util.CubeMapWrapper; import com.jme3.environment.util.EnvMapUtils; -import com.jme3.app.Application; import com.jme3.math.*; - -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.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; +import static com.jme3.environment.util.EnvMapUtils.*; +import static com.jme3.math.FastMath.abs; +import static com.jme3.math.FastMath.sqrt; + /** * Generates one face of the prefiltered environnement map for PBR. This job can * be lauched from a separate thread. @@ -69,6 +61,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress { private int targetMapSize; private EnvMapUtils.FixSeamsMethod fixSeamsMethod; + private EnvMapUtils.GenerationType genType; private TextureCubeMap sourceMap; private TextureCubeMap store; private final Application app; @@ -107,11 +100,12 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress { * {@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) { + public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, EnvMapUtils.GenerationType genType, TextureCubeMap store) { this.sourceMap = sourceMap; this.targetMapSize = targetMapSize; this.fixSeamsMethod = fixSeamsMethod; this.store = store; + this.genType = genType; init(); } @@ -162,68 +156,105 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress { * @return The irradiance cube map for the given coefficients */ private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) { - TextureCubeMap pem = store; + try { + TextureCubeMap pem = store; - int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1); + int nbMipMap = store.getImage().getMipMapSizes().length; - setEnd(nbMipMap); + setEnd(nbMipMap); + if (!sourceEnvMap.getImage().hasMipmaps() || sourceEnvMap.getImage().getMipMapSizes().length < nbMipMap) { + throw new IllegalArgumentException("The input cube map must have at least " + nbMipMap + "mip maps"); + } - CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap); - CubeMapWrapper targetWrapper = new CubeMapWrapper(pem); + 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); + Vector3f texelVect = new Vector3f(); + Vector3f color = new Vector3f(); + ColorRGBA outColor = new ColorRGBA(); + int targetMipMapSize = targetMapSize; + for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) { + float roughness = getRoughnessFromMip(mipLevel, nbMipMap); + int nbSamples = getSampleFromMip(mipLevel, nbMipMap); - 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); - prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color); + 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); + prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, mipLevel, 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); + outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y, 0.0001f), Math.max(color.z, 0.0001f), 1); + targetWrapper.setPixel(x, y, face, mipLevel, outColor); + } } + targetMipMapSize /= 2; + progress(); } - progress(); - } - return pem; + return pem; + } catch (Exception e) { + e.printStackTrace(); + throw e; + } } - private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) { + private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, int mipLevel, Vector3f store) { Vector3f prefilteredColor = store; float totalWeight = 0.0f; + int nbRotations = 1; + if (genType == GenerationType.HighQuality) { + nbRotations = numSamples == 1 ? 1 : 18; + } + + float rad = 2f * FastMath.PI / (float) nbRotations; + // offset rotation to avoid sampling pattern + float gi = (float) (FastMath.abs(N.z + N.x) * 256.0); + float offset = rad * (FastMath.cos((gi * 0.5f) % (2f * FastMath.PI)) * 0.5f + 0.5f); + // a = roughness² and a2 = a² float a2 = roughness * roughness; a2 *= a2; + + //Computing tangent frame + Vector3f upVector = Vector3f.UNIT_X; + if (abs(N.z) < 0.999) { + upVector = Vector3f.UNIT_Y; + } + Vector3f tangentX = tmp1.set(upVector).crossLocal(N).normalizeLocal(); + Vector3f tangentY = tmp2.set(N).crossLocal(tangentX); + + // https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/ + // in local space view == normal == 0,0,1 + Vector3f V = new Vector3f(0, 0, 1); + + Vector3f lWorld = new Vector3f(); for (int i = 0; i < numSamples; i++) { Xi = getHammersleyPoint(i, numSamples, Xi); - H = importanceSampleGGX(Xi, a2, N, H); - + H = importanceSampleGGX(Xi, a2, 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; + float VoH = H.z; + Vector3f L = H.multLocal(VoH * 2f).subtractLocal(V); + float NoL = L.z; + + float computedMipLevel = mipLevel; + if (mipLevel != 0) { + computedMipLevel = computeMipLevel(roughness, numSamples, this.targetMapSize, VoH); + } + + toWorld(L, N, tangentX, tangentY, lWorld); + totalWeight += samplePixel(envMapReader, lWorld, NoL, computedMipLevel, prefilteredColor); + + for (int j = 1; j < nbRotations; j++) { + rotateDirection(offset + j * rad, L, lWorld); + L.set(lWorld); + toWorld(L, N, tangentX, tangentY, lWorld); + totalWeight += samplePixel(envMapReader, lWorld, NoL, computedMipLevel, prefilteredColor); } + } if (totalWeight > 0) { prefilteredColor.divideLocal(totalWeight); @@ -232,7 +263,78 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress { return prefilteredColor; } - public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) { + private float samplePixel(CubeMapWrapper envMapReader, Vector3f lWorld, float NoL, float computedMipLevel, Vector3f store) { + + if (NoL <= 0) { + return 0; + } + envMapReader.getPixel(lWorld, computedMipLevel, c); + store.setX(store.x + c.r * NoL); + store.setY(store.y + c.g * NoL); + store.setZ(store.z + c.b * NoL); + + return NoL; + } + + private void toWorld(Vector3f L, Vector3f N, Vector3f tangentX, Vector3f tangentY, Vector3f store) { + store.set(tangentX).multLocal(L.x); + tmp.set(tangentY).multLocal(L.y); + store.addLocal(tmp); + tmp.set(N).multLocal(L.z); + store.addLocal(tmp); + } + + private float computeMipLevel(float roughness, int numSamples, float size, float voH) { + // H[2] is NoH in local space + // adds 1.e-5 to avoid ggx / 0.0 + float NoH = voH + 1E-5f; + + // Probability Distribution Function + float Pdf = ggx(NoH, roughness) * NoH / (4.0f * voH); + + // Solid angle represented by this sample + float omegaS = 1.0f / (numSamples * Pdf); + + // Solid angle covered by 1 pixel with 6 faces that are EnvMapSize X EnvMapSize + float omegaP = 4.0f * FastMath.PI / (6.0f * size * size); + + // Original paper suggest biasing the mip to improve the results + float mipBias = 1.0f; // I tested that the result is better with bias 1 + double maxLod = Math.log(size) / Math.log(2f); + double log2 = Math.log(omegaS / omegaP) / Math.log(2); + return Math.min(Math.max(0.5f * (float) log2 + mipBias, 0.0f), (float) maxLod); + } + + + private float ggx(float NoH, float alpha) { + // use GGX / Trowbridge-Reitz, same as Disney and Unreal 4 + // cf http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p3 + float tmp = alpha / (NoH * NoH * (alpha * alpha - 1.0f) + 1.0f); + return tmp * tmp * (1f / FastMath.PI); + } + + private Vector3f rotateDirection(float angle, Vector3f l, Vector3f store) { + float s, c, t; + + s = FastMath.sin(angle); + c = FastMath.cos(angle); + t = 1.f - c; + + store.x = l.x * c + l.y * s; + store.y = -l.x * s + l.y * c; + store.z = l.z * (t + c); + return store; + } + + /** + * Computes GGX half vector in local space + * + * @param xi + * @param a2 + * @param store + * @return + */ + public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -243,22 +345,9 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress { 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); + store.x = sinThetaCosPhi; + store.y = sinThetaSinPhi; + store.z = cosTheta; return store; } diff --git a/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java index cdf66a6d1..7226f9610 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java @@ -31,17 +31,15 @@ */ 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.math.*; 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; +import static com.jme3.math.FastMath.pow; + /** * Wraps a Cube map and allows to read from or write pixels into it. * @@ -57,6 +55,8 @@ public class CubeMapWrapper { private final Vector2f uvs = new Vector2f(); private final Image image; + private final ColorRGBA tmpColor = new ColorRGBA(); + /** * Creates a CubeMapWrapper for the given cube map * Note that the cube map must be initialized, and the mipmaps sizes should @@ -105,7 +105,7 @@ public class CubeMapWrapper { * @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) { + public ColorRGBA getPixel(Vector3f vector, float mipLevel, ColorRGBA store) { if (mipMapRaster == null) { throw new IllegalArgumentException("This cube map has no mip maps"); } @@ -113,10 +113,26 @@ public class CubeMapWrapper { store = new ColorRGBA(); } - int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch); + int lowerMipLevel = (int) mipLevel; + int higherMipLevel = (int) FastMath.ceil(mipLevel); + float ratio = mipLevel - lowerMipLevel; + + int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[lowerMipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch); mipMapRaster.setSlice(face); - mipMapRaster.setMipLevel(mipLevel); - return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store); + mipMapRaster.setMipLevel(lowerMipLevel); + mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store); + + face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[higherMipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch); + mipMapRaster.setSlice(face); + mipMapRaster.setMipLevel(higherMipLevel); + mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, tmpColor); + + store.r = FastMath.interpolateLinear(ratio, store.r, tmpColor.r); + store.g = FastMath.interpolateLinear(ratio, store.g, tmpColor.g); + store.b = FastMath.interpolateLinear(ratio, store.b, tmpColor.b); + store.a = FastMath.interpolateLinear(ratio, store.a, tmpColor.a); + + return store; } /** diff --git a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java index 4598ec0d6..91b65655e 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java @@ -37,18 +37,15 @@ import com.jme3.math.*; 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.*; import com.jme3.texture.image.ColorSpace; import com.jme3.ui.Picture; import com.jme3.util.BufferUtils; +import com.jme3.util.TempVars; + import java.nio.ByteBuffer; -import java.util.ArrayList; -import static com.jme3.math.FastMath.*; -import com.jme3.util.TempVars; +import static com.jme3.math.FastMath.*; /** * @@ -88,6 +85,11 @@ public class EnvMapUtils { None } + public static enum GenerationType { + Fast, + HighQuality + } + /** * Creates a cube map from 6 images * @@ -117,6 +119,8 @@ public class EnvMapUtils { cubeImage.addData(backImg.getData(0)); cubeImage.addData(frontImg.getData(0)); + cubeImage.setMipMapSizes(rightImg.getMipMapSizes()); + TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); cubeMap.setAnisotropicFilter(0); cubeMap.setMagFilter(Texture.MagFilter.Bilinear); @@ -148,6 +152,8 @@ public class EnvMapUtils { cubeImage.addData(d.duplicate()); } + cubeImage.setMipMapSizes(srcImg.getMipMapSizes()); + TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter()); cubeMap.setMagFilter(sourceMap.getMagFilter()); @@ -730,7 +736,7 @@ public class EnvMapUtils { 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); + int nbMipMap = Math.min(6, (int) (Math.log(size) / Math.log(2))); CubeMapWrapper targetWrapper = new CubeMapWrapper(pem); targetWrapper.initMipMaps(nbMipMap); return pem; diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java b/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java index ac1989d5c..d00ab7e22 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java @@ -110,7 +110,7 @@ class ByteAlignedImageCodec extends ImageCodec { } public void readComponents(ByteBuffer buf, int x, int y, int width, int offset, int[] components, byte[] tmp) { - readPixelRaw(buf, (x + y * width + offset) * bpp + offset, bpp, tmp); + readPixelRaw(buf, (x + y * width ) * bpp + offset, bpp, tmp); components[0] = readComponent(tmp, ap, az); components[1] = readComponent(tmp, rp, rz); components[2] = readComponent(tmp, gp, gz); diff --git a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java index 3ac6a7ecd..7ea5b23f7 100644 --- a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java @@ -34,8 +34,8 @@ package com.jme3.util; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; import com.jme3.texture.image.ImageRaster; + import java.nio.ByteBuffer; import java.util.ArrayList; @@ -58,8 +58,8 @@ public class MipMapGenerator { float xRatio = ((float)(input.getWidth() - 1)) / output.getWidth(); float yRatio = ((float)(input.getHeight() - 1)) / output.getHeight(); - - ColorRGBA outputColor = new ColorRGBA(); + + ColorRGBA outputColor = new ColorRGBA(0, 0, 0, 0); ColorRGBA bottomLeft = new ColorRGBA(); ColorRGBA bottomRight = new ColorRGBA(); ColorRGBA topLeft = new ColorRGBA(); @@ -72,29 +72,21 @@ public class MipMapGenerator { int x2 = (int)x2f; int y2 = (int)y2f; - - float xDiff = x2f - x2; - float yDiff = y2f - y2; - + input.getPixel(x2, y2, bottomLeft); input.getPixel(x2 + 1, y2, bottomRight); input.getPixel(x2, y2 + 1, topLeft); input.getPixel(x2 + 1, y2 + 1, topRight); - - bottomLeft.multLocal( (1f - xDiff) * (1f - yDiff) ); - bottomRight.multLocal( (xDiff) * (1f - yDiff) ); - topLeft.multLocal( (1f - xDiff) * (yDiff) ); - topRight.multLocal( (xDiff) * (yDiff) ); - + outputColor.set(bottomLeft).addLocal(bottomRight) .addLocal(topLeft).addLocal(topRight); - + outputColor.multLocal(1f / 4f); output.setPixel(x, y, outputColor); } } return outputImage; } - + public static Image resizeToPowerOf2(Image original){ int potWidth = FastMath.nearestPowerOfTwo(original.getWidth()); int potHeight = FastMath.nearestPowerOfTwo(original.getHeight()); diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java index 17f3efa55..a88e12bb0 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java @@ -11,12 +11,8 @@ 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.math.*; +import com.jme3.scene.*; import com.jme3.ui.Picture; import com.jme3.util.SkyFactory; @@ -39,11 +35,13 @@ public class RefEnv extends SimpleApplication { @Override public void simpleInitApp() { - cam.setLocation(new Vector3f(-17.713732f, 1.8661976f, 17.156784f)); - cam.setRotation(new Quaternion(0.021403445f, 0.9428821f, -0.06178002f, 0.32664734f)); + cam.setLocation(new Vector3f(-17.95047f, 4.917353f, -17.970531f)); + cam.setRotation(new Quaternion(0.11724457f, 0.29356146f, -0.03630452f, 0.94802815f)); +// cam.setLocation(new Vector3f(14.790441f, 7.164179f, 19.720007f)); +// cam.setRotation(new Quaternion(-0.038261678f, 0.9578362f, -0.15233073f, -0.24058504f)); flyCam.setDragToRotate(true); flyCam.setMoveSpeed(5); - Spatial sc = assetManager.loadModel("Models/gltf/ref/scene.gltf"); + Spatial sc = assetManager.loadModel("Scenes/PBR/ref/scene.gltf"); rootNode.attachChild(sc); Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); rootNode.attachChild(sky); @@ -68,7 +66,7 @@ public class RefEnv extends SimpleApplication { public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("tex") && isPressed) { if (tex == null) { - return; + tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(stateManager.getState(EnvironmentCamera.class).debugEnv, assetManager); } if (tex.getParent() == null) { guiNode.attachChild(tex); @@ -120,13 +118,12 @@ public class RefEnv extends SimpleApplication { frame++; if (frame == 2) { - final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() { + final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, EnvMapUtils.GenerationType.Fast, new JobProgressAdapter() { @Override public void done(LightProbe result) { System.err.println("Done rendering env maps"); tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); - // guiNode.attachChild(tex); rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic); } }); diff --git a/jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.bin b/jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.bin new file mode 100644 index 000000000..fb6b6831c Binary files /dev/null and b/jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.bin differ diff --git a/jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.gltf b/jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.gltf new file mode 100644 index 000000000..d986072b0 --- /dev/null +++ b/jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.gltf @@ -0,0 +1,2466 @@ +{ + "accessors": [ + { + "bufferView": 1, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 5784, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 11568, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 17352, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 11520, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 23136, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 28920, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 23040, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 34704, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 40488, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 34560, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 46272, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 52056, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 46080, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 57840, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 63624, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 57600, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 69408, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 75192, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 69120, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 80976, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 86760, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 80640, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 92544, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 98328, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 92160, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 104112, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 109896, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 103680, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 115680, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 121464, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 115200, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 127248, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 133032, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 126720, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 138816, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 144600, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 138240, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 150384, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 156168, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 149760, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 161952, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 167736, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 161280, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 173520, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 179304, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 172800, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 185088, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 190872, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 184320, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 196656, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 202440, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 195840, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 208224, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 214008, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 207360, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 219792, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 225576, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 218880, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 231360, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 237144, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 230400, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 242928, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 248712, + "componentType": 5126, + "count": 482, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3" + }, + { + "bufferView": 0, + "byteOffset": 241920, + "componentType": 5125, + "count": 2880, + "max": [ + 481 + ], + "min": [ + 0 + ], + "type": "SCALAR" + } + ], + "asset": { + "generator": "Sketchfab (OSG glTF plugin)", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 253440, + "byteOffset": 0, + "name": "ScalarBufferView", + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 254496, + "byteOffset": 253440, + "byteStride": 12, + "name": "Vec4BufferView", + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 507936, + "uri": "scene.bin" + } + ], + "materials": [ + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.022", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 1 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.021", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.90000000000000002 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.020", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 1 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.019", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.90000000000000002 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.010", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.011", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.20000000000000001 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.012", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.20000000000000001 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.013", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.29999999999999999 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.014", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.40000000000000002 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.018", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.80000000000000004 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.017", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.69999999999999996 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.016", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.59999999999999998 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.015", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0, + 0, + 0, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.5 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.006", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.5 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.007", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.59999999999999998 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.008", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.69999999999999996 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.009", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.80000000000000004 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.005", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.40000000000000002 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.004", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.29999999999999999 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.003", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.20000000000000001 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.002", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.10000000000000001 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "name": "Material.001", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.80000001190000003, + 0.80000001190000003, + 0.80000001190000003, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0 + } + } + ], + "meshes": [ + { + "name": "GeodeSphere", + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 0 + }, + "indices": 2, + "material": 21, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.001", + "primitives": [ + { + "attributes": { + "NORMAL": 4, + "POSITION": 3 + }, + "indices": 5, + "material": 20, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.002", + "primitives": [ + { + "attributes": { + "NORMAL": 7, + "POSITION": 6 + }, + "indices": 8, + "material": 19, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.003", + "primitives": [ + { + "attributes": { + "NORMAL": 10, + "POSITION": 9 + }, + "indices": 11, + "material": 18, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.004", + "primitives": [ + { + "attributes": { + "NORMAL": 13, + "POSITION": 12 + }, + "indices": 14, + "material": 17, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.005", + "primitives": [ + { + "attributes": { + "NORMAL": 16, + "POSITION": 15 + }, + "indices": 17, + "material": 16, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.006", + "primitives": [ + { + "attributes": { + "NORMAL": 19, + "POSITION": 18 + }, + "indices": 20, + "material": 15, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.007", + "primitives": [ + { + "attributes": { + "NORMAL": 22, + "POSITION": 21 + }, + "indices": 23, + "material": 14, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.008", + "primitives": [ + { + "attributes": { + "NORMAL": 25, + "POSITION": 24 + }, + "indices": 26, + "material": 13, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.009", + "primitives": [ + { + "attributes": { + "NORMAL": 28, + "POSITION": 27 + }, + "indices": 29, + "material": 12, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.010", + "primitives": [ + { + "attributes": { + "NORMAL": 31, + "POSITION": 30 + }, + "indices": 32, + "material": 11, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.011", + "primitives": [ + { + "attributes": { + "NORMAL": 34, + "POSITION": 33 + }, + "indices": 35, + "material": 10, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.012", + "primitives": [ + { + "attributes": { + "NORMAL": 37, + "POSITION": 36 + }, + "indices": 38, + "material": 9, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.013", + "primitives": [ + { + "attributes": { + "NORMAL": 40, + "POSITION": 39 + }, + "indices": 41, + "material": 8, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.014", + "primitives": [ + { + "attributes": { + "NORMAL": 43, + "POSITION": 42 + }, + "indices": 44, + "material": 7, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.015", + "primitives": [ + { + "attributes": { + "NORMAL": 46, + "POSITION": 45 + }, + "indices": 47, + "material": 6, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.016", + "primitives": [ + { + "attributes": { + "NORMAL": 49, + "POSITION": 48 + }, + "indices": 50, + "material": 5, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.017", + "primitives": [ + { + "attributes": { + "NORMAL": 52, + "POSITION": 51 + }, + "indices": 53, + "material": 4, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.018", + "primitives": [ + { + "attributes": { + "NORMAL": 55, + "POSITION": 54 + }, + "indices": 56, + "material": 3, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.019", + "primitives": [ + { + "attributes": { + "NORMAL": 58, + "POSITION": 57 + }, + "indices": 59, + "material": 2, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.020", + "primitives": [ + { + "attributes": { + "NORMAL": 61, + "POSITION": 60 + }, + "indices": 62, + "material": 1, + "mode": 4 + } + ] + }, + { + "name": "GeodeSphere.021", + "primitives": [ + { + "attributes": { + "NORMAL": 64, + "POSITION": 63 + }, + "indices": 65, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "name": "RootNode (gltf orientation matrix)", + "rotation": [ + -0.70710678118654746, + -0, + -0, + 0.70710678118654757 + ] + }, + { + "children": [ + 2 + ], + "name": "RootNode (model correction matrix)" + }, + { + "children": [ + 3, + 5, + 7, + 9, + 11, + 13, + 15, + 17, + 19, + 21, + 23, + 25, + 27, + 29, + 31, + 33, + 35, + 37, + 39, + 41, + 43, + 45 + ], + "name": "Root" + }, + { + "children": [ + 4 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -15, + 0, + 2, + 1 + ], + "name": "Sphere" + }, + { + "mesh": 0, + "name": "GeodeSphere" + }, + { + "children": [ + 6 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -12, + 0, + 2, + 1 + ], + "name": "Sphere.001" + }, + { + "mesh": 1, + "name": "GeodeSphere.001" + }, + { + "children": [ + 8 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -9, + 0, + 2, + 1 + ], + "name": "Sphere.002" + }, + { + "mesh": 2, + "name": "GeodeSphere.002" + }, + { + "children": [ + 10 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -6, + 0, + 2, + 1 + ], + "name": "Sphere.003" + }, + { + "mesh": 3, + "name": "GeodeSphere.003" + }, + { + "children": [ + 12 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -3, + 0, + 2, + 1 + ], + "name": "Sphere.004" + }, + { + "mesh": 4, + "name": "GeodeSphere.004" + }, + { + "children": [ + 14 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 9, + 0, + 2, + 1 + ], + "name": "Sphere.005" + }, + { + "mesh": 5, + "name": "GeodeSphere.005" + }, + { + "children": [ + 16 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 6, + 0, + 2, + 1 + ], + "name": "Sphere.006" + }, + { + "mesh": 6, + "name": "GeodeSphere.006" + }, + { + "children": [ + 18 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 3, + 0, + 2, + 1 + ], + "name": "Sphere.007" + }, + { + "mesh": 7, + "name": "GeodeSphere.007" + }, + { + "children": [ + 20 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 2, + 1 + ], + "name": "Sphere.008" + }, + { + "mesh": 8, + "name": "GeodeSphere.008" + }, + { + "children": [ + 22 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + -2, + 1 + ], + "name": "Sphere.009" + }, + { + "mesh": 9, + "name": "GeodeSphere.009" + }, + { + "children": [ + 24 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 3, + 0, + -2, + 1 + ], + "name": "Sphere.010" + }, + { + "mesh": 10, + "name": "GeodeSphere.010" + }, + { + "children": [ + 26 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 6, + 0, + -2, + 1 + ], + "name": "Sphere.011" + }, + { + "mesh": 11, + "name": "GeodeSphere.011" + }, + { + "children": [ + 28 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 9, + 0, + -2, + 1 + ], + "name": "Sphere.012" + }, + { + "mesh": 12, + "name": "GeodeSphere.012" + }, + { + "children": [ + 30 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -3, + 0, + -2, + 1 + ], + "name": "Sphere.013" + }, + { + "mesh": 13, + "name": "GeodeSphere.013" + }, + { + "children": [ + 32 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -6, + 0, + -2, + 1 + ], + "name": "Sphere.014" + }, + { + "mesh": 14, + "name": "GeodeSphere.014" + }, + { + "children": [ + 34 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -9, + 0, + -2, + 1 + ], + "name": "Sphere.015" + }, + { + "mesh": 15, + "name": "GeodeSphere.015" + }, + { + "children": [ + 36 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -12, + 0, + -2, + 1 + ], + "name": "Sphere.016" + }, + { + "mesh": 16, + "name": "GeodeSphere.016" + }, + { + "children": [ + 38 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + -15, + 0, + -2, + 1 + ], + "name": "Sphere.017" + }, + { + "mesh": 17, + "name": "GeodeSphere.017" + }, + { + "children": [ + 40 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 12, + 0, + 2, + 1 + ], + "name": "Sphere.018" + }, + { + "mesh": 18, + "name": "GeodeSphere.018" + }, + { + "children": [ + 42 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 15, + 0, + 2, + 1 + ], + "name": "Sphere.019" + }, + { + "mesh": 19, + "name": "GeodeSphere.019" + }, + { + "children": [ + 44 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 12, + 0, + -2, + 1 + ], + "name": "Sphere.020" + }, + { + "mesh": 20, + "name": "GeodeSphere.020" + }, + { + "children": [ + 46 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 15, + 0, + -2, + 1 + ], + "name": "Sphere.021" + }, + { + "mesh": 21, + "name": "GeodeSphere.021" + } + ], + "scene": 0, + "scenes": [ + { + "name": "OSG_Scene", + "nodes": [ + 0 + ] + } + ] +} +