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

工具模式下如何通过SubViewport捕获MeshInstance3D图像?

问题描述

尝试通过SubViewport捕获MeshInstance3D的图像并分配给Sprite,但现有代码存在两个问题:

  • 无法捕获正确的图像(怀疑SubViewport/Camera3D更新速度不足)
  • Sprite无法与对应Mesh保持正确的相对位置(怀疑3D与2D单位转换有误)

现有代码:

@tool
extends Node2D

const PIXEL_PER_METER = 100.0
@export var btn := false : set=set_btn

func set_btn(new_val):
    if(new_val):
        assign_images()

func assign_images():
    var sub_viewport=$SubViewport
    var camera=$SubViewport/Camera3D
    var mesh_instances=[ $SubViewport/A, $SubViewport/B, $SubViewport/C ]
    var sprites=[ $A, $B, $C ]
    
    sub_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
    camera.projection = Camera3D.PROJECTION_ORTHOGONAL
    
    for i in len(mesh_instances):
        var sprite=sprites[i]
        var mesh=mesh_instances[i]
        var mesh_aabb=mesh.get_aabb()
        camera.size = max(abs(mesh_aabb.size.x), abs(mesh_aabb.size.y))
        camera.global_position = Vector3(mesh.global_position.x, mesh.global_position.y, camera.global_position.z)
        sprite.position = Vector2(mesh.position.x * PIXEL_PER_METER, mesh.position.y * -PIXEL_PER_METER)
        #await get_tree().process_frame  # this doesn't work either
        sprite.texture=ImageTexture.create_from_image(sub_viewport.get_texture().get_image())

注:接受替代方案,但禁止使用MeshInstance2D。


解决方案

一、修复图像捕获问题

核心问题是修改Camera参数后,SubViewport未完成渲染就捕获图像。需要强制SubViewport立即完成当前帧的渲染:

  • 每次捕获前设置SubViewport仅在下一帧清空,避免残留之前的渲染内容
  • 修改Camera参数后调用force_draw()强制立即渲染,确保捕获到最新图像
  • 捕获完成后恢复SubViewport的更新模式为禁用,节省性能

二、修复位置与尺寸映射问题

3D正交相机的size是半高度(总高度为2*size),且需要考虑Mesh的全局AABB偏移,同时要处理3D与2D的坐标/单位转换:

  • 计算Mesh的全局空间AABB,确保Camera对准Mesh的中心
  • 相机size设为Mesh AABB最大尺寸的一半,保证Mesh完整显示
  • 根据SubViewport分辨率、相机size和像素米比例,计算Sprite的缩放值,保证显示尺寸正确
  • 转换3D坐标到2D时反转Y轴,同时基于全局位置计算Sprite的2D位置

完整修正代码
@tool
extends Node2D

const PIXEL_PER_METER = 100.0
@export var btn := false : set=set_btn

func set_btn(new_val):
    if new_val:
        assign_images()

func assign_images():
    var sub_viewport = $SubViewport
    var camera = $SubViewport/Camera3D
    var mesh_instances = [ $SubViewport/A, $SubViewport/B, $SubViewport/C ]
    var sprites = [ $A, $B, $C ]

    # 初始化相机为正交模式
    camera.projection = Camera3D.PROJECTION_ORTHOGONAL
    # 先禁用自动更新,避免不必要渲染
    sub_viewport.render_target_update_mode = SubViewport.UPDATE_DISABLED

    for i in range(len(mesh_instances)):
        var sprite = sprites[i]
        var mesh = mesh_instances[i]
        
        # 获取Mesh的全局AABB(包含旋转缩放后的实际大小和位置)
        var global_aabb = mesh.get_global_aabb()
        var mesh_center = global_aabb.center
        var mesh_size = global_aabb.size

        # 调整相机位置到Mesh中心,保持Z轴不变
        camera.global_position = Vector3(mesh_center.x, mesh_center.y, camera.global_position.z)
        # 相机size设为Mesh最大尺寸的一半(正交相机size是半高度)
        camera.size = max(mesh_size.x, mesh_size.y) / 2.0

        # 强制SubViewport清空并立即渲染
        sub_viewport.render_target_clear_mode = SubViewport.CLEAR_MODE_ONLY_NEXT_FRAME
        sub_viewport.force_draw()

        # 捕获图像并赋值给Sprite
        var captured_image = sub_viewport.get_texture().get_image()
        sprite.texture = ImageTexture.create_from_image(captured_image)

        # 计算Sprite的缩放:保证Mesh在2D中的尺寸正确映射
        # SubViewport分辨率 / (2*camera.size) 得到每米对应的像素数,再除以PIXEL_PER_METER得到缩放比例
        var viewport_scale = sub_viewport.size.x / (2 * camera.size) / PIXEL_PER_METER
        sprite.scale = Vector2(viewport_scale, viewport_scale)

        # 转换3D全局位置到2D:反转Y轴,乘以像素米比例
        sprite.global_position = Vector2(
            mesh_center.x * PIXEL_PER_METER,
            -mesh_center.y * PIXEL_PER_METER
        )

内容的提问来源于stack exchange,提问作者Manas R. Makde

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:18:21