// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors. // All rights reserved. // Code licensed under the BSD License. // http://www.anki3d.org/LICENSE #include #include #include #include #include #include namespace anki { inline constexpr const Char* kMaterialTemplate = R"( %diff% %spec% %roughnessMetalness% %normal% %emission% %subsurface% )"; static ImporterString getTextureUri(const cgltf_texture_view& view) { ANKI_ASSERT(view.texture); ANKI_ASSERT(view.texture->image); ANKI_ASSERT(view.texture->image->uri); ImporterString out = view.texture->image->uri; out.replaceAll("%20", " "); return out; } /// Read the texture and find out if it has constant color. If a component is not constant return -1 static Error findConstantColorsInImage(CString fname, Vec4& constantColor) { ImageLoader iloader(&ImporterMemoryPool::getSingleton()); ANKI_CHECK(iloader.load(fname)); ANKI_ASSERT(iloader.getColorFormat() == ImageBinaryColorFormat::kRgba8); ANKI_ASSERT(iloader.getCompression() == ImageBinaryDataCompression::kRaw); const U8Vec4* data = reinterpret_cast(&iloader.getSurface(0, 0, 0).m_data[0]); ConstWeakArray pixels(data, iloader.getWidth() * iloader.getHeight()); const F32 epsilon = 1.0f / 255.0f; for(U32 y = 0; y < iloader.getHeight(); ++y) { for(U32 x = 0; x < iloader.getWidth(); ++x) { const U8Vec4& pixel = pixels[y * iloader.getWidth() + x]; const Vec4 pixelf = Vec4(pixel) / 255.0f; if(x == 0 && y == 0) { // Initialize constantColor = pixelf; } else { for(U32 i = 0; i < 4; ++i) { if(absolute(pixelf[i] - constantColor[i]) > epsilon) { constantColor[i] = -1.0f; } } } } } return Error::kNone; } static Error importImage(CString in, CString out, Bool alpha) { ImageImporterConfig config; Array inputFnames = {in}; config.m_inputFilenames = inputFnames; config.m_outFilename = out; config.m_compressions = ImageBinaryDataCompression::kS3tc | ImageBinaryDataCompression::kAstc; config.m_minMipmapDimension = 8; config.m_noAlpha = !alpha; String tmp; if(getTempDirectory(tmp)) { ANKI_IMPORTER_LOGE("getTempDirectory() failed"); return 1; } config.m_tempDirectory = tmp; #if ANKI_OS_WINDOWS config.m_compressonatorFilename = ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Windows64/Compressonator/compressonatorcli.exe"; config.m_astcencFilename = ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Windows64/astcenc-avx2.exe"; #elif ANKI_OS_LINUX config.m_compressonatorFilename = ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Linux64/Compressonator/compressonatorcli"; config.m_astcencFilename = ANKI_SOURCE_DIRECTORY "/ThirdParty/Bin/Linux64/astcenc-avx2"; #else # error "Unupported" #endif config.m_flipImage = false; ANKI_IMPORTER_LOGV("Importing image \"%s\" as \"%s\"", in.cstr(), out.cstr()); ANKI_CHECK(importImage(config)); return Error::kNone; } static void fixImageUri(ImporterString& uri) { uri.replaceAll(".tga", ".ankitex"); uri.replaceAll(".png", ".ankitex"); uri.replaceAll(".jpg", ".ankitex"); uri.replaceAll(".jpeg", ".ankitex"); } Error GltfImporter::writeMaterial(const cgltf_material& mtl, Bool writeRayTracing) const { const Error err = writeMaterialInternal(mtl, writeRayTracing); if(err) { ANKI_IMPORTER_LOGE("Failed to import material: %s", mtl.name); } return err; } Error GltfImporter::writeMaterialInternal(const cgltf_material& mtl, Bool writeRayTracing) const { if(!writeRayTracing) { ANKI_IMPORTER_LOGW("Skipping ray tracing is ignored"); } ImporterString fname; fname.sprintf("%s%s", m_outDir.cstr(), computeMaterialResourceFilename(mtl).cstr()); ANKI_IMPORTER_LOGV("Importing material %s", fname.cstr()); if(!mtl.has_pbr_metallic_roughness) { ANKI_IMPORTER_LOGE("Expecting PBR metallic roughness"); return Error::kUserData; } ImporterHashMap extras; ANKI_CHECK(appendExtras(mtl.extras, extras)); ImporterString xml; xml += XmlDocument>::kXmlHeader; xml += "\n"; xml += kMaterialTemplate; // Diffuse if(mtl.pbr_metallic_roughness.base_color_texture.texture) { const ImporterString fname = getTextureUri(mtl.pbr_metallic_roughness.base_color_texture); ImporterString uri; uri.sprintf("%s%s", m_texrpath.cstr(), fname.cstr()); const F32* diffCol = &mtl.pbr_metallic_roughness.base_color_factor[0]; xml.replaceAll("%diff%", ImporterString().sprintf("\n" "\t\t", uri.cstr(), diffCol[0], diffCol[1], diffCol[2], diffCol[3])); xml.replaceAll("%diffTexMutator%", "1"); Vec4 constantColor; ANKI_CHECK(findConstantColorsInImage(fname, constantColor)); const Bool constantAlpha = constantColor.w >= 0.0f; xml.replaceAll("%alphaTestMutator%", (constantAlpha) ? "0" : "1"); if(m_importTextures) { ImporterString out = m_outDir; out += fname; fixImageUri(out); ANKI_CHECK(importImage(fname, out, !constantAlpha)); } } else { const F32* diffCol = &mtl.pbr_metallic_roughness.base_color_factor[0]; xml.replaceAll("%diff%", ImporterString().sprintf("", diffCol[0], diffCol[1], diffCol[2], diffCol[3])); xml.replaceAll("%diffTexMutator%", "0"); xml.replaceAll("%alphaTestMutator%", "0"); } // Specular color (freshnel) { Vec3 specular; auto it = extras.find("specular"); if(it != extras.getEnd()) { ImporterStringList tokens; tokens.splitString(it->toCString(), ' '); if(tokens.getSize() != 3) { ANKI_IMPORTER_LOGE("Wrong specular: %s", it->cstr()); return Error::kUserData; } 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%", ImporterString().sprintf("", specular.x, specular.y, specular.z)); xml.replaceAll("%specTexMutator%", "0"); } // Shadow { Bool shadow = true; auto it = extras.find("shadow"); if(it != extras.getEnd()) { const ImporterString val = *it; if(val == "false" || val == "0") { shadow = false; } else if(val == "true" || val == "1") { shadow = true; } else { ANKI_IMPORTER_LOGE("Wrong value for extra named shadow: %s", val.cstr()); return Error::kUserData; } } xml.replaceAll("%shadowEnabled%", (shadow) ? "1" : "0"); } // Identify metallic/roughness texture F32 constantMetaliness = -1.0f, constantRoughness = -1.0f; if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture) { const ImporterString fname = getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture); Vec4 constantColor; ANKI_CHECK(findConstantColorsInImage(fname, constantColor)); constantRoughness = constantColor.y; constantMetaliness = constantColor.z; } // Roughness/metallic if(mtl.pbr_metallic_roughness.metallic_roughness_texture.texture && (constantRoughness < 0.0f || constantMetaliness < 0.0f)) { ImporterString uri; uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture).cstr()); xml.replaceAll("%roughnessMetalness%", ImporterString().sprintf("\n" "\t\t\n" "\t\t", uri.cstr(), mtl.pbr_metallic_roughness.roughness_factor, mtl.pbr_metallic_roughness.metallic_factor)); xml.replaceAll("%roughnessMetalnessTexMutator%", "1"); if(m_importTextures) { const ImporterString in = getTextureUri(mtl.pbr_metallic_roughness.metallic_roughness_texture); ImporterString out = m_outDir; out += in; fixImageUri(out); ANKI_CHECK(importImage(in, out, false)); } } else { // No texture or both roughness and metalness are constant const F32 roughness = (constantRoughness >= 0.0f) ? constantRoughness * mtl.pbr_metallic_roughness.roughness_factor : mtl.pbr_metallic_roughness.roughness_factor; const F32 metalness = (constantMetaliness >= 0.0f) ? constantMetaliness * mtl.pbr_metallic_roughness.metallic_factor : mtl.pbr_metallic_roughness.metallic_factor; xml.replaceAll("%roughnessMetalness%", ImporterString().sprintf("\n" "\t\t", roughness, metalness)); xml.replaceAll("%roughnessMetalnessTexMutator%", "0"); } // Normal texture if(mtl.normal_texture.texture) { Vec4 constantColor; ANKI_CHECK(findConstantColorsInImage(getTextureUri(mtl.normal_texture).cstr(), constantColor)); if(constantColor.xyz == -1.0f) { ImporterString uri; uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.normal_texture).cstr()); xml.replaceAll("%normal%", ImporterString().sprintf("", uri.cstr())); xml.replaceAll("%normalTexMutator%", "1"); if(m_importTextures) { const ImporterString in = getTextureUri(mtl.normal_texture); ImporterString out = m_outDir; out += in; fixImageUri(out); ANKI_CHECK(importImage(in, out, false)); } } else { xml.replaceAll("%normal%", ""); xml.replaceAll("%normalTexMutator%", "0"); } } else { xml.replaceAll("%normal%", ""); xml.replaceAll("%normalTexMutator%", "0"); } // Emissive texture if(mtl.emissive_texture.texture) { ImporterString uri; uri.sprintf("%s%s", m_texrpath.cstr(), getTextureUri(mtl.emissive_texture).cstr()); xml.replaceAll("%emission%", ImporterString().sprintf("\n" "\t\t", uri.cstr(), mtl.emissive_strength.emissive_strength, mtl.emissive_strength.emissive_strength, mtl.emissive_strength.emissive_strength)); xml.replaceAll("%emissiveTexMutator%", "1"); if(m_importTextures) { const ImporterString in = getTextureUri(mtl.emissive_texture); ImporterString out = m_outDir; out += in; fixImageUri(out); ANKI_CHECK(importImage(in, out, false)); } } else { xml.replaceAll("%emission%", ImporterString().sprintf("", mtl.emissive_factor[0] * mtl.emissive_strength.emissive_strength, mtl.emissive_factor[1] * mtl.emissive_strength.emissive_strength, mtl.emissive_factor[2] * mtl.emissive_strength.emissive_strength)); 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%", ImporterString().sprintf("", subsurface)); } // Replace texture extensions with .anki fixImageUri(xml); // Write file File file; ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::kWrite)); ANKI_CHECK(file.writeText(xml)); return Error::kNone; } } // end namespace anki