如何在服务器端向3D文件添加文本
Linux后端REST API中给OBJ模型添加文本并保存的实现方案
为什么Three.js在后端无法生效
Three.js依赖浏览器的WebGL渲染环境和DOM API,Linux服务器端没有这些运行环境,直接调用会因缺少相关API而报错,因此需要采用无头渲染方案或使用不依赖浏览器的3D模型处理库。
方案一:Node.js(Three.js + 无头浏览器)
通过无头浏览器模拟浏览器环境,运行Three.js代码完成模型修改,是最贴近前端开发习惯的方案。
- 依赖安装:
npm install three puppeteer - 核心实现逻辑:
- 启动无头Chrome实例,加载包含Three.js及相关加载器的页面。
- 在页面环境中加载OBJ模型、生成文本几何体,调整位置后合并到场景。
- 导出合并后的OBJ数据,保存到服务器本地。
- 示例代码:
const puppeteer = require('puppeteer'); const fs = require('fs'); const path = require('path'); async function addTextToObj(inputObjPath, customText, outputObjPath) { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] // Linux环境需要的参数 }); const page = await browser.newPage(); // 注入Three.js及所需加载器 await page.setContent(` <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/FontLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/OBJExporter.js"></script> <script> window.processModel = async (objData, text) => { const scene = new THREE.Scene(); const loader = new THREE.OBJLoader(); const model = loader.parse(objData); // 加载字体生成文本网格 const fontLoader = new THREE.FontLoader(); const font = await fontLoader.loadAsync('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/fonts/helvetiker_regular.typeface.json'); const textGeo = new THREE.TextGeometry(text, { font: font, size: 0.5, height: 0.1, curveSegments: 12 }); const textMesh = new THREE.Mesh(textGeo); textMesh.position.set(0, 2, 0); // 调整文本位置到模型上方 scene.add(model); scene.add(textMesh); // 导出OBJ const exporter = new THREE.OBJExporter(); return exporter.parse(scene); }; </script> `); // 读取本地OBJ文件内容 const objContent = fs.readFileSync(inputObjPath, 'utf8'); // 在浏览器环境中执行模型处理 const resultObj = await page.evaluate((data, text) => window.processModel(data, text), objContent, customText); // 保存结果 fs.writeFileSync(outputObjPath, resultObj); await browser.close(); } // API接口中可直接调用此方法 // addTextToObj('./models/input.obj', 'My Custom Text', './models/output.obj'); - 注意事项:
- 字体文件建议下载到本地,避免依赖外部CDN;
- Linux服务器需安装无头Chrome依赖(如
libnss3、libxss1等,puppeteer安装时会提示); - 需根据原模型尺寸调整文本的大小和位置,避免错位。
方案二:.NET Core(C#)
使用AssimpNet处理3D模型,结合字体库生成文本网格,适合.NET技术栈的项目。
- NuGet包安装:
Install-Package AssimpNet Install-Package SixLabors.Fonts - 核心实现逻辑:
- 用AssimpNet加载OBJ模型;
- 用SixLabors.Fonts将文本转换为矢量路径,再将路径拉伸为3D网格;
- 将文本网格合并到原模型场景,导出为新OBJ。
- 示例代码片段:
using Assimp; using SixLabors.Fonts; using SixLabors.Shapes; public static void AddTextToModel(string inputPath, string text, string outputPath) { // 加载原OBJ模型 var importer = new AssimpContext(); var scene = importer.ImportFile(inputPath, PostProcessSteps.Triangulate | PostProcessSteps.FlipUVs); // 生成文本矢量路径 var font = SystemFonts.Get("Arial"); var glyphs = font.CreateGlyphs(text, new RendererOptions(font, 24)); var path = new PathBuilder().AddGlyphs(glyphs).Build(); // 将2D路径转为3D拉伸网格 var textMesh = CreateExtrudedMesh(path, 5f); textMesh.Name = "CustomText"; // 添加到模型场景 scene.Meshes.Add(textMesh); // 导出新OBJ importer.ExportFile(scene, outputPath, FileFormat.Obj); } // 辅助方法:将2D路径拉伸为3D网格 private static Mesh CreateExtrudedMesh(IPath path, float thickness) { var mesh = new Mesh(); // 此处需实现路径顶点的生成逻辑,包括上下表面和侧面的三角面 // 可参考AssimpNet的Mesh结构,手动填充Vertices、Faces等属性 return mesh; } - 注意事项:
- AssimpNet默认使用左手坐标系,若原模型是右手系(如Three.js导出的),需添加
PostProcessSteps.FlipWindingOrder处理; - 文本转3D网格的逻辑需要自己实现,或寻找第三方辅助库简化开发。
- AssimpNet默认使用左手坐标系,若原模型是右手系(如Three.js导出的),需添加
方案三:C++
基于Assimp和FreeType实现,性能最优,适合高并发、高性能需求的场景。
- 依赖安装(Ubuntu/Debian):
sudo apt-get install libassimp-dev libfreetype6-dev - 核心实现逻辑:
- 用Assimp加载OBJ模型;
- 用FreeType解析字体,生成文本的字形轮廓;
- 将2D轮廓拉伸为3D网格,合并到原模型;
- 导出新的OBJ文件。
- 示例代码片段:
#include <assimp/Importer.hpp> #include <assimp/scene.h> #include <assimp/postprocess.h> #include <assimp/Exporter.hpp> #include <ft2build.h> #include FT_FREETYPE_H #include <vector> #include <string> aiMesh* CreateTextMesh(FT_Face face, const std::string& text, float thickness) { aiMesh* mesh = new aiMesh(); // 遍历每个字符,解析字形轮廓,生成3D网格的顶点和三角面 // 需处理轮廓的每个点,生成上下表面和侧面的顶点数据 return mesh; } void AddTextToObj(const std::string& inputPath, const std::string& text, const std::string& outputPath) { // 加载原模型 Assimp::Importer importer; const aiScene* scene = importer.ReadFile(inputPath, aiProcess_Triangulate); // 初始化FreeType FT_Library ft; FT_Init_FreeType(&ft); FT_Face face; // 使用系统字体,路径需根据Linux环境调整 FT_New_Face(ft, "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 0, &face); FT_Set_Pixel_Sizes(face, 0, 48); // 生成文本网格 aiMesh* textMesh = CreateTextMesh(face, text, 2.0f); // 合并到原场景 aiScene* editableScene = const_cast<aiScene*>(scene); editableScene->mMeshes = reinterpret_cast<aiMesh**>(realloc(editableScene->mMeshes, sizeof(aiMesh*) * (editableScene->mNumMeshes + 1))); editableScene->mMeshes[editableScene->mNumMeshes++] = textMesh; // 导出OBJ Assimp::Exporter exporter; exporter.Export(editableScene, "obj", outputPath); // 清理资源 FT_Done_Face(face); FT_Done_FreeType(ft); } - 注意事项:
- 需手动管理内存,避免内存泄漏;
- FreeType的字形轮廓处理需要细致实现,确保网格拓扑正确;
- 可封装为REST API(如用FastCGI或ASP.NET Core托管C++服务)。
内容的提问来源于stack exchange,提问作者cansado2930
相关产品推荐
相关产品推荐

