如何借助AI在UE5中将图片批量生成3D模型

126 阅读11分钟

前言

年底了把今年搞得东西整理了下,其中25年2月份搞个比较有意思的东西,是关于虚幻引擎里接入 AI 生成 3D 模型的一个方案,这个可玩性还是非常高的不光是在游戏里、在建筑上电影制作或是在3D打印上都可应用。

26年我也打算搞个 3d 打印机玩玩,把生活中的一些有意思的图片做成3D模型,希望今年年底能写一篇关于 3D 打印的文章 🐎

先看效果

本来想插入个视频,但是由于西瓜视频和抖音合并了,掘金又校验得是西瓜视频的链接,所以导入不了,没办法只能先放个链接了:视频效果

一些思考

虽然前端很少接触 UE 但是在 AI 横行的时代,没有什么技术是跨工种的,AI 能辅助我们快速的了解掌握新技术,而且还会有额外的收获,实践中我也确实发现了 UE 中的一个开发模式和前端的低代码思想高度一致,包括和当下的 AI 工作流也是高度一致,虽然之前没接触过虚幻引擎,但是近一年一直在用 AI 工作流,所以到了 UE 里也就驾轻就熟了。

虚幻引擎介绍

虚幻引擎5(Unreal Engine 5,简称UE5)是由 Epic Games 开发的一款强大的游戏开发引擎,广泛应用于游戏开发、影视制作、建筑可视化等多个领域。

img_v3_02tv_6c1b9c60-bb1a-45ec-9f26-87057972fb1g.jpg

Tripo 介绍

Tripo 是一款由清华大学VAST团队开发的人工智能驱动3D建模工具,它能在几秒到一分钟内,将你的图片、手绘草图或文字描述转化为高质量的3D模型

image.png

背景及需求

随着 AI 能力的发展与成熟,应公司业务需求,游戏开发及素材制作一直使用虚幻引擎,想接入 AI 能力,各部门都进行了大规模的 AI 场景探索,其中素材制作相关部门有想法在 UE5 中接入 AI,想快速将图片转成3D模型,并且无跳出的在 UE 编辑器内使用,完成所有工作。

这里需要借助一个非常重要的多模态大模型来实现核心的图片转3D功能,就是Tripo,那为什么不直接在人家官网上去上传图片然后生成模型用呢?有下面几个问题

  1. 通过官网网页的方式有并发的限制,当用户访问量高的时候,生成一个模型需要很长很长的时间,而且不支持批量生成的,只能一个图或者一组图片去生成一个模型。
  2. 生成后的模型在官网上需要手动下载到本地,然后再手动导入到 UE 中,这个过程也很耗费时间,而且需要 UE 和 Tripo 官网来回跳转切换,对于素材制作或者游戏制作的同学来说相当难受

所以我们要做的就是如何将 Tripo 接入到 UE 中,从选择图片开始到模型生成后导入都在编辑器里执行,并且等待的同时不影响用户操作编辑器干别的事

前期决策

刚开始我给的建议是比较保守的,因为我坚信不久 UE IDE 官方或者 Tripo 官方一定会接入这种能力或者让开发者能比较方便的调用三方模型或者 MCP 来实现对应 AI 能力,但是确实得等,而且人家到底做不做以及何时做,作为用户是完全不知道的,这也就比较被动,由于从产能上讲确实能节省将近90%制作模型的时间,所以最后还是决定自己动手丰衣足食!

事实确实是一年过去了,到目前为止 UE 团队和 Tripo 团队确实也还没动静😑

调研阶段

我第一个方案定的是用蓝图脚本(也就是工作流的方式),因为这种是 UE 里最常见的模式,但尝试了很久耗费了我大量的精力但是由于节点不丰富和流程过于复杂最终放弃了;

第二个方案想的是开发一个UE的插件,但UE插件开发大量使用c++,至于c++早就还给老师了~即使借助AI,编译调试过程也极其痛苦,所以最后也放弃了;

当我陷入绝望的时候突然发现了 UE 中有一个执行脚本的功能,于是我萌生了一个邪修的想法,利用 python 实现核心流程然后在 UE 里触发执行脚本不就行了,不得不说 python 真香,2天就搞定了最头疼的部分,至于这个脚本怎么写怎么用下面会详细讲👇

