在 Three.js 中更换 GLTF 模型的贴图需要了解模型的结构和材质系统。以下是详细的方法和示例:
javascript
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
let model;
loader.load('model.gltf', (gltf) => {
model = gltf.scene;
scene.add(model);
// 加载后更换贴图
replaceTextures(model);
});
javascript
function replaceAllTextures(model, newTexture) {
model.traverse((child) => {
if (child.isMesh) {
// 检查材质类型
if (child.material) {
replaceMeshTextures(child, newTexture);
}
}
});
}
function replaceMeshTextures(mesh, newTexture) {
const material = mesh.material;
// 处理 MeshStandardMaterial 或 MeshPhysicalMaterial
if (material.isMeshStandardMaterial || material.isMeshPhysicalMaterial) {
// 更换漫反射贴图
if (material.map) {
material.map = newTexture;
material.needsUpdate = true;
}
// 或者直接设置新贴图(覆盖原有贴图)
material.map = newTexture;
material.needsUpdate = true;
}
}
javascript
// 加载新贴图
const textureLoader = new THREE.TextureLoader();
const newDiffuseMap = textureLoader.load('new_diffuse.jpg');
const newNormalMap = textureLoader.load('new_normal.jpg');
const newRoughnessMap = textureLoader.load('new_roughness.jpg');
function replaceSpecificTextures(model) {
model.traverse((child) => {
if (child.isMesh && child.material) {
const material = child.material;
// 更换不同类型贴图
if (material.map) {
// 漫反射贴图
material.map = newDiffuseMap;
}
if (material.normalMap) {
// 法线贴图
material.normalMap = newNormalMap;
material.normalScale.set(1, 1); // 设置法线贴图强度
}
if (material.roughnessMap) {
// 粗糙度贴图
material.roughnessMap = newRoughnessMap;
}
if (material.metalnessMap) {
// 金属度贴图
material.metalnessMap = textureLoader.load('new_metalness.jpg');
}
if (material.aoMap) {
// 环境光遮蔽贴图
material.aoMap = textureLoader.load('new_ao.jpg');
}
if (material.emissiveMap) {
// 自发光贴图
material.emissiveMap = textureLoader.load('new_emissive.jpg');
material.emissive.set(0xffffff); // 设置自发光颜色
}
material.needsUpdate = true;
}
});
}
javascript
function replaceTexturesByName(model) {
model.traverse((child) => {
if (child.isMesh) {
const material = child.material;
// 检查材质名称
if (material.name) {
switch (material.name.toLowerCase()) {
case 'body_material':
material.map = textureLoader.load('body_diffuse.jpg');
material.normalMap = textureLoader.load('body_normal.jpg');
break;
case 'glass_material':
// 更换透明材质
material.map = textureLoader.load('glass_texture.png');
material.transparent = true;
material.opacity = 0.8;
break;
case 'metal_material':
material.map = textureLoader.load('metal_diffuse.jpg');
material.metalness = 0.8;
material.roughness = 0.2;
break;
case 'plastic_material':
material.map = textureLoader.load('plastic_diffuse.jpg');
material.roughness = 0.7;
material.metalness = 0.1;
break;
}
material.needsUpdate = true;
}
}
});
}
javascript
function createNewPBRMaterial() {
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 0.5,
roughness: 0.5,
});
// 加载所有贴图
const maps = {
map: textureLoader.load('albedo.jpg'),
normalMap: textureLoader.load('normal.jpg'),
roughnessMap: textureLoader.load('roughness.jpg'),
metalnessMap: textureLoader.load('metalness.jpg'),
aoMap: textureLoader.load('ao.jpg'),
emissiveMap: textureLoader.load('emissive.jpg'),
alphaMap: textureLoader.load('alpha.png'), // 透明贴图
displacementMap: textureLoader.load('height.png'), // 高度贴图
};
// 设置贴图参数
Object.assign(material, maps);
// 设置贴图缩放和偏移
material.map.repeat.set(2, 2);
material.map.wrapS = THREE.RepeatWrapping;
material.map.wrapT = THREE.RepeatWrapping;
// 设置法线贴图强度
material.normalScale.set(1, 1);
// 设置置换贴图参数
material.displacementScale = 0.1;
material.displacementBias = -0.05;
return material;
}
function replaceAllMaterials(model) {
model.traverse((child) => {
if (child.isMesh) {
// 保存原始材质的某些属性(如果需要)
const oldMaterial = child.material;
const newMaterial = createNewPBRMaterial();
// 如果需要继承某些属性
newMaterial.side = oldMaterial.side;
newMaterial.transparent = oldMaterial.transparent;
newMaterial.opacity = oldMaterial.opacity;
child.material = newMaterial;
}
});
}
javascript
class TextureManager {
constructor(model) {
this.model = model;
this.originalTextures = new Map();
this.textureCache = new Map();
this.currentTextureSet = 'default';
// 保存原始贴图
this.saveOriginalTextures();
}
saveOriginalTextures() {
this.model.traverse((child) => {
if (child.isMesh && child.material) {
const material = child.material;
const textures = {
map: material.map,
normalMap: material.normalMap,
roughnessMap: material.roughnessMap,
// 保存其他贴图...
};
this.originalTextures.set(child.uuid, textures);
}
});
}
async loadTextureSet(setName) {
if (this.textureCache.has(setName)) {
this.applyTextureSet(setName);
return;
}
// 异步加载贴图
const textures = await this.loadTextures(setName);
this.textureCache.set(setName, textures);
this.applyTextureSet(setName);
}
async loadTextures(setName) {
const basePath = `textures/${setName}/`;
return {
diffuse: await this.loadTexture(`${basePath}diffuse.jpg`),
normal: await this.loadTexture(`${basePath}normal.jpg`),
roughness: await this.loadTexture(`${basePath}roughness.jpg`),
metalness: await this.loadTexture(`${basePath}metalness.jpg`),
};
}
loadTexture(url) {
return new Promise((resolve) => {
textureLoader.load(url, resolve);
});
}
applyTextureSet(setName) {
const textures = this.textureCache.get(setName);
this.model.traverse((child) => {
if (child.isMesh && child.material) {
const material = child.material;
// 根据材质名称或类型应用不同的贴图
if (material.name && material.name.includes('metal')) {
material.map = textures.metalness;
} else {
material.map = textures.diffuse;
material.normalMap = textures.normal;
material.roughnessMap = textures.roughness;
}
material.needsUpdate = true;
}
});
this.currentTextureSet = setName;
}
resetToOriginal() {
this.model.traverse((child) => {
if (child.isMesh && this.originalTextures.has(child.uuid)) {
const original = this.originalTextures.get(child.uuid);
Object.assign(child.material, original);
child.material.needsUpdate = true;
}
});
}
}
// 使用示例
let textureManager;
loader.load('model.gltf', (gltf) => {
const model = gltf.scene;
scene.add(model);
textureManager = new TextureManager(model);
// 切换贴图集
document.getElementById('texture1').addEventListener('click', () => {
textureManager.loadTextureSet('wood');
});
document.getElementById('texture2').addEventListener('click', () => {
textureManager.loadTextureSet('metal');
});
document.getElementById('reset').addEventListener('click', () => {
textureManager.resetToOriginal();
});
});
javascript
// 1. 使用 LoadingManager 管理加载
const loadingManager = new THREE.LoadingManager();
loadingManager.onProgress = (url, loaded, total) => {
console.log(`Loading: ${loaded}/${total} - ${url}`);
};
const textureLoader = new THREE.TextureLoader(loadingManager);
// 2. 压缩贴图
function loadCompressedTexture(url) {
return new Promise((resolve, reject) => {
const loader = new THREE.CompressedTextureLoader();
loader.load(url, resolve, undefined, reject);
});
}
// 3. 缓存已加载的贴图
const textureCache = {};
function getCachedTexture(url) {
if (!textureCache[url]) {
textureCache[url] = textureLoader.load(url);
}
return textureCache[url];
}
// 4. 释放不需要的贴图
function disposeUnusedTextures() {
Object.keys(textureCache).forEach(key => {
if (!isTextureInUse(textureCache[key])) {
textureCache[key].dispose();
delete textureCache[key];
}
});
}
javascript
function fixTextureIssues(material) {
// 1. 检查贴图是否加载
if (material.map && material.map.image) {
console.log('贴图已加载:', material.map.image.src);
}
// 2. 检查 UV 坐标
if (material.map) {
material.map.needsUpdate = true;
}
// 3. 检查材质是否需要更新
material.needsUpdate = true;
// 4. 检查纹理过滤和包裹方式
if (material.map) {
material.map.minFilter = THREE.LinearFilter;
material.map.magFilter = THREE.LinearFilter;
material.map.wrapS = THREE.RepeatWrapping;
material.map.wrapT = THREE.RepeatWrapping;
}
// 5. 检查透明材质设置
if (material.transparent) {
material.alphaTest = 0.5;
material.depthWrite = false;
}
}
// 应用修复
model.traverse((child) => {
if (child.isMesh && child.material) {
fixTextureIssues(child.material);
}
});
javascript
// GLTF 模型可能有多个材质
function handleMultiMaterial(mesh) {
if (Array.isArray(mesh.material)) {
// 多个材质(如 MultiMaterial)
mesh.material.forEach((material, index) => {
replaceMaterialTextures(material, index);
});
} else {
// 单个材质
replaceMaterialTextures(mesh.material);
}
}
function replaceMaterialTextures(material, materialIndex = 0) {
// 根据材质索引使用不同的贴图
const texturePaths = [
'textures/material_0/diffuse.jpg',
'textures/material_1/diffuse.jpg',
// ...
];
if (texturePaths[materialIndex]) {
material.map = textureLoader.load(texturePaths[materialIndex]);
material.needsUpdate = true;
}
}
更换 GLTF 模型贴图的关键步骤:
正确加载模型:使用 GLTFLoader
遍历模型结构:使用 traverse() 方法
识别材质类型:检查材质属性
更换贴图:根据需要更换特定贴图
更新材质:设置 material.needsUpdate = true
优化性能:缓存贴图,管理内存
通过以上方法,你可以灵活地控制和更换 GLTF 模型的任何贴图。