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. 47
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  2. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  3. 8
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  4. 4
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  5. 123
      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;
#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;
}

@ -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
}
}

@ -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;

@ -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);
}

@ -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");

@ -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) {

@ -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());
}
}

@ -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);
}

Loading…
Cancel
Save