/// <summary> /// Import materials step by step. Call this until it returns false. /// </summary> /// <param name="info">Progress information to be updated</param> /// <returns>Return true if in progress, false otherwise.</returns> public bool BuildMaterials(ProgressInfo info) { if (materialData == null) { Debug.LogWarning("No material library defined."); return(false); } if (info.materialsLoaded >= materialData.Count) { return(false); } MaterialData matData = materialData[info.materialsLoaded]; info.materialsLoaded++; if (currMaterials.ContainsKey(matData.materialName)) { Debug.LogWarning("Duplicate material found: " + matData.materialName + ". Repeated occurence ignored"); } else { currMaterials.Add(matData.materialName, BuildMaterial(matData)); } return(info.materialsLoaded < materialData.Count); }
/// <summary> /// Parse bump parameters. /// </summary> /// <param name="param">list of paramers</param> /// <param name="mtlData">material data to be updated</param> /// <remarks>Only the bump map texture path is used here.</remarks> /// <seealso cref="https://github.com/hammmm/unity-obj-loader"/> private void ParseBumpParameters(string[] param, MaterialData mtlData) { Regex regexNumber = new Regex(@"^[-+]?[0-9]*\.?[0-9]+$"); var bumpParams = new Dictionary <string, BumpParamDef>(); bumpParams.Add("bm", new BumpParamDef("bm", "string", 1, 1)); bumpParams.Add("clamp", new BumpParamDef("clamp", "string", 1, 1)); bumpParams.Add("blendu", new BumpParamDef("blendu", "string", 1, 1)); bumpParams.Add("blendv", new BumpParamDef("blendv", "string", 1, 1)); bumpParams.Add("imfchan", new BumpParamDef("imfchan", "string", 1, 1)); bumpParams.Add("mm", new BumpParamDef("mm", "string", 1, 1)); bumpParams.Add("o", new BumpParamDef("o", "number", 1, 3)); bumpParams.Add("s", new BumpParamDef("s", "number", 1, 3)); bumpParams.Add("t", new BumpParamDef("t", "number", 1, 3)); bumpParams.Add("texres", new BumpParamDef("texres", "string", 1, 1)); int pos = 1; string filename = null; while (pos < param.Length) { if (!param[pos].StartsWith("-")) { filename = param[pos]; pos++; continue; } // option processing string optionName = param[pos].Substring(1); pos++; if (!bumpParams.ContainsKey(optionName)) { continue; } BumpParamDef def = bumpParams[optionName]; ArrayList args = new ArrayList(); int i = 0; bool isOptionNotEnough = false; for (; i < def.valueNumMin; i++, pos++) { if (pos >= param.Length) { isOptionNotEnough = true; break; } if (def.valueType == "number") { Match match = regexNumber.Match(param[pos]); if (!match.Success) { isOptionNotEnough = true; break; } } args.Add(param[pos]); } if (isOptionNotEnough) { Debug.Log("bump variable value not enough for option:" + optionName + " of material:" + mtlData.materialName); continue; } for (; i < def.valueNumMax && pos < param.Length; i++, pos++) { if (def.valueType == "number") { Match match = regexNumber.Match(param[pos]); if (!match.Success) { break; } } args.Add(param[pos]); } // TODO: some processing of options Debug.Log("found option: " + optionName + " of material: " + mtlData.materialName + " args: " + string.Concat(args.ToArray())); } // set the file name, if found // TODO: other parsed parameters are not used for now if (filename != null) { mtlData.bumpTexPath = filename; } }
/// <summary> /// Parse the material library lines to get material data. /// </summary> /// <param name="lines">lines read from the material library file</param> /// <param name="mtlData">list of material data</param> private void ParseMaterialData(string[] lines, List <MaterialData> mtlData) { MaterialData current = new MaterialData(); char[] separators = new char[] { ' ', '\t' }; for (int i = 0; i < lines.Length; i++) { string line = lines[i].Trim(); // remove comments if (line.IndexOf("#") != -1) { line = line.Substring(0, line.IndexOf("#")); } string[] p = line.Split(separators, StringSplitOptions.RemoveEmptyEntries); if (p.Length == 0 || string.IsNullOrEmpty(p[0])) { continue; } string parameters = null; if (line.Length > p[0].Length) { parameters = line.Substring(p[0].Length + 1).Trim(); } try { switch (p[0]) { case "newmtl": current = new MaterialData(); current.materialName = DataSet.FixMaterialName(parameters); mtlData.Add(current); break; case "Ka": // Ambient component (not supported) current.ambientColor = StringsToColor(p); break; case "Kd": // Diffuse component current.diffuseColor = StringsToColor(p); break; case "Ks": // Specular component current.specularColor = StringsToColor(p); break; case "Ke": // Specular component current.emissiveColor = StringsToColor(p); break; case "Ns": // Specular exponent --> shininess current.shininess = ParseFloat(p[1]); break; case "d": // dissolve into the background (1=opaque, 0=transparent) current.overallAlpha = p.Length > 1 && p[1] != "" ? ParseFloat(p[1]) : 1.0f; break; case "Tr": // Transparency current.overallAlpha = p.Length > 1 && p[1] != "" ? 1.0f - ParseFloat(p[1]) : 1.0f; break; case "map_KD": case "map_Kd": // Color texture, diffuse reflectivity if (!string.IsNullOrEmpty(parameters)) { current.diffuseTexPath = parameters; } break; // TODO: different processing needed, options not supported case "map_Ks": // specular reflectivity of the material case "map_kS": case "map_Ns": // Scalar texture for specular exponent if (!string.IsNullOrEmpty(parameters)) { current.specularTexPath = parameters; } break; case "map_bump": // Bump map texture if (!string.IsNullOrEmpty(parameters)) { current.bumpTexPath = parameters; } break; case "bump": ParseBumpParameters(p, current); break; case "map_opacity": case "map_d": // Scalar texture modulating the dissolve into the background if (!string.IsNullOrEmpty(parameters)) { current.opacityTexPath = parameters; } break; case "illum": // Illumination model. 1 - diffuse, 2 - specular (not used) current.illumType = int.Parse(p[1]); break; case "refl": // reflection map (replaced with Unity environment reflection) if (!string.IsNullOrEmpty(parameters)) { current.hasReflectionTex = true; } break; case "map_Ka": // ambient reflectivity color texture case "map_kA": if (!string.IsNullOrEmpty(parameters)) { Debug.Log("Map not supported:" + line); } break; default: Debug.Log("this line was not processed :" + line); break; } } catch (Exception e) { Debug.LogErrorFormat("Error at line {0} in mtl file: {1}", i + 1, e); } } }
/// <summary> /// Build a Unity Material from MaterialData /// </summary> /// <param name="md">material data</param> /// <returns>Unity material</returns> private Material BuildMaterial(MaterialData md) { string shaderName = "Mobile/Diffuse"; // (md.illumType == 2) ? "Standard (Specular setup)" : "Standard"; bool specularMode = false; // (md.specularTex != null); ModelUtil.MtlBlendMode mode = md.overallAlpha < 1.0f ? ModelUtil.MtlBlendMode.TRANSPARENT : ModelUtil.MtlBlendMode.OPAQUE; bool useUnlit = buildOptions != null && buildOptions.litDiffuse && md.diffuseTex != null && md.bumpTex == null && md.opacityTex == null && md.specularTex == null && !md.hasReflectionTex; bool?diffuseIsTransparent = null; if (useUnlit) {// do not use unlit shader if the texture has transparent pixels diffuseIsTransparent = ModelUtil.ScanTransparentPixels(md.diffuseTex, ref mode); } if (useUnlit && diffuseIsTransparent.Value) { shaderName = "Mobile/Diffuse"; } else if (specularMode) { shaderName = "Mobile/Diffuse"; } Material newMaterial = new Material(Shader.Find(shaderName)); // "Standard (Specular setup)" newMaterial.name = md.materialName; float shinLog = Mathf.Log(md.shininess, 2); // get the metallic value from the shininess float metallic = Mathf.Clamp01(shinLog / 10.0f); // get the smoothness from the shininess float smoothness = Mathf.Clamp01(shinLog / 10.0f); if (specularMode) { newMaterial.SetColor("_SpecColor", md.specularColor); newMaterial.SetFloat("_Shininess", md.shininess / 1000.0f); //m.color = new Color( md.diffuse.r, md.diffuse.g, md.diffuse.b, md.alpha); } else { newMaterial.SetFloat("_Metallic", metallic); //m.SetFloat( "_Glossiness", md.shininess ); } if (md.diffuseTex != null) { // diffuse if (md.opacityTex != null) { // diffuse + opacity: // update diffuse texture if an opacity map was found int w = md.diffuseTex.width; int h = md.diffuseTex.width; Texture2D albedoTexture = new Texture2D(w, h, TextureFormat.ARGB32, false); Color col = new Color(); for (int x = 0; x < albedoTexture.width; x++) { for (int y = 0; y < albedoTexture.height; y++) { col = md.diffuseTex.GetPixel(x, y); col.a *= md.opacityTex.GetPixel(x, y).grayscale; // blend diffuse and opacity textures albedoTexture.SetPixel(x, y, col); } } albedoTexture.name = md.diffuseTexPath; albedoTexture.Apply(); // mode = ModelUtil.MtlBlendMode.TRANSPARENT; // The map_d value is multiplied by the d value --> Fade mode mode = ModelUtil.MtlBlendMode.FADE; #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.opacityTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref albedoTexture, texAssetPath, "_alpha"); } } #endif newMaterial.SetTexture("_MainTex", albedoTexture); } else {// md.opacityTex == null // diffuse without opacity: if there are transparent pixels ==> transparent material if (!diffuseIsTransparent.HasValue) { diffuseIsTransparent = ModelUtil.ScanTransparentPixels(md.diffuseTex, ref mode); } newMaterial.SetTexture("_MainTex", md.diffuseTex); } //Debug.LogFormat("Diffuse set for {0}",m.name); } else if (md.opacityTex != null) { // opacity without diffuse //mode = ModelUtil.MtlBlendMode.TRANSPARENT; mode = ModelUtil.MtlBlendMode.FADE; int w = md.opacityTex.width; int h = md.opacityTex.width; Texture2D albedoTexture = new Texture2D(w, h, TextureFormat.ARGB32, false); Color col = new Color(); bool detected = false; for (int x = 0; x < albedoTexture.width; x++) { for (int y = 0; y < albedoTexture.height; y++) { col = md.diffuseColor; col.a = md.overallAlpha * md.opacityTex.GetPixel(x, y).grayscale; ModelUtil.DetectMtlBlendFadeOrCutout(col.a, ref mode, ref detected); //if (md.alpha == 1.0f && col.a == 0.0f) mode = ModelUtil.MtlBlendMode.CUTOUT; albedoTexture.SetPixel(x, y, col); } } albedoTexture.name = md.diffuseTexPath; albedoTexture.Apply(); #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.opacityTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref albedoTexture, texAssetPath, "_op"); } } #endif newMaterial.SetTexture("_MainTex", albedoTexture); } md.diffuseColor.a = md.overallAlpha; newMaterial.SetColor("_Color", md.diffuseColor); md.emissiveColor.a = md.overallAlpha; newMaterial.SetColor("_EmissionColor", md.emissiveColor); if (md.emissiveColor.r > 0 || md.emissiveColor.g > 0 || md.emissiveColor.b > 0) { newMaterial.EnableKeyword("_EMISSION"); } if (md.bumpTex != null) { // bump map defined // TODO: if importing assets do not create a nomal map, change importer settings // let (improperly) assign a normal map to the bumb map // if the file name contains a specific tag // TODO: customize normal map tag if (md.bumpTexPath.Contains("_normal_map")) { newMaterial.EnableKeyword("_NORMALMAP"); newMaterial.SetFloat("_BumpScale", 0.25f); // lower the bump effect with the normal map newMaterial.SetTexture("_BumpMap", md.bumpTex); } else { // calculate normal map Texture2D normalMap = ModelUtil.HeightToNormalMap(md.bumpTex); #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.bumpTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref normalMap, texAssetPath, "_nm", true); } } else #endif { newMaterial.SetTexture("_BumpMap", normalMap); //newMaterial.SetTexture("_BumpMap", md.bumpTex); newMaterial.EnableKeyword("_NORMALMAP"); newMaterial.SetFloat("_BumpScale", 1.0f); // adjust the bump effect with the normal map } } } if (md.specularTex != null) { Texture2D glossTexture = new Texture2D(md.specularTex.width, md.specularTex.height, TextureFormat.ARGB32, false); Color col = new Color(); float pix = 0.0f; for (int x = 0; x < glossTexture.width; x++) { for (int y = 0; y < glossTexture.height; y++) { pix = md.specularTex.GetPixel(x, y).grayscale; // red = metallic col.r = metallic * pix;// md.specular.grayscale*pix; col.g = col.r; col.b = col.r; // alpha = smoothness // if reflecting set maximum smoothness value, else use a precomputed value if (md.hasReflectionTex) { col.a = pix; } else { col.a = pix * smoothness; } glossTexture.SetPixel(x, y, col); } } glossTexture.Apply(); #if UNITY_EDITOR if (!string.IsNullOrEmpty(alternativeTexPath)) { string texAssetPath = AssetDatabase.GetAssetPath(md.specularTex); if (!string.IsNullOrEmpty(texAssetPath)) { EditorUtil.SaveAndReimportPngTexture(ref glossTexture, texAssetPath, "_spec"); } } #endif if (specularMode) { newMaterial.EnableKeyword("_SPECGLOSSMAP"); newMaterial.SetTexture("_SpecGlossMap", glossTexture); } else { newMaterial.EnableKeyword("_METALLICGLOSSMAP"); newMaterial.SetTexture("_MetallicGlossMap", glossTexture); } //m.SetTexture( "_MetallicGlossMap", md.specularLevelTex ); } // replace the texture with Unity environment reflection if (md.hasReflectionTex) { if (md.overallAlpha < 1.0f) { Color col = Color.white; col.a = md.overallAlpha; newMaterial.SetColor("_Color", col); mode = ModelUtil.MtlBlendMode.FADE; } // the "amount of" info is missing, using a default value if (md.specularTex != null) { newMaterial.SetFloat("_Metallic", metallic);// 1.0f); } // usually the reflection texture is not blurred newMaterial.SetFloat("_Glossiness", 1.0f); } ModelUtil.SetupMaterialWithBlendMode(newMaterial, mode); //newMaterial.SetColor("_Color", Color.white); //newMaterial.SetFloat("_Cutoff", 0.900000f); //#if UNITY_EDITOR // if (!string.IsNullOrEmpty(alternateTexPath)) // { // string path = alternateTexPath + "../Materials/" + m.name + ".mat"; // path = path.Replace("Textures/../", ""); // Debug.LogFormat("Creating material asset in {0}", path); // AssetDatabase.CreateAsset(m, path); // m = AssetDatabase.LoadAssetAtPath<Material>(path); // } //#endif return(newMaterial); }