GLB更换纹理图,生成新的GLB

1,279 阅读3分钟

最近碰到一个需求是需要更换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 并没有提供这个的支持,我查看了一下他的源码,感觉修改起来过于麻烦,所以并没有针对这一步进行优化,假如有大佬有更好的方案也可以留言

感谢!!!