如何通过脚本将.fbx文件转为帧图像并生成视频?
FBX转视频的正确实现方案
核心问题分析
你之前尝试将FBX转OBJ后用FFmpeg转视频的思路是错误的——OBJ是纯3D几何数据文件,FFmpeg没有渲染3D模型的能力,根本无法直接将OBJ转为视频。你的初始思路(先转帧图像再用FFmpeg合成视频)是正确的,核心是要渲染FBX模型生成序列帧,而非转OBJ格式。
可行实现路径
基于你之前用three.js的尝试,推荐两种方案:浏览器端渲染帧+后端合成,或后端用专业工具批量处理。
方案一:浏览器端three.js渲染帧 + 后端FFmpeg合成
前端代码(生成序列帧)
替换你之前的转OBJ逻辑,改为渲染FBX模型并生成连续帧图片:
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/js/loaders/FBXLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/examples/js/controls/OrbitControls.js"></script> <canvas id="renderCanvas" style="display:none;"></canvas> <input type="file" id="fileInput" accept=".fbx"> <button onclick="startRender()">开始渲染</button> <script> let scene, camera, renderer, controls, model; let frameCount = 0; const totalFrames = 120; // 生成120帧(对应4秒@30fps) const frameImages = []; // 初始化three.js场景 function initScene() { scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff); camera = new THREE.PerspectiveCamera(75, 1920/1080, 0.1, 1000); camera.position.set(5, 5, 5); renderer = new THREE.WebGLRenderer({canvas: document.getElementById('renderCanvas')}); renderer.setSize(1920, 1080); // 添加灯光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 5); scene.add(directionalLight); controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; } // 加载FBX模型 function loadFBXModel(file) { const loader = new THREE.FBXLoader(); const url = URL.createObjectURL(file); loader.load(url, (object) => { model = object; // 自动调整模型位置和大小 const box = new THREE.Box3().setFromObject(model); const center = box.getCenter(new THREE.Vector3()); model.position.sub(center); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); model.scale.multiplyScalar(5 / maxDim); scene.add(model); startRenderingFrames(); }); } // 逐帧渲染并保存图片 function startRenderingFrames() { if (frameCount >= totalFrames) { sendFramesToBackend(); return; } // 让模型旋转(模拟动画) if (model) { model.rotation.y += 0.05; } renderer.render(scene, camera); // 将canvas转为base64图片 const imgData = renderer.domElement.toDataURL('image/png'); frameImages.push(imgData); frameCount++; requestAnimationFrame(startRenderingFrames); } // 将帧图片传给后端 function sendFramesToBackend() { const formData = new FormData(); frameImages.forEach((img, index) => { // 将base64转为blob const blob = dataURItoBlob(img); formData.append(`frame${index}`, blob, `frame${index}.png`); }); const xhr = new XMLHttpRequest(); xhr.open('POST', 'convert.php', true); xhr.onload = function() { if (xhr.status === 200) { console.log('视频合成完成:', xhr.responseText); alert('视频合成完成'); } else { console.error('合成失败:', xhr.responseText); alert('合成失败'); } }; xhr.send(formData); } // 辅助函数:base64转blob function dataURItoBlob(dataURI) { const byteString = atob(dataURI.split(',')[1]); const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ab], {type: mimeString}); } // 入口函数 function startRender() { const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; if (!file) { alert('请选择FBX文件'); return; } initScene(); loadFBXModel(file); } </script>
后端PHP代码(合成视频)
替换之前的PHP逻辑,改为接收序列帧并合成视频:
<?php if ($_SERVER["REQUEST_METHOD"] == "POST") { // 创建临时目录存储帧图片 $tempDir = './temp_frames/'; if (!file_exists($tempDir)) { mkdir($tempDir, 0777, true); } // 保存上传的帧图片 $frameCount = 0; foreach ($_FILES as $fileKey => $file) { if ($file['error'] === UPLOAD_ERR_OK) { $framePath = $tempDir . sprintf('frame%04d.png', $frameCount); move_uploaded_file($file['tmp_name'], $framePath); $frameCount++; } } if ($frameCount === 0) { echo '没有接收到帧图片'; exit; } // FFmpeg合成视频:30fps,输出mp4 $outputVideo = './output.mp4'; $ffmpegCmd = "ffmpeg -r 30 -i {$tempDir}frame%04d.png -c:v libx264 -crf 23 -pix_fmt yuv420p {$outputVideo}"; exec($ffmpegCmd, $output, $returnCode); // 清理临时帧文件 array_map('unlink', glob($tempDir . '*.png')); rmdir($tempDir); if ($returnCode === 0) { echo "视频已生成:{$outputVideo}"; } else { echo '视频合成失败,请检查FFmpeg是否正确安装'; var_dump($output); } } else { echo '无效请求'; } ?>
方案二:后端用Blender命令行处理(更高效)
如果服务器可以安装Blender,直接用专业3D工具渲染FBX并合成视频,无需前端参与:
- 服务器安装Blender
- 编写Blender渲染脚本(
render_fbx.py):
import bpy import sys # 清除默认场景 bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete() # 加载FBX文件 fbx_path = sys.argv[1] bpy.ops.import_scene.fbx(filepath=fbx_path) # 设置渲染参数 scene = bpy.context.scene scene.render.image_settings.file_format = 'PNG' scene.render.filepath = './temp_frames/frame' scene.render.resolution_x = 1920 scene.render.resolution_y = 1080 scene.frame_start = 1 scene.frame_end = 120 # 渲染120帧 # 添加灯光 light = bpy.data.lights.new(name="Light", type='SUN') light_obj = bpy.data.objects.new(name="Light", object_data=light) bpy.context.collection.objects.link(light_obj) light_obj.location = (10, 10, 5) # 设置相机 camera = bpy.data.cameras.new(name="Camera") camera_obj = bpy.data.objects.new(name="Camera", object_data=camera) bpy.context.collection.objects.link(camera_obj) bpy.context.scene.camera = camera_obj camera_obj.location = (5, 5, 5) camera_obj.rotation_euler = (1.1, 0, 0.785) # 渲染序列帧 bpy.ops.render.render(animation=True)
- 后端PHP调用Blender和FFmpeg:
<?php if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_FILES["fbxFile"])) { $fbxFile = $_FILES["fbxFile"]; $fbxPath = $fbxFile["tmp_name"]; $tempDir = './temp_frames/'; if (!file_exists($tempDir)) { mkdir($tempDir, 0777, true); } // 调用Blender渲染帧 $blenderCmd = "blender -b -P render_fbx.py -- {$fbxPath}"; exec($blenderCmd, $blenderOutput, $blenderReturn); if ($blenderReturn !== 0) { echo 'Blender渲染帧失败'; var_dump($blenderOutput); exit; } // FFmpeg合成视频 $outputVideo = './output.mp4'; $ffmpegCmd = "ffmpeg -r 30 -i {$tempDir}frame%04d.png -c:v libx264 -crf 23 -pix_fmt yuv420p {$outputVideo}"; exec($ffmpegCmd, $ffmpegOutput, $ffmpegReturn); // 清理临时文件 array_map('unlink', glob($tempDir . '*.png')); rmdir($tempDir); if ($ffmpegReturn === 0) { echo "视频已生成:{$outputVideo}"; } else { echo 'FFmpeg合成失败'; var_dump($ffmpegOutput); } } else { echo '请上传FBX文件'; } ?>
注意事项
- 确保服务器已安装FFmpeg,若不在系统PATH中需写全执行路径
- 浏览器端方案受限于用户设备性能,大模型可能卡顿;后端Blender方案更稳定高效
- 可根据需求调整帧率、总帧数、分辨率等参数
内容的提问来源于stack exchange,提问作者Ittefaq Technologies
相关产品推荐
相关产品推荐

