Quick answer: Set MeshInstance3D.custom_aabb to an AABB that encloses the maximum displaced extent. Or use extra_cull_margin for a uniform expansion. Default culling uses mesh-bind-pose AABB.
A vertex shader displaces a flag mesh upward. Mesh visibly clips into view but the renderer culls it because the AABB still represents the un-displaced flag.
The Symptom
Mesh pops in/out as it edges into the camera frustum. The visible animated extent extends beyond the bounding box.
The Fix
extends MeshInstance3D
func _ready() -> void:
# Manually expanded AABB to include max displacement
custom_aabb = AABB(Vector3(-2, -1, -2), Vector3(4, 3, 4))
The AABB is in local space. Expand to cover whatever your vertex shader does at peak amplitude.
extra_cull_margin Alternative
extra_cull_margin = 2.0 # meters
Uniform expansion. Easier; less precise. Cost: more meshes pass culling each frame because the box is bigger.
Skinned Mesh Caveat
For SkinnedMeshes (animated), Godot 4 has its own handling but vertex shader displacement on top still requires custom_aabb. Combine.
Verifying
Walk the camera so the displaced mesh edges into and out of the frustum. With proper custom_aabb, the mesh stays visible until the AABB fully exits. Without, pops.
“Custom AABB or extra_cull_margin. Displaced mesh stays on-screen.”
Related Issues
For shader uniform runtime, see shader uniform. For shader uniform array, see uniform array.
Tell the renderer the real bounds.