godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.98k stars 21.08k forks source link

3D scene goes blank when using more than 1000 MeshInstance3D's in Web Export #96968

Open DragonAxe opened 3 weeks ago

DragonAxe commented 3 weeks ago

Tested versions

Tested on: v4.3.stable.official [77dcf97d8] (compatibility renderer) Firefox: 130.0 (64 bit) Chromium: Version 128.0.6613.119 (Official Build) Arch Linux (64-bit)

System information

Manjaro Linux #1 SMP PREEMPT_DYNAMIC Mon Aug 19 09:51:26 UTC 2024 - Wayland - GLES3 (Compatibility) - Mesa Intel(R) UHD Graphics 620 (KBL GT2) - Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz (8 Threads)

Issue description

In my 3D project, I was using blender to create my level by making hundreds of copies of the same object with different offsets and rotations but using the same mesh to save on video memory. Everything worked as expected when running natively. 2D elements like Controls and Sprites still render fine on web. All 3D elements are gone and only the clear color remains on web.

Screenshot of my Blender scene with 2543 objects all using the same mesh: image

After some testing, I narrowed the problem down to the number of MeshInstance3D nodes. If the scene has more than 1000 MeshInstance3Ds, the web export scene will go blank. I did not find any mention of this limitation in the documentation, github issues, or reddit posts.

Steps to reproduce

Minimal reproduction project (MRP)

Add the following script to a root node of an empty scene.

extends Node

func _ready() -> void:
  add_camera()
  add_sun()

  # Works in web export:
  add_generated_meshes(1000)  # Shows 1000 spheres
  # Doesn't work in web export:
  # add_generated_meshes(1001)  # Shows only clear color background

  print(get_meshinstance_count())  

func get_meshinstance_count() -> int:
  var count: int = 0
  for child: Node in get_all_children(get_tree().root):
    if child is MeshInstance3D:
      count += 1
  return count

func get_all_children(node: Node) -> Array[Node]:
  var nodes : Array[Node] = []
  for N: Node in node.get_children(true):
    if N.get_child_count() > 0:
      nodes.append(N)
      nodes.append_array(get_all_children(N))
    else:
      nodes.append(N)
  return nodes

func add_camera() -> void:
  var camera: Camera3D = Camera3D.new()
  add_child(camera)
  camera.global_position = Vector3(0, 0, 50)

func add_sun() -> void:
  var sun: DirectionalLight3D = DirectionalLight3D.new()
  add_child(sun)
  sun.global_rotation_degrees = Vector3(-50, 0, 0)

func add_generated_meshes(count: int) -> void:
  var width: int = sqrt(count) as int
  for i: int in range(0, count):
    var mesh: MeshInstance3D = MeshInstance3D.new()
    mesh.mesh = SphereMesh.new()
    add_child(mesh)
    mesh.global_position = Vector3(
      i%width - width/2.0,
      floorf((i as float)/width) - width/2.0,
      0
    )
BokoYoss commented 2 hours ago

I noticed this on a few of my projects, was just struggling to nail down the repro. Good find on the exact count that causes this. Some notes that might help with the investigation:

  1. It started with 4.3. Running the code above on 4.2, you can do 10000 meshes in the web export without issue for instance.
  2. There are no errors when it happens, and the game continues to run (just without rendering) with audio, controls etc.
  3. It is reversible- if you queue_free() a mesh when this happens and that brings you back under the magic number, you'll get your game world back. You can flicker between this state and visibility (one of our games allowed us to "claw back" the viewport by blindly collecting spawned objects)