Implemented texture loading and PBR metalness/roughness pipeline support

Fixed  some mesh loading issues.
fix-456
Nehon 8 years ago committed by Rémy Bouquet
parent 3bbfabed5e
commit 40c4f7936d
  1. 29
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  2. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  3. 6
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  4. 2
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  5. 117
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  6. 52
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
  7. 1
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/MaterialAdapter.java
  8. 12
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMaterialAdapter.java

@ -9,9 +9,7 @@ varying vec2 texCoord;
varying vec2 texCoord2; varying vec2 texCoord2;
#endif #endif
#ifndef BASECOLORMAP varying vec4 Color;
varying vec4 Color;
#endif
uniform vec4 g_LightData[NB_LIGHTS]; uniform vec4 g_LightData[NB_LIGHTS];
@ -33,11 +31,16 @@ varying vec3 wPosition;
#ifdef BASECOLORMAP #ifdef BASECOLORMAP
uniform sampler2D m_BaseColorMap; uniform sampler2D m_BaseColorMap;
#endif #endif
#ifdef METALLICMAP
#ifdef USE_PACKED_MR
uniform sampler2D m_MetallicRoughnessMap;
#else
#ifdef METALLICMAP
uniform sampler2D m_MetallicMap; uniform sampler2D m_MetallicMap;
#endif #endif
#ifdef ROUGHNESSMAP #ifdef ROUGHNESSMAP
uniform sampler2D m_RoughnessMap; uniform sampler2D m_RoughnessMap;
#endif
#endif #endif
#ifdef EMISSIVE #ifdef EMISSIVE
@ -109,20 +112,27 @@ void main(){
#endif #endif
#ifdef BASECOLORMAP #ifdef BASECOLORMAP
vec4 albedo = texture2D(m_BaseColorMap, newTexCoord); vec4 albedo = texture2D(m_BaseColorMap, newTexCoord) * Color;
#else #else
vec4 albedo = Color; vec4 albedo = Color;
#endif #endif
#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
#ifdef ROUGHNESSMAP #ifdef ROUGHNESSMAP
float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-8); float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-8);
#else #else
float Roughness = max(m_Roughness, 1e-8); float Roughness = max(m_Roughness, 1e-8);
#endif #endif
#ifdef METALLICMAP #ifdef METALLICMAP
float Metallic = texture2D(m_MetallicMap, newTexCoord).r; float Metallic = texture2D(m_MetallicMap, newTexCoord).r * max(m_Metallic, 0.0);
#else #else
float Metallic = max(m_Metallic, 0.0); float Metallic = max(m_Metallic, 0.0);
#endif #endif
#endif
float alpha = albedo.a; float alpha = albedo.a;
@ -141,7 +151,7 @@ void main(){
//as it's complient with normal maps generated with blender. //as it's complient with normal maps generated with blender.
//see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898
//for more explanation. //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(tbnMat * normal);
//normal = normalize(normal * inverse(tbnMat)); //normal = normalize(normal * inverse(tbnMat));
#else #else
@ -239,5 +249,4 @@ void main(){
gl_FragColor.a = alpha; gl_FragColor.a = alpha;
} }

