134 lines
5.5 KiB
TypeScript
134 lines
5.5 KiB
TypeScript
import JSZip from "jszip";
|
|
import * as THREE from "three";
|
|
import Loader from "@/core/loader/Loader.ts";
|
|
import App from "@/core/app/App.ts";
|
|
|
|
/**
|
|
* 解析材质zip包
|
|
*/
|
|
export function parseMaterialZip(zipFile: File): Promise<THREE.MeshStandardMaterial> {
|
|
return new Promise(async (resolve, reject) => {
|
|
const zip = new JSZip();
|
|
|
|
const zipContent = await zip.loadAsync(zipFile);
|
|
|
|
// 强制检查根目录下是否存在material.json
|
|
let materialJson: any = zipContent.file('material.json');
|
|
if (!materialJson) {
|
|
materialJson = {
|
|
textures: {},
|
|
properties: {}
|
|
}
|
|
|
|
// 获取所有文件路径列表(JSZip存储结构为 {路径: 文件对象})
|
|
const filePaths = Object.keys(zipContent.files);
|
|
for (let i = 0; i < filePaths.length; i++) {
|
|
const relativePath: string = filePaths[i];
|
|
const file = zipContent.file(relativePath);
|
|
|
|
if (!file) continue;
|
|
|
|
// 判断是否为文件(JSZip中目录路径以 '/' 结尾)
|
|
if (file.dir || relativePath.endsWith('/')) continue;
|
|
|
|
if (relativePath.includes("baseColor")) {
|
|
materialJson.textures.baseColor = relativePath;
|
|
} else if (relativePath.includes("normal")) {
|
|
materialJson.textures.normal = relativePath;
|
|
} else if (relativePath.includes("bump")) {
|
|
materialJson.textures.bump = relativePath;
|
|
} else if (relativePath.includes("displacement")) {
|
|
materialJson.textures.displacement = relativePath;
|
|
} else if (relativePath.includes("emissive")) {
|
|
materialJson.textures.emissive = relativePath;
|
|
} else if (relativePath.includes("alpha")) {
|
|
materialJson.textures.alpha = relativePath;
|
|
} else if (relativePath.includes("env")) {
|
|
materialJson.textures.env = relativePath;
|
|
} else if (relativePath.includes("light")) {
|
|
materialJson.textures.light = relativePath;
|
|
} else {
|
|
// arm可能在一张图上各占一个通道
|
|
if (relativePath.includes("roughness")) {
|
|
materialJson.textures.roughness = relativePath;
|
|
}
|
|
if (relativePath.includes("metalness")) {
|
|
materialJson.textures.metalness = relativePath;
|
|
}
|
|
if (relativePath.includes("ao")) {
|
|
materialJson.textures.ao = relativePath;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
materialJson = JSON.parse(await materialJson.async('text'));
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
|
|
// 并行加载所有纹理
|
|
const texturePromises = Object.entries(materialJson.textures).map(
|
|
async ([type, path]:any) => {
|
|
const textureFile = zipContent.file(path);
|
|
if (!textureFile) {
|
|
console.warn(`Texture file not found: ${path}`);
|
|
return { type, texture: null };
|
|
}
|
|
|
|
const extension = path.toString().split(".").pop()?.toLowerCase() || "jpg";
|
|
let textureBlob: Blob;
|
|
|
|
try {
|
|
// 特殊处理 EXR 格式
|
|
if (extension === "exr") {
|
|
const buffer = await textureFile.async("arraybuffer");
|
|
textureBlob = new Blob([buffer], { type: "image/x-exr" });
|
|
} else {
|
|
textureBlob = await textureFile.async("blob");
|
|
}
|
|
} catch (err) {
|
|
console.error(`Failed to load texture (${type}):`, err);
|
|
return { type, texture: null };
|
|
}
|
|
|
|
const textureUrl = URL.createObjectURL(textureBlob);
|
|
|
|
return new Promise<{ type: string; texture: THREE.Texture | null }>(
|
|
(resolve) => {
|
|
Loader.loadUrlTexture(
|
|
extension,
|
|
textureUrl,
|
|
(texture: THREE.Texture) => {
|
|
URL.revokeObjectURL(textureUrl); // 清理资源
|
|
resolve({ type, texture });
|
|
},
|
|
(error: Error) => {
|
|
URL.revokeObjectURL(textureUrl);
|
|
console.error(`Texture load error (${type}):`, error);
|
|
resolve({ type, texture: null });
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
|
|
// 等待所有纹理加载完成
|
|
const textureResults = await Promise.all(texturePromises);
|
|
const textures = textureResults.reduce((acc, { type, texture }) => {
|
|
if (texture) acc[type] = texture;
|
|
return acc;
|
|
}, {} as Record<string, THREE.Texture>);
|
|
|
|
// 处理无有效纹理的情况
|
|
if (Object.keys(textures).length === 0) {
|
|
throw new Error("No valid textures found in the zip file");
|
|
}
|
|
|
|
const material = await App.createPBRMaterial(textures, materialJson.properties || {});
|
|
|
|
resolve(material);
|
|
})
|
|
} |