// Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors. // All rights reserved. // Code licensed under the BSD License. // http://www.anki3d.org/LICENSE #include #include namespace anki { const char* MATERIAL_TEMPLATE = R"( %parallaxInput% %diff% %spec% %roughness% %metallic% %normal% %emission% %subsurface% %height% )"; const char* RT_MATERIAL_TEMPLATE = R"( )"; static CString getTextureUri(const cgltf_texture_view& view) { ANKI_ASSERT(view.texture); ANKI_ASSERT(view.texture->image); ANKI_ASSERT(view.texture->image->uri); return view.texture->image->uri; } /// Read the texture and find out if static Error identifyMetallicRoughnessTexture(CString fname, F32& constantMetalines, F32& constantRoughness, GenericMemoryPoolAllocator& alloc) { ImageLoader iloader(alloc); ANKI_CHECK(iloader.load(fname)); ANKI_ASSERT(iloader.getColorFormat() == ImageBinaryColorFormat::RGBA8); ANKI_ASSERT(iloader.getCompression() == ImageBinaryDataCompression::RAW); const U8Vec4* data = reinterpret_cast(&iloader.getSurface(0, 0, 0).m_data[0]); const F32 epsilon = 1.0f / 255.0f; for(U32 y = 0; y < iloader.getWidth(); ++y) { for(U32 x = 0; x < iloader.getHeight(); ++x) { const U8Vec4& pixel = *(data + y * iloader.getWidth() + x); const F32 m = F32(pixel.z()) / 255.0f; const F32 r = F32(pixel.y()) / 255.0f; if(x == 0 && y == 0) { // Initialize constantMetalines = m; constantRoughness = r; } else { if(constantMetalines < 0.0f || absolute(m - constantMetalines) > epsilon) { constantMetalines = -1.0f; } if(constantRoughness < 0.0f || absolute(r - constantRoughness) > epsilon) { constantRoughness = -1.0f; } } } } return Error::NONE; } Error GltfImporter::writeMaterial(const cgltf_material& mtl, RayTypeBit usedRayTypes) { StringAuto fname(m_alloc); fname.sprintf("%s%s.ankimtl", m_outDir.cstr(), mtl.name); ANKI_IMPORTER_LOGI("Importing material %s", fname.cstr()); if(!mtl.has_pbr_metallic_roughness) { ANKI_IMPORTER_LOGE("Expecting PBR metallic roughness"); return Error::USER_DATA; } HashMapAuto extras(m_alloc); ANKI_CHECK(getExtras(mtl.extras, extras)); StringAuto xml(m_alloc); xml.append(XML_HEADER); xml.append("\n"); xml.append(MATERIAL_TEMPLATE); if(!!(usedRayTypes & RayTypeBit::SHADOWS)) { xml.append(RT_MATERIAL_TEMPLATE); } // Diffuse if(mtl.pbr_metallic_roughness.base_color_texture.texture) { StringAuto uri(m_alloc); uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.pbr_metallic_roughness.base_color_texture).cstr()); xml.replaceAll("%diff%", StringAuto{m_alloc}.sprintf("", uri.cstr())); xml.replaceAll("%diffTexMutator%", "1"); } else { const F32* diffCol = &mtl.pbr_metallic_roughness.base_color_factor[0]; xml.replaceAll("%diff%", StringAuto{m_alloc}.sprintf("", diffCol[0], diffCol[1], diffCol[2])); xml.replaceAll("%diffTexMutator%", "0"); } // Specular color (freshnel) { Vec3 specular; auto it = extras.find("specular"); if(it != extras.getEnd()) { StringListAuto tokens(m_alloc); tokens.splitString(it->toCString(), ' '); if(tokens.getSize() != 3) { ANKI_IMPORTER_LOGE("Wrong specular: %s", it->cstr()); return Error::USER_DATA; } auto token = tokens.getBegin(); ANKI_CHECK(token->toNumber(specular.x())); ++token; ANKI_CHECK(token->toNumber(specular.y())); ++token; ANKI_CHECK(token->toNumber(specular.z())); } else { specular = Vec3(0.04f); } xml.replaceAll("%spec%", StringAuto{m_alloc}.sprintf("", specular.x(), specular.y(), specular.z())); xml.replaceAll("%specTexMutator%", "0"); } // Identify metallic/roughness texture F32 constantMetaliness = -1.0f, constantRoughness = -1.0f; if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture) { const CString fname = getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture); ANKI_CHECK(identifyMetallicRoughnessTexture(fname, constantMetaliness, constantRoughness, m_alloc)); } // Roughness if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture && constantRoughness < 0.0f) { StringAuto uri(m_alloc); uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture).cstr()); xml.replaceAll("%roughness%", StringAuto{m_alloc}.sprintf("", uri.cstr())); xml.replaceAll("%roughnessTexMutator%", "1"); } else { const F32 roughness = (constantRoughness >= 0.0f) ? constantRoughness * mtl.pbr_metallic_roughness.roughness_factor : mtl.pbr_metallic_roughness.roughness_factor; xml.replaceAll("%roughness%", StringAuto{m_alloc}.sprintf("", roughness)); xml.replaceAll("%roughnessTexMutator%", "0"); } // Metallic if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture && constantMetaliness < 0.0f) { StringAuto uri(m_alloc); uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture).cstr()); xml.replaceAll("%metallic%", StringAuto{m_alloc}.sprintf("", uri.cstr())); xml.replaceAll("%metalTexMutator%", "1"); } else { const F32 metalines = (constantMetaliness >= 0.0f) ? constantMetaliness * mtl.pbr_metallic_roughness.metallic_factor : mtl.pbr_metallic_roughness.metallic_factor; xml.replaceAll("%metallic%", StringAuto{m_alloc}.sprintf("", metalines)); xml.replaceAll("%metalTexMutator%", "0"); } // Normal texture if(mtl.normal_texture.texture) { StringAuto uri(m_alloc); uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.normal_texture).cstr()); xml.replaceAll("%normal%", StringAuto{m_alloc}.sprintf("", uri.cstr())); xml.replaceAll("%normalTexMutator%", "1"); } else { xml.replaceAll("%normal%", ""); xml.replaceAll("%normalTexMutator%", "0"); } // Emissive texture if(mtl.emissive_texture.texture) { StringAuto uri(m_alloc); uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.emissive_texture).cstr()); xml.replaceAll("%emission%", StringAuto{m_alloc}.sprintf("", uri.cstr())); xml.replaceAll("%emissiveTexMutator%", "1"); } else { const F32* emissionCol = &mtl.emissive_factor[0]; xml.replaceAll("%emission%", StringAuto{m_alloc}.sprintf("", emissionCol[0], emissionCol[1], emissionCol[2])); xml.replaceAll("%emissiveTexMutator%", "0"); } // Subsurface { F32 subsurface; auto it = extras.find("subsurface"); if(it != extras.getEnd()) { ANKI_CHECK(it->toNumber(subsurface)); } else { subsurface = 0.0f; } xml.replaceAll("%subsurface%", StringAuto{m_alloc}.sprintf("", subsurface)); } // Height texture auto it = extras.find("height_map"); if(it != extras.getEnd()) { StringAuto uri(m_alloc); uri.sprintf("%s%s", m_texrpath.cstr(), it->cstr()); xml.replaceAll("%height%", StringAuto{m_alloc}.sprintf("\n" "\t\t", uri.cstr())); xml.replaceAll("%parallaxMutator%", "1"); } else { xml.replaceAll("%height%", ""); xml.replaceAll("%parallaxInput%", ""); xml.replaceAll("%parallaxMutator%", "0"); } // Replace texture extensions with .anki xml.replaceAll(".tga", ".ankitex"); xml.replaceAll(".png", ".ankitex"); xml.replaceAll(".jpg", ".ankitex"); xml.replaceAll(".jpeg", ".ankitex"); // Write file File file; ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::WRITE)); ANKI_CHECK(file.writeText("%s", xml.cstr())); return Error::NONE; } } // end namespace anki