You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过脚本将.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并合成视频,无需前端参与:

  1. 服务器安装Blender
  2. 编写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)
  1. 后端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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:57:05