工具模式下如何通过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
相关产品推荐
相关产品推荐