所以最终方案: 环境配置+python脚本+三方大模型(tripo)接入+ UE中集成脚本

开发阶段

这里三个方案下面我都会介绍下,但是重点聊 python 的方案,也就是第三种方式,如果对前两个方案不感兴趣可直接跳到 phthon方案👇

蓝图脚本方案

蓝图(Blueprints)  是虚幻引擎中强大的可视化脚本系统,它让开发者无需编写传统代码,即可通过节点连线的方式构建游戏逻辑、交互系统和内容原型。

蓝图这部分功能我是实现了的,但是开发和接入都比较麻烦就不展开说了,如果有小伙伴感兴趣可以评论区留言我到时候再补充,我放了一部分节点截图,参考如下

img_v3_02tf_767826e2-df3e-4988-82e8-a5a3000062cg.jpg img_v3_02tf_bec71dbb-0ab2-4d5f-9bda-46d9f0d3067g.jpg img_v3_02tf_0a6ad00e-8321-46c8-89c3-e700a7a05bfg.jpg img_v3_02tf_125c99e2-f42e-4738-a3df-467735c8ee9g.jpg img_v3_02tf_e3309068-72d2-4e0c-b5a3-98b841dc543g.jpg img_v3_02tf_694e4dfa-402a-4e1d-9293-faa57106e31g.jpg

蓝图方式接入 AI 是有一些教学视频的比如B站上,基本上都是基于蓝图接入简单的大模型比如chatGpt,进行简单对话,大概思路就是利用蓝图设置请求参数然后运行时环境模拟发起请求,并且利用UI蓝图将结果显示在页面上,B站有很多视频,照着几个小时就能实现,我前期也是参考了这个思路所以才走偏了!!!

所以当需要进行多次网络请求、双向通信、本地资源上传等场景的时候那么蓝图节点非常复杂,而且有很多节点没有,并不适用

C++方案

C++ 是 UE 的基石与骨骼,承担着构建高性能、可扩展、可维护的底层框架和核心系统的重任。它与蓝图形成“双轨开发”模式,各司其职。

简单说就是蓝图和C++两种方式可以并行开发,蓝图是可视化的,C++是纯代码,但是我们的这个需求实际上就是蓝图实现不了的节点可以用c++自定义出蓝图节点或者直接写个完整的插件,

这部分同理不展开介绍了,如果有小伙伴感兴趣可以评论区留言我到时候再补充,同理放张截图可供参考

img_v3_02tt_1e3c8f6f-4019-4f57-9c8b-85eed2288f4g.jpg

核心逻辑在source里实现即可,比如自己写一个蓝图节点用,或者写一个插件发布,让其他用户可安装等场景

python 方案

duang duang duang!!!邪修方案来了

为方便理解整个流程,序列图和流程图如下:

sequenceDiagram
    participant Client as 本地客户端
    participant Upload as 上传API<br/>/v2/openapi/upload
    participant Task as 任务API<br/>/v2/openapi/task
    participant WS as WebSocket<br/>/v2/openapi/task/watch/all
    participant Convert as 格式转换API<br/>/v2/openapi/task
    participant UE as UE系统

    Note over Client,WS: 初始化阶段
    Client->>WS: 建立 WebSocket 连接
    WS-->>Client: 连接成功

    Note over Client,Task: 图片上传与生成
    Client->>Client: 从本地上传多个图片
    Client->>Upload: POST 上传图片
    Upload-->>Client: 返回 token
    Client->>Task: POST 调用图片生成模型 API<br/>(使用 token)
    Task-->>Client: 返回任务已创建

    Note over Client,WS: 任务监听
    loop 监听 WebSocket 消息
        WS->>Client: 推送任务状态
        alt event=finalized && type=image_to_model
            Client->>Client: 任务完成,获取 task_id
        else 其他事件
            Client->>Client: 继续监听
        end
    end

    Note over Client,Convert: 格式转换
    Client->>Convert: POST 调用格式转换 API<br/>(GLB→FBX,使用 task_id)
    Convert-->>Client: 返回转换任务已创建

    Note over Client,WS: 转换任务监听
    loop 继续监听 WebSocket 消息
        WS->>Client: 推送任务状态
        alt event=finalized && type=convert_model
            Client->>Client: 转换完成,获取模型 URL
        else 其他事件
            Client->>Client: 继续监听
        end
    end

    Note over Client,UE: UE 导入
    Client->>Client: 将模型 URL 转换为 chunk
    Client->>UE: 写入 UE 项目目录
    UE->>UE: 检测到文件导入
    UE->>Client: 弹出导入弹窗
    alt 用户点击导入
        Client->>UE: 确认导入
        UE->>UE: 导入模型
    else 用户取消
        Note over Client: 取消导入
    end