@ -6,33 +6,39 @@ MaterialDef PBR Lighting {
Float AlphaDiscardThreshold (AlphaTestFallOff) Float AlphaDiscardThreshold (AlphaTestFallOff)
//metalness of the material //metalness of the material
Float Metallic : 0.0 Float Metallic : 1.0
//Roughness of the material //Roughness of the material
Float Roughness : 1.0 Float Roughness : 1.0
// Base material color // Base material color
Color BaseColor Color BaseColor : 1.0 1.0 1.0 1.0
// The emissive color of the object // The emissive color of the object
Color Emissive Color Emissive
// the emissive power // the emissive power
Float EmissivePower : 3.0 Float EmissivePower : 3.0
// the emissive intensity // the emissive intensity
Float EmissiveIntensity : 1.0 Float EmissiveIntensity : 2.0
// BaseColor map // BaseColor map
Texture2D BaseColorMap Texture2D BaseColorMap
// Specular/gloss map // Metallic map
Texture2D MetallicMap -LINEAR Texture2D MetallicMap -LINEAR
// Roughness Map // Roughness Map
Texture2D RoughnessMap -LINEAR 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 // Texture of the emissive parts of the material
Texture2D EmissiveMap Texture2D EmissiveMap
// Normal map // Normal map
Texture2D NormalMap -LINEAR Texture2D NormalMap -LINEAR
//The type of normal map: -1.0 (DirectX), 1.0 (OpenGl)
Float NormalType : -1.0
// For Spec gloss pipeline // For Spec gloss pipeline
Texture2D SpecularMap Texture2D SpecularMap
Texture2D GlossMap Texture2D GlossMap
@ -138,7 +144,8 @@ MaterialDef PBR Lighting {
DISCARD_ALPHA : AlphaDiscardThreshold DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
//INDIRECT_LIGHTING : IntegrateBRDF USE_PACKED_MR: MetallicRoughnessMap
NORMAL_TYPE: NormalType
VERTEX_COLOR : UseVertexColor VERTEX_COLOR : UseVertexColor
} }
} }

@ -13,9 +13,7 @@ varying vec2 texCoord;
attribute vec2 inTexCoord2; attribute vec2 inTexCoord2;
#endif #endif
#ifndef BASECOLORMAP varying vec4 Color;
varying vec4 Color;
#endif
attribute vec3 inPosition; attribute vec3 inPosition;
attribute vec2 inTexCoord; attribute vec2 inTexCoord;
@ -61,9 +59,7 @@ void main(){
wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w); wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w);
#endif #endif
#ifndef BASECOLORMAP
Color = m_BaseColor; Color = m_BaseColor;
#endif
#ifdef VERTEX_COLOR #ifdef VERTEX_COLOR
Color *= inColor; Color *= inColor;

@ -143,7 +143,7 @@ vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 Spe
vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){ vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
//TODO magic values should be replaced by defines. //TODO magic values should be replaced by defines.
float Lod = log2(Roughness) * 1.5 + 6.0 - 1.0; float Lod = log2(Roughness) * 1.6 + 5.0;
vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz, Lod).rgb; vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz, Lod).rgb;
return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv); return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv);
} }

