最近碰到一个需求是需要更换glb的内部纹理图然后生成一个新的glb文件,采用了 gltf-pipeline 插件,之前本来以为能够像 jq 一样,内部引用一下就可以,后来看了文档之后发现需要使用安装 node.js 在node.js内部才能够 require 当前的依赖,同样需要选择图片来进行更换,所以用node.js 生成了一个上传图片的接口
具体步骤
上传图片
首先安装node.js 这一步就不复述了,网上教程很多
上传图片HTML
<div class="word">
<input type="file" id="file" style="display: none" onchange="handleChange()" />
<label class="buttons" for="file">上传图片</label>
<!-- 这里展示上传文件的名称 -->
<div id="filetext"></div>
<input type="button" class="buttons inp" value="修改GLB" disabled="disabled" onclick="uploadFile()" />
<a href="javascript:return false;" class="buttons down" id="urls" >下载GLB</a>
</div>
上传图片 JavaScript
//导入express框架
const express = require("express");
const multer = require("multer"); //用express的第三方中间件 multer 实现文件上传功能。
const path = require("path");
const app = express();
//解决跨域问题
const cors = require("cors");
// 中间件 获取参数的
const bodyParser = require("body-parser");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
// 请求头设置
app.all("*", function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OP0TIONS");
res.header("X-Powered-By", "3.2.1");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
const storage = multer.diskStorage({
destination: function (req, file, cb) {
// 图片保存到本地,路径是upload
cb(null, path.resolve("upload"));
},
filename: function (req, file, cb) {
// 图片重命名,因为上传的文件名肯定是不一致的,但是glb的纹理图名称固定
cb(null, "Wolf3D_Avatar_Diffuse" + path.extname(file.originalname));
},
});
// 图片直接保存在内存中
// const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
// 这里设置上传接口
app.post("/upload", upload.single("file"), function (req, res, next) {
res.status(200);
res.json({
success: '请求成功';
})
}
// 这一步设置接口为3000
const server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
});
这样上传图片就基本完成了,接下来是上传图片之后的更换glb纹理图。这里使用了 gltf-pipeline。
更换纹理图
app.post("/upload", upload.single("file"), function (req, res, next) {
const gltfPipeline = require("gltf-pipeline");
const fsExtra = require("fs-extra");
const glbToGltf = gltfPipeline.glbToGltf;
const gltfToGlb = gltfPipeline.gltfToGlb;
const mergeBuffers = gltfPipeline.mergeBuffers;
const processGltf = gltfPipeline.processGltf;
const glb = fsExtra.readFileSync("body.glb");
// 上传的图片生成文件流用于替换
const pngs = fsExtra.readFileSync("upload/Wolf3D_Avatar_Diffuse.png");
// 这里是图片保存在内存中,假如图片没有过大,保存在内存中应该是最优解
// pngs = req.file.buffer;
// 生成 model 文件夹,用于存放纹理图
fsExtra.ensureDir("model");
// 这一步表示需要将纹理图单独进行拆分出来
const options = {
separateTextures: true,
};
// 这一步需要注意,因为我们将glb的纹理拆分出来之后会生成一个gltf文件和纹理图文件,所以需要注明资源所在的位置
const glboptions = {
resourceDirectory: "model",
};
glbToGltf(glb).then(function (glbResults) {
processGltf(glbResults.gltf, options).then(function (results) {
// Save separate resources
const separateResources = results.separateResources;
for (const relativePath in separateResources) {
if (separateResources.hasOwnProperty(relativePath)) {
const resource = separateResources[relativePath];
// 这一步是判断需要替换的纹理图是否是我们需要的那张,因为glb文件一般会有多张图
if (relativePath === "Wolf3D_Avatar_Diffuse.png") {
fsExtra.writeFileSync(`model/${relativePath}`, pngs);
} else {
fsExtra.writeFileSync(`model/${relativePath}`, resource);
}
}
}
const gltf = fsExtra.readJsonSync("model/model.gltf");
// 这一步加时间主要是防止假如同时多个人操作时名称冲突的情况
const date = Math.floor(Date.now());
// 这里直接用上面拆分出来的gltf文件就可以了,也就是results.gltf
gltfToGlb(results.gltf, glboptions).then(function (results) {
fsExtra.writeFileSync(`upload/model${date}.glb`, results.glb);
// 删除 model 文件夹
fsExtra.remove("model");
});
res.status(200);
// json格式
res.send({
url: `http://172.17.28.141:5500/%E7%9F%AD%E6%9C%9F%E9%A1%B9%E7%9B%AE/glb/upload/model${date}.glb`,
});
//传入页面
// res.send();
});
});
});
这样来说一个更换glb纹理之后生成新glb的页面就基本完成了
之后再是在下载的时候处理按钮点击的问题,因为之前防止未上传图片就直接点击,做了禁止点击的效果
解决按钮点击问题
// 这一段我为了简单,直接放在 html 内部了
function handleChange() {
document.getElementById("filetext").innerHTML =
document.getElementById("file").files[0].name;
let inp = document.getElementsByClassName("inp")[0];
inp.removeAttribute("disabled");
inp.style.cursor = "pointer";
inp.style.background = "#00bfff";
inp.style.color = "#fff";
}
function uploadFile() {
let xhr = new XMLHttpRequest();
let formData = new FormData();
formData.append("file", document.getElementById("file").files[0]);
//设置请求的类型及url
xhr.open("post", "http://127.0.0.1:3000/upload");
//发送请求
xhr.send(formData);
xhr.onreadystatechange = function () {
// 这步为判断服务器是否正确响应
if (xhr.readyState == 4 && xhr.status == 200) {
const data = JSON.parse(xhr.responseText);
document.getElementById("urls").href = data.url;
document.getElementById("urls").style.cursor = "pointer";
document.getElementById("urls").style.background = "#00bfff";
document.getElementById("urls").style.color = "#fff";
}
};
}
这样来说应该是基本完成了,其实假如能够直接在内存中使用图片的流会更好,但是因为 gltf-pipeline 并没有提供这个的支持,我查看了一下他的源码,感觉修改起来过于麻烦,所以并没有针对这一步进行优化,假如有大佬有更好的方案也可以留言
感谢!!!