flowchart TD
    Start([开始]) --> Upload[从本地上传多个图片]
    Upload --> UploadAPI[调用 Tripo 上传图片接口<br/>POST /v2/openapi/upload]
    UploadAPI --> GetToken[获取返回的 token]
    GetToken --> GenTask[使用 token 调用图片生成模型 API<br/>POST /v2/openapi/task]
    GenTask --> CreateTask[生成图片转模型任务]
    
    Start --> WSConnect[建立 WebSocket 连接<br/>wss://api.tripo3d.ai/v2/openapi/task/watch/all]
    WSConnect --> Listen[监听 WebSocket 消息]
    
    CreateTask --> Listen
    Listen --> CheckEvent{检查事件类型}
    
    CheckEvent -->|event=finalized<br/>type=image_to_model| TaskComplete[图片转模型任务完成<br/>获取 task_id]
    CheckEvent -->|其他事件| Listen
    
    TaskComplete --> ConvertAPI[调用格式转换 API<br/>POST /v2/openapi/task<br/>将 GLB 转换为 FBX]
    ConvertAPI --> CreateConvertTask[生成格式转换任务]
    CreateConvertTask --> Listen
    
    CheckEvent -->|event=finalized<br/>type=convert_model| ConvertComplete[格式转换任务完成<br/>获取模型 URL]
    CheckEvent -->|其他事件| Listen
    
    ConvertComplete --> Download[将模型 URL 转换为 chunk]
    Download --> WriteFile[写入 UE 项目对应目录]
    WriteFile --> UEDetect[UE 检测到文件导入]
    UEDetect --> ShowDialog[弹出导入弹窗<br/>询问用户是否导入或配置]
    ShowDialog --> UserAction{用户操作}
    UserAction -->|点击导入| ImportModel[导入模型到 UE]
    UserAction -->|取消| End([结束])
    ImportModel --> End
    
    style Start fill:#4CAF50,stroke:#2E7D32,stroke-width:3px,color:#fff
    style End fill:#4CAF50,stroke:#2E7D32,stroke-width:3px,color:#fff
    style Upload fill:#E3F2FD,stroke:#1976D2,stroke-width:2px,color:#000
    style UploadAPI fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff
    style GetToken fill:#E3F2FD,stroke:#1976D2,stroke-width:2px,color:#000
    style GenTask fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff
    style CreateTask fill:#E3F2FD,stroke:#1976D2,stroke-width:2px,color:#000
    style WSConnect fill:#F3E5F5,stroke:#7B1FA2,stroke-width:2px,color:#000
    style Listen fill:#F3E5F5,stroke:#7B1FA2,stroke-width:2px,color:#000
    style CheckEvent fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
    style TaskComplete fill:#FFF9C4,stroke:#F57F17,stroke-width:2px,color:#000
    style ConvertAPI fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff
    style CreateConvertTask fill:#E3F2FD,stroke:#1976D2,stroke-width:2px,color:#000
    style ConvertComplete fill:#FFF9C4,stroke:#F57F17,stroke-width:2px,color:#000
    style Download fill:#E8F5E9,stroke:#388E3C,stroke-width:2px,color:#000
    style WriteFile fill:#E8F5E9,stroke:#388E3C,stroke-width:2px,color:#000
    style UEDetect fill:#E8F5E9,stroke:#388E3C,stroke-width:2px,color:#000
    style ShowDialog fill:#E8F5E9,stroke:#388E3C,stroke-width:2px,color:#000
    style UserAction fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
    style ImportModel fill:#C8E6C9,stroke:#2E7D32,stroke-width:2px,color:#000

1.准备环境

windows 为例,Mac 基本流程一样

  1. 首先需要电脑上有python环境,这个自行安装即可,并安装 python 脚本里的依赖包
  • 打开windows自带的powershell
  • 找到虚幻引擎下的 python 启动文件如:D:\ourpalm\UE_5.4\Engine\Binaries\ThirdParty\Python3\Win64\python.exe
  • 安装依赖 requests:D:\ourpalm\UE_5.4\Engine\Binaries\ThirdParty\Python3\Win64\python.exe -m pip install requests
  • 安装依赖 websocket-client: D:\ourpalm\UE_5.4\Engine\Binaries\ThirdParty\Python3\Win64\python.exe -m pip install websocket-client

踩坑提示:如果目录路径里带空格(Program Files)则需要添加& 和引号如下: & "D:\Program Files\Epic Games\UE_5.4\Engine\Binaries\ThirdParty\Python3\Win64\python.exe" -m pip install requests

  1. 注册 Tripo 账号&生成 API key(密钥)注册 生成密钥

获取的的 key 替换掉下面脚本里的 api_key 即可

  • 安装 ca 证书(Mac里不用做这一步,windows 复制一份到指定路径就行)
# D:\ourpalm\UE_5.4\Engine\Binaries\ThirdParty\Python3\Win64\python.exe
# import ssl
# print(ssl.get_default_verify_paths())
#直接将 cert.pem 文件复制拷贝一份到指定路径如:
C:\Program Files\Common Files\SSL\certs\cert.pem
C:\Program Files\Common Files\SSL\cert.pem

这个是为了当你使用UE内置的Python或任何通过Python联网的工具(如插件管理器、包管理器pip)访问HTTPS网址时,Python的ssl模块需要使用一个包含全球各大可信CA公钥的列表(cert.pem)来验证网站证书的真实性,否则就会抛出SSL验证错误。

环境总结:基本上都是为了保证 python 环境和安装的包能正常运行,如果之前配置过环境这一步可省略

2.核心脚本

图片上传&转 3D 脚本(脚本1)
import tkinter as tk
from tkinter import filedialog
import requests
import json
import threading

/*平台生成的api_key*/
api_key = "tsk_-xxxxxxxxxxxxxx-FsgO-cbSxO7egfFPH"
upload_url = "https://api.tripo3d.ai/v2/openapi/upload"
image_to_model_url = "https://api.tripo3d.ai/v2/openapi/task"

json_headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {api_key}"
}
headers = {"Authorization": f"Bearer {api_key}"}