@ -8,6 +8,8 @@ import com.jme3.material.RenderState;
import com.jme3.math.*; import com.jme3.math.*;
import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.*; import com.jme3.scene.*;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import java.io.*; import java.io.*;
import java.nio.Buffer; import java.nio.Buffer;
@ -35,6 +37,9 @@ public class GltfLoader implements AssetLoader {
private JsonArray bufferViews; private JsonArray bufferViews;
private JsonArray buffers; private JsonArray buffers;
private JsonArray materials; private JsonArray materials;
private JsonArray textures;
private JsonArray images;
private JsonArray samplers;
private Material defaultMat; private Material defaultMat;
private AssetInfo info; private AssetInfo info;
@ -75,6 +80,9 @@ public class GltfLoader implements AssetLoader {
bufferViews = root.getAsJsonArray("bufferViews"); bufferViews = root.getAsJsonArray("bufferViews");
buffers = root.getAsJsonArray("buffers"); buffers = root.getAsJsonArray("buffers");
materials = root.getAsJsonArray("materials"); materials = root.getAsJsonArray("materials");
textures = root.getAsJsonArray("textures");
images = root.getAsJsonArray("images");
samplers = root.getAsJsonArray("samplers");
JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene"); JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene");
@ -123,9 +131,11 @@ public class GltfLoader implements AssetLoader {
activeChild = defaultScene.getAsInt(); activeChild = defaultScene.getAsInt();
} }
root.getChild(activeChild).setCullHint(Spatial.CullHint.Inherit); root.getChild(activeChild).setCullHint(Spatial.CullHint.Inherit);
System.err.println(nbPrim + " Geoms loaded");
return root; return root;
} }
int nbPrim = 0;
private Spatial loadNode(int nodeIndex) throws IOException { private Spatial loadNode(int nodeIndex) throws IOException {
Spatial spatial = fetchFromCache("nodes", nodeIndex, Spatial.class); Spatial spatial = fetchFromCache("nodes", nodeIndex, Spatial.class);
if (spatial != null) { if (spatial != null) {
@ -134,20 +144,18 @@ public class GltfLoader implements AssetLoader {
return spatial.clone(); return spatial.clone();
} }
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject(); JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
JsonArray children = nodeData.getAsJsonArray("children");
Integer meshIndex = getAsInteger(nodeData, "mesh"); Integer meshIndex = getAsInteger(nodeData, "mesh");
if (meshIndex != null) { if (meshIndex != null) {
if (meshes == null) { if (meshes == null) {
throw new AssetLoadException("Can't find any mesh data, yet a node references a mesh"); 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), //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. //We don't have this in JME so we have to make one mesh and one Geometry for each primitive.
Geometry[] primitives = loadMeshPrimitives(meshIndex); Geometry[] primitives = loadMeshPrimitives(meshIndex);
if (primitives.length > 1) { if (primitives.length == 1 && children == null) {
//only one geometry, let's not wrap it in another node. //only one geometry, let's not wrap it in another node unless the node has children.
spatial = primitives[0]; spatial = primitives[0];
} else { } else {
//several geometries, let's make a parent Node and attach them to it //several geometries, let's make a parent Node and attach them to it
@ -157,21 +165,23 @@ public class GltfLoader implements AssetLoader {
} }
spatial = node; spatial = node;
} }
nbPrim += primitives.length;
spatial.setName(loadMeshName(meshIndex)); spatial.setName(loadMeshName(meshIndex));
} else { } else {
//no mesh, we have a node. Can be a camera node or a regular node. //no mesh, we have a node. Can be a camera node or a regular node.
//TODO handle camera nodes? //TODO handle camera nodes?
Node node = new Node(); Node node = new Node();
JsonArray children = nodeData.getAsJsonArray("children");
spatial = node;
}
if (children != null) { if (children != null) {
for (JsonElement child : children) { for (JsonElement child : children) {
node.attachChild(loadNode(child.getAsInt())); ((Node) spatial).attachChild(loadNode(child.getAsInt()));
}
} }
spatial = node;
} }
if (spatial.getName() == null) { if (spatial.getName() == null) {
spatial.setName(getAsString(nodeData.getAsJsonObject(), "name")); spatial.setName(getAsString(nodeData.getAsJsonObject(), "name"));
} }
@ -236,6 +246,7 @@ public class GltfLoader implements AssetLoader {
if (primitives == null) { if (primitives == null) {
throw new AssetLoadException("Can't find any primitives in mesh " + meshIndex); throw new AssetLoadException("Can't find any primitives in mesh " + meshIndex);
} }
String name = getAsString(meshData, "name");
geomArray = new Geometry[primitives.size()]; geomArray = new Geometry[primitives.size()];
int index = 0; int index = 0;
@ -267,11 +278,15 @@ public class GltfLoader implements AssetLoader {
} }
} }
if (name != null) {
geom.setName(name + (primitives.size() > 1 ? ("_" + index) : ""));
}
geom.updateModelBound(); geom.updateModelBound();
geomArray[index] = geom; geomArray[index] = geom;
index++; index++;
//TODO material, targets(morph anim...) //TODO targets(morph anim...)
} }
addToCache("meshes", meshIndex, geomArray, meshes.size()); 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, "metallicFactor", getAsFloat(pbrMat, "metallicFactor", 1f));
adapter.setParam(mat, "roughnessFactor", getAsFloat(pbrMat, "roughnessFactor", 1f)); adapter.setParam(mat, "roughnessFactor", getAsFloat(pbrMat, "roughnessFactor", 1f));
adapter.setParam(mat, "emissiveFactor", getAsColor(matData, "emissiveFactor", ColorRGBA.Black)); adapter.setParam(mat, "emissiveFactor", getAsColor(matData, "emissiveFactor", ColorRGBA.Black));
adapter.setParam(mat, "alphaMode", getAsString(matData, "alphaMode")); 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, "alphaCutoff", getAsFloat(matData, "alphaCutoff"));
}
adapter.setParam(mat, "doubleSided", getAsBoolean(matData, "doubleSided")); adapter.setParam(mat, "doubleSided", getAsBoolean(matData, "doubleSided"));
//TODO textures adapter.setParam(mat, "baseColorTexture", readTexture(pbrMat.getAsJsonObject("baseColorTexture")));
//adapter.setParam(mat, "baseColorTexture", readTexture); adapter.setParam(mat, "metallicRoughnessTexture", readTexture(pbrMat.getAsJsonObject("metallicRoughnessTexture")));
//adapter.setParam(mat, "metallicRoughnessTexture", readTexture); adapter.setParam(mat, "normalTexture", readTexture(matData.getAsJsonObject("normalTexture")));
//adapter.setParam(mat, "normalTexture", readTexture); adapter.setParam(mat, "occlusionTexture", readTexture(matData.getAsJsonObject("occlusionTexture")));
//adapter.setParam(mat, "occlusionTexture", readTexture); adapter.setParam(mat, "emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture")));
//adapter.setParam(mat, "emissiveTexture", readTexture);
return mat; 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) { private String loadMeshName(int meshIndex) {
JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
return getAsString(meshData, "name"); return getAsString(meshData, "name");

@ -5,6 +5,7 @@ import com.jme3.asset.AssetLoadException;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
import com.jme3.texture.Texture;
import com.jme3.util.LittleEndien; import com.jme3.util.LittleEndien;
import java.io.*; 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) { public static void padBuffer(Buffer buffer, int bufferSize) {
buffer.clear(); buffer.clear();
if (buffer instanceof IntBuffer) { if (buffer instanceof IntBuffer) {
@ -255,7 +305,7 @@ public class GltfUtils {
return null; return null;
} }
JsonArray color = el.getAsJsonArray(); 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) { public static ColorRGBA getAsColor(JsonObject parent, String name, ColorRGBA defaultValue) {

@ -81,6 +81,7 @@ public abstract class MaterialAdapter {
if (value instanceof Vector2f) return VarType.Vector2; if (value instanceof Vector2f) return VarType.Vector2;
if (value instanceof Matrix3f) return VarType.Matrix3; if (value instanceof Matrix3f) return VarType.Matrix3;
if (value instanceof Matrix4f) return VarType.Matrix4; if (value instanceof Matrix4f) return VarType.Matrix4;
if (value instanceof String) return VarType.Boolean;
throw new AssetLoadException("Unsupported material parameter type : " + value.getClass().getSimpleName()); throw new AssetLoadException("Unsupported material parameter type : " + value.getClass().getSimpleName());
} }
} }

@ -16,10 +16,10 @@ public class PBRMaterialAdapter extends MaterialAdapter {
addParamMapping("metallicRoughnessTexture", "MetallicRoughnessMap"); addParamMapping("metallicRoughnessTexture", "MetallicRoughnessMap");
addParamMapping("normalTexture", "NormalMap"); addParamMapping("normalTexture", "NormalMap");
addParamMapping("occlusionTexture", "LightMap"); addParamMapping("occlusionTexture", "LightMap");
addParamMapping("emisiveTexture", "EmissiveMap"); addParamMapping("emissiveTexture", "EmissiveMap");
addParamMapping("emisiveFactor", "Emissive"); addParamMapping("emissiveFactor", "Emissive");
addParamMapping("alphaMode", "alpha"); addParamMapping("alphaMode", "alpha");
addParamMapping("alphaCutoff", "AlphaDiscardThreshold"); // addParamMapping("alphaCutoff", "AlphaDiscardThreshold");
addParamMapping("doubleSided", "doubleSided"); addParamMapping("doubleSided", "doubleSided");
} }
@ -47,9 +47,9 @@ public class PBRMaterialAdapter extends MaterialAdapter {
} }
return null; return null;
} }
if (param.getName().equals("MetallicRoughnessMap")) { if (param.getName().equals("NormalMap")) {
//use packed Metallic/Roughness //Set the normal map type to OpenGl
mat.setBoolean("UsePackedMR", true); mat.setFloat("NormalType", 1.0f);
} }

Loading…
Cancel
Save