diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag index 9ce4e421b..afd891885 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag @@ -9,9 +9,7 @@ varying vec2 texCoord; varying vec2 texCoord2; #endif -#ifndef BASECOLORMAP - varying vec4 Color; -#endif +varying vec4 Color; uniform vec4 g_LightData[NB_LIGHTS]; @@ -33,11 +31,16 @@ varying vec3 wPosition; #ifdef BASECOLORMAP uniform sampler2D m_BaseColorMap; #endif -#ifdef METALLICMAP - uniform sampler2D m_MetallicMap; -#endif -#ifdef ROUGHNESSMAP - uniform sampler2D m_RoughnessMap; + +#ifdef USE_PACKED_MR + uniform sampler2D m_MetallicRoughnessMap; +#else + #ifdef METALLICMAP + uniform sampler2D m_MetallicMap; + #endif + #ifdef ROUGHNESSMAP + uniform sampler2D m_RoughnessMap; + #endif #endif #ifdef EMISSIVE @@ -109,19 +112,26 @@ void main(){ #endif #ifdef BASECOLORMAP - vec4 albedo = texture2D(m_BaseColorMap, newTexCoord); + vec4 albedo = texture2D(m_BaseColorMap, newTexCoord) * Color; #else vec4 albedo = Color; #endif - #ifdef ROUGHNESSMAP - float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-8); - #else - float Roughness = max(m_Roughness, 1e-8); - #endif - #ifdef METALLICMAP - float Metallic = texture2D(m_MetallicMap, newTexCoord).r; + + #ifdef USE_PACKED_MR + vec2 rm = texture2D(m_MetallicRoughnessMap, newTexCoord).gb; + float Roughness = rm.x * max(m_Roughness, 1e-8); + float Metallic = rm.y * max(m_Metallic, 0.0); #else - float Metallic = max(m_Metallic, 0.0); + #ifdef ROUGHNESSMAP + float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-8); + #else + float Roughness = max(m_Roughness, 1e-8); + #endif + #ifdef METALLICMAP + float Metallic = texture2D(m_MetallicMap, newTexCoord).r * max(m_Metallic, 0.0); + #else + float Metallic = max(m_Metallic, 0.0); + #endif #endif float alpha = albedo.a; @@ -141,7 +151,7 @@ void main(){ //as it's complient with normal maps generated with blender. //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 //for more explanation. - vec3 normal = normalize((normalHeight.xyz * vec3(2.0,-2.0,2.0) - vec3(1.0,-1.0,1.0))); + vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0))); normal = normalize(tbnMat * normal); //normal = normalize(normal * inverse(tbnMat)); #else @@ -238,6 +248,5 @@ void main(){ #endif gl_FragColor.a = alpha; - } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md index 5595ad1e1..4108ba53c 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md @@ -6,26 +6,29 @@ MaterialDef PBR Lighting { Float AlphaDiscardThreshold (AlphaTestFallOff) //metalness of the material - Float Metallic : 0.0 + Float Metallic : 1.0 //Roughness of the material Float Roughness : 1.0 // Base material color - Color BaseColor + Color BaseColor : 1.0 1.0 1.0 1.0 // The emissive color of the object Color Emissive // the emissive power Float EmissivePower : 3.0 // the emissive intensity - Float EmissiveIntensity : 1.0 + Float EmissiveIntensity : 2.0 // BaseColor map Texture2D BaseColorMap - // Specular/gloss map + // Metallic map Texture2D MetallicMap -LINEAR // Roughness Map Texture2D RoughnessMap -LINEAR + + //Metallic and Roughness are packed respectively in the b and g channel of a single map + Texture2D MetallicRoughnessMap -LINEAR // Texture of the emissive parts of the material Texture2D EmissiveMap @@ -33,6 +36,9 @@ MaterialDef PBR Lighting { // Normal map Texture2D NormalMap -LINEAR + //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl) + Float NormalType : -1.0 + // For Spec gloss pipeline Texture2D SpecularMap Texture2D GlossMap @@ -138,7 +144,8 @@ MaterialDef PBR Lighting { DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones INSTANCING : UseInstancing - //INDIRECT_LIGHTING : IntegrateBRDF + USE_PACKED_MR: MetallicRoughnessMap + NORMAL_TYPE: NormalType VERTEX_COLOR : UseVertexColor } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert index c5cb00075..77782456b 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert @@ -13,9 +13,7 @@ varying vec2 texCoord; attribute vec2 inTexCoord2; #endif -#ifndef BASECOLORMAP - varying vec4 Color; -#endif +varying vec4 Color; attribute vec3 inPosition; attribute vec2 inTexCoord; @@ -61,9 +59,7 @@ void main(){ wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w); #endif - #ifndef BASECOLORMAP - Color = m_BaseColor; - #endif + Color = m_BaseColor; #ifdef VERTEX_COLOR Color *= inColor; diff --git a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib index 75387bb05..40aea5dfa 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib @@ -143,8 +143,8 @@ vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 Spe vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){ //TODO magic values should be replaced by defines. - float Lod = log2(Roughness) * 1.5 + 6.0 - 1.0; - vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz, Lod).rgb; + float Lod = log2(Roughness) * 1.6 + 5.0; + vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz, Lod).rgb; return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv); } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 4a321362b..eed54938c 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -8,6 +8,8 @@ import com.jme3.material.RenderState; import com.jme3.math.*; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.*; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; import java.io.*; import java.nio.Buffer; @@ -35,6 +37,9 @@ public class GltfLoader implements AssetLoader { private JsonArray bufferViews; private JsonArray buffers; private JsonArray materials; + private JsonArray textures; + private JsonArray images; + private JsonArray samplers; private Material defaultMat; private AssetInfo info; @@ -75,6 +80,9 @@ public class GltfLoader implements AssetLoader { bufferViews = root.getAsJsonArray("bufferViews"); buffers = root.getAsJsonArray("buffers"); materials = root.getAsJsonArray("materials"); + textures = root.getAsJsonArray("textures"); + images = root.getAsJsonArray("images"); + samplers = root.getAsJsonArray("samplers"); JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene"); @@ -123,9 +131,11 @@ public class GltfLoader implements AssetLoader { activeChild = defaultScene.getAsInt(); } root.getChild(activeChild).setCullHint(Spatial.CullHint.Inherit); + System.err.println(nbPrim + " Geoms loaded"); return root; } + int nbPrim = 0; private Spatial loadNode(int nodeIndex) throws IOException { Spatial spatial = fetchFromCache("nodes", nodeIndex, Spatial.class); if (spatial != null) { @@ -134,20 +144,18 @@ public class GltfLoader implements AssetLoader { return spatial.clone(); } JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject(); + JsonArray children = nodeData.getAsJsonArray("children"); Integer meshIndex = getAsInteger(nodeData, "mesh"); if (meshIndex != null) { if (meshes == null) { throw new AssetLoadException("Can't find any mesh data, yet a node references a mesh"); } - //TODO material - Material mat = defaultMat; - //there is a mesh in this node, however gltf can split meshes in primitives (some kind of sub meshes), //We don't have this in JME so we have to make one mesh and one Geometry for each primitive. Geometry[] primitives = loadMeshPrimitives(meshIndex); - if (primitives.length > 1) { - //only one geometry, let's not wrap it in another node. + if (primitives.length == 1 && children == null) { + //only one geometry, let's not wrap it in another node unless the node has children. spatial = primitives[0]; } else { //several geometries, let's make a parent Node and attach them to it @@ -157,21 +165,23 @@ public class GltfLoader implements AssetLoader { } spatial = node; } - + nbPrim += primitives.length; spatial.setName(loadMeshName(meshIndex)); } else { //no mesh, we have a node. Can be a camera node or a regular node. //TODO handle camera nodes? Node node = new Node(); - JsonArray children = nodeData.getAsJsonArray("children"); - if (children != null) { - for (JsonElement child : children) { - node.attachChild(loadNode(child.getAsInt())); - } - } + spatial = node; } + + if (children != null) { + for (JsonElement child : children) { + ((Node) spatial).attachChild(loadNode(child.getAsInt())); + } + } + if (spatial.getName() == null) { spatial.setName(getAsString(nodeData.getAsJsonObject(), "name")); } @@ -236,6 +246,7 @@ public class GltfLoader implements AssetLoader { if (primitives == null) { throw new AssetLoadException("Can't find any primitives in mesh " + meshIndex); } + String name = getAsString(meshData, "name"); geomArray = new Geometry[primitives.size()]; int index = 0; @@ -267,11 +278,15 @@ public class GltfLoader implements AssetLoader { } } + if (name != null) { + geom.setName(name + (primitives.size() > 1 ? ("_" + index) : "")); + } + geom.updateModelBound(); geomArray[index] = geom; index++; - //TODO material, targets(morph anim...) + //TODO targets(morph anim...) } addToCache("meshes", meshIndex, geomArray, meshes.size()); @@ -404,20 +419,86 @@ public class GltfLoader implements AssetLoader { adapter.setParam(mat, "metallicFactor", getAsFloat(pbrMat, "metallicFactor", 1f)); adapter.setParam(mat, "roughnessFactor", getAsFloat(pbrMat, "roughnessFactor", 1f)); adapter.setParam(mat, "emissiveFactor", getAsColor(matData, "emissiveFactor", ColorRGBA.Black)); - adapter.setParam(mat, "alphaMode", getAsString(matData, "alphaMode")); - adapter.setParam(mat, "alphaCutoff", getAsFloat(matData, "alphaCutoff")); + String alphaMode = getAsString(matData, "alphaMode"); + adapter.setParam(mat, "alphaMode", alphaMode); + if (alphaMode != null && alphaMode.equals("MASK")) { + adapter.setParam(mat, "alphaCutoff", getAsFloat(matData, "alphaCutoff")); + } adapter.setParam(mat, "doubleSided", getAsBoolean(matData, "doubleSided")); - //TODO textures - //adapter.setParam(mat, "baseColorTexture", readTexture); - //adapter.setParam(mat, "metallicRoughnessTexture", readTexture); - //adapter.setParam(mat, "normalTexture", readTexture); - //adapter.setParam(mat, "occlusionTexture", readTexture); - //adapter.setParam(mat, "emissiveTexture", readTexture); + adapter.setParam(mat, "baseColorTexture", readTexture(pbrMat.getAsJsonObject("baseColorTexture"))); + adapter.setParam(mat, "metallicRoughnessTexture", readTexture(pbrMat.getAsJsonObject("metallicRoughnessTexture"))); + adapter.setParam(mat, "normalTexture", readTexture(matData.getAsJsonObject("normalTexture"))); + adapter.setParam(mat, "occlusionTexture", readTexture(matData.getAsJsonObject("occlusionTexture"))); + adapter.setParam(mat, "emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture"))); return mat; } + private Texture2D readTexture(JsonObject texture) { + if (texture == null) { + return null; + } + Integer textureIndex = getAsInteger(texture, "index"); + if (textureIndex == null) { + throw new AssetLoadException("Texture as no index"); + } + if (textures == null) { + throw new AssetLoadException("There are no textures, yet one is referenced by a material"); + } + JsonObject textureData = textures.get(textureIndex).getAsJsonObject(); + Integer sourceIndex = getAsInteger(textureData, "source"); + Integer samplerIndex = getAsInteger(textureData, "sampler"); + + Texture2D texture2d = loadImage(sourceIndex); + readSampler(samplerIndex, texture2d); + + return texture2d; + } + + private Texture2D loadImage(int sourceIndex) { + if (images == null) { + throw new AssetLoadException("No image defined"); + } + + JsonObject image = images.get(sourceIndex).getAsJsonObject(); + String uri = getAsString(image, "uri"); + if (uri == null) { + //Image is embed in a buffer not supported yet + //TODO support images embed in a buffer + throw new AssetLoadException("Images embed in a buffer are not supported yet"); + } else if (uri.startsWith("data:")) { + //base64 encoded image, not supported yet + //TODO support base64 encoded images + throw new AssetLoadException("Base64 encoded embed images are not supported yet"); + } else { + TextureKey key = new TextureKey(info.getKey().getFolder() + uri, false); + Texture tex = info.getManager().loadTexture(key); + return (Texture2D) tex; + } + + } + + private void readSampler(int samplerIndex, Texture2D texture) { + if (samplers == null) { + throw new AssetLoadException("No samplers defined"); + } + JsonObject sampler = samplers.get(samplerIndex).getAsJsonObject(); + Texture.MagFilter magFilter = getMagFilter(getAsInteger(sampler, "magFilter")); + Texture.MinFilter minFilter = getMinFilter(getAsInteger(sampler, "minFilter")); + Texture.WrapMode wrapS = getWrapMode(getAsInteger(sampler, "wrapS")); + Texture.WrapMode wrapT = getWrapMode(getAsInteger(sampler, "wrapT")); + + if (magFilter != null) { + texture.setMagFilter(magFilter); + } + if (minFilter != null) { + texture.setMinFilter(minFilter); + } + texture.setWrap(Texture.WrapAxis.S, wrapS); + texture.setWrap(Texture.WrapAxis.T, wrapT); + } + private String loadMeshName(int meshIndex) { JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); return getAsString(meshData, "name"); diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 2d24fcbfc..8ed1b21bd 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -5,6 +5,7 @@ import com.jme3.asset.AssetLoadException; import com.jme3.math.ColorRGBA; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; +import com.jme3.texture.Texture; import com.jme3.util.LittleEndien; import java.io.*; @@ -116,6 +117,55 @@ public class GltfUtils { } } + public static Texture.MagFilter getMagFilter(Integer value) { + if (value == null) { + return null; + } + switch (value) { + case 9728: + return Texture.MagFilter.Nearest; + case 9729: + return Texture.MagFilter.Bilinear; + } + return null; + } + + public static Texture.MinFilter getMinFilter(Integer value) { + if (value == null) { + return null; + } + switch (value) { + case 9728: + return Texture.MinFilter.NearestNoMipMaps; + case 9729: + return Texture.MinFilter.BilinearNoMipMaps; + case 9984: + return Texture.MinFilter.NearestNearestMipMap; + case 9985: + return Texture.MinFilter.BilinearNearestMipMap; + case 9986: + return Texture.MinFilter.NearestLinearMipMap; + case 9987: + return Texture.MinFilter.Trilinear; + + } + return null; + } + + public static Texture.WrapMode getWrapMode(Integer value) { + if (value == null) { + return Texture.WrapMode.Repeat; + } + switch (value) { + case 33071: + return Texture.WrapMode.EdgeClamp; + case 33648: + return Texture.WrapMode.MirroredRepeat; + default: + return Texture.WrapMode.Repeat; + } + } + public static void padBuffer(Buffer buffer, int bufferSize) { buffer.clear(); if (buffer instanceof IntBuffer) { @@ -255,7 +305,7 @@ public class GltfUtils { return null; } JsonArray color = el.getAsJsonArray(); - return new ColorRGBA(color.get(0).getAsFloat(), color.get(1).getAsFloat(), color.get(2).getAsFloat(), color.get(3).getAsFloat()); + return new ColorRGBA(color.get(0).getAsFloat(), color.get(1).getAsFloat(), color.get(2).getAsFloat(), color.size() > 3 ? color.get(3).getAsFloat() : 1f); } public static ColorRGBA getAsColor(JsonObject parent, String name, ColorRGBA defaultValue) { diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java index 069b388a6..0ba4416cd 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java @@ -81,6 +81,7 @@ public abstract class MaterialAdapter { if (value instanceof Vector2f) return VarType.Vector2; if (value instanceof Matrix3f) return VarType.Matrix3; if (value instanceof Matrix4f) return VarType.Matrix4; + if (value instanceof String) return VarType.Boolean; throw new AssetLoadException("Unsupported material parameter type : " + value.getClass().getSimpleName()); } } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java index d783e925e..3a15ffcf9 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java @@ -16,10 +16,10 @@ public class PBRMaterialAdapter extends MaterialAdapter { addParamMapping("metallicRoughnessTexture", "MetallicRoughnessMap"); addParamMapping("normalTexture", "NormalMap"); addParamMapping("occlusionTexture", "LightMap"); - addParamMapping("emisiveTexture", "EmissiveMap"); - addParamMapping("emisiveFactor", "Emissive"); + addParamMapping("emissiveTexture", "EmissiveMap"); + addParamMapping("emissiveFactor", "Emissive"); addParamMapping("alphaMode", "alpha"); - addParamMapping("alphaCutoff", "AlphaDiscardThreshold"); + // addParamMapping("alphaCutoff", "AlphaDiscardThreshold"); addParamMapping("doubleSided", "doubleSided"); } @@ -47,9 +47,9 @@ public class PBRMaterialAdapter extends MaterialAdapter { } return null; } - if (param.getName().equals("MetallicRoughnessMap")) { - //use packed Metallic/Roughness - mat.setBoolean("UsePackedMR", true); + if (param.getName().equals("NormalMap")) { + //Set the normal map type to OpenGl + mat.setFloat("NormalType", 1.0f); }