def upload_file(filepath):
    with open(filepath, 'rb') as f:
        files = {'file': (filepath, f, 'image/jpeg')}
        response = requests.post(upload_url, headers=headers, files=files)
        response_json = response.json()
        print(response_json)
        token = response_json.get("data").get("image_token")
        if token:
            image_to_model(token)

def image_to_model(token):
    image_to_model_data = {
        "type": "image_to_model",
        "file": {
            "type": "jpg",
            "file_token": token
        }
    }
    response = requests.post(image_to_model_url, headers=json_headers, json=image_to_model_data)
    print(response.json())

def process_files(filepaths):
    for filepath in filepaths:
        # 创建线程
        thread = threading.Thread(target=upload_file, args=(filepath,))
        thread.start()  # 启动线程

def main():
    tk.Tk().withdraw()  # 隐藏主窗口
    filepaths = filedialog.askopenfilenames()  # 弹出文件选择对话框
    print(filepaths)
    
    # 处理文件
    process_files(filepaths)

if __name__ == "__main__":
    main()

    
任务监听&类型转换&写入编辑器脚本源代码
      
import websocket
import requests
import json
import threading

api_key = "tsk_-xxxxxxxxx-FsgO-cbSxO7egfFPH"
convert_glb_to_fbx_url = "https://api.tripo3d.ai/v2/openapi/task"
save_path = "D:\\ourpalm\\myProject\\测试python脚本\\Content\\"

json_headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {api_key}"
}

def on_message(ws, message):
    try:
        data = json.loads(message)
        task_id = data['data']['task_id']
        status = data['data']['status']
        
        print(f"Received message for task {task_id} with status {status}")
        
        # 根据需要调用其他接口
        if status == 'success':  
            result = call_other_api(task_id)
            print(f"Result from other API for task {task_id}: {result}")
            print(data)
            
            # event==='finalized'并且type==='image_to_model'调用convert转换
            if(data['event'] == 'finalized' and data['data']['type'] == 'image_to_model'):
                glb_to_fbx_data = {
                    "type": "convert_model",
                    "format":"FBX",
                    "original_model_task_id":data['data']['task_id']
                }
                glb_to_fbx_response = requests.post(convert_glb_to_fbx_url, headers=json_headers, json=glb_to_fbx_data)

            # event==='finalized'并且type==='convert_model'获取文件下载链接
            if(data['event'] == 'finalized' and data['data']['type'] == 'convert_model'):
                model_url = data['data']['output']['model']
                print(model_url)
                glb_to_fbx_response = requests.get(model_url, stream = True)
                glb_to_fbx_response.raise_for_status() 
                base_url = 'https://tripo-data.cdn.bcebos.com/' 
                path = model_url[len(base_url):]

                path_without_query = path.split('?')[0].replace('/','__')
                file_path = save_path + path_without_query
                
                with open(file_path,'wb+') as f:
                    for chunk in glb_to_fbx_response.iter_content(chunk_size=16384):
                        f.write(chunk)

    except json.JSONDecodeError:
        print("Received non-JSON message:", message)
    except KeyError:
        print("Message format error, missing expected keys.")

def on_error(ws, error):
    print("WebSocket error:", error)

def on_close(ws):
    print("WebSocket connection closed.")

def on_open(ws):
    print("WebSocket connection opened.")

def start_websocket():
    url = "wss://api.tripo3d.ai/v2/openapi/task/watch/all"
    headers = {
        "Authorization": f"Bearer {api_key}"
    }
    ws = websocket.WebSocketApp(url, header=headers,
                                on_message=on_message,
                                on_error=on_error,
                                on_close=on_close)
    ws.on_open = on_open
    threading.Thread(target=ws.run_forever).start()

def call_other_api(task_id):
    # 这里是调用其他API的代码,根据实际需要实现
    # 模拟返回值
    return {"result": "some_result"}

# 启动WebSocket连接
start_websocket()

可以设置选择图片的位置和存入模型的位置 save_path

3.设置 UE 编辑器启动自执行脚本

这一步是为了实现一打开UE编辑器就会自动执行脚本1连接websocket:

  • 编辑器的“Edit”菜单中找到“Plugins”,然后搜索“Python”,并启用“Python Editor Script Plugin”
  • 在“Project Settings”中找到“Python”部分,并启用“Developer Mode”
  • 在“Startup Scripts”添加启动脚本路径如:xxx/xxx/startWebsocket.py

就是将任务监听的脚本路径配置到 UE 自启动的配置里

4.手动执行脚本选择图片

这一步是手动选择脚本2,然后会弹出文件选择图片开始流程

执行脚本入口在:工具=>执行 python 脚本=>弹出文件对话框=>"选择图片文件.py"=>弹出选择图片对话框=>选择对应要生成模型的图片即可

上述 1-3 流程只需要配置一次,下次打开 UE 编辑器会自动执行启动脚本实时监听已生成的模型并下载导入至编辑器内;4 为图片生成新3D模型的时候执行

总结

到这里,整个流程都已跑通,对于使用 UE 的用户来说,只需要在当前编辑的工具栏操作一下选择图片即可,然后就干自己的事就行了,当模型生成完会自动导入到当前编辑器中,完美实现了无跳出的批量生成模型。

温馨提示:当前方案是25年年初做的,官方 api 接口可能有更新迭代,请根据最新接口文档替换接口链接,主要参考实现流程即可

如有问题或想法欢迎评论区留言~