ssbo 视锥剔除优化提升效果一般

This commit is contained in:
Your Name 2026-02-27 16:57:53 +08:00
parent f3f8da7b90
commit 86aaa21ddd
2 changed files with 72 additions and 5 deletions

View File

@ -1,4 +1,5 @@

import math
from panda3d.core import GeomNode, GeomVertexFormat, GeomVertexWriter
from panda3d.core import InternalName, LMatrix4f, NodePath, Vec3
import time
@ -12,8 +13,9 @@ class ObjectController:
- 提交: 离开 chunk 时仅重建该 chunk 的静态表示
"""
def __init__(self, chunk_size=64):
def __init__(self, chunk_size=64, chunk_world_size=40.0):
self.chunk_size = max(8, int(chunk_size))
self.chunk_world_size = max(8.0, float(chunk_world_size))
self._reset_state()
def _reset_state(self):
@ -39,6 +41,9 @@ class ObjectController:
# }
self.chunks = {}
self.active_chunks = set()
self._next_chunk_id = 0
# spatial cell key -> [chunk_id, ...]
self._cell_to_chunks = {}
# UI hierarchy metadata (matches source model parent/child structure)
self.tree_root_key = None
@ -138,6 +143,33 @@ class ObjectController:
self.chunks[chunk_id] = chunk_data
return chunk_data
def _get_cell_key_from_pos(self, pos):
inv = 1.0 / self.chunk_world_size
return (
int(math.floor(pos.x * inv)),
int(math.floor(pos.y * inv)),
int(math.floor(pos.z * inv)),
)
def _allocate_spatial_chunk(self, root_np, world_pos):
"""
Allocate object into a spatially-local chunk for better frustum culling.
Objects in the same world cell are grouped together, and overflow creates
another chunk for that same cell.
"""
cell_key = self._get_cell_key_from_pos(world_pos)
chunk_ids = self._cell_to_chunks.setdefault(cell_key, [])
for chunk_id in chunk_ids:
chunk = self.chunks.get(chunk_id)
if chunk and len(chunk["members"]) < self.chunk_size:
return chunk_id, chunk
chunk_id = self._next_chunk_id
self._next_chunk_id += 1
chunk_ids.append(chunk_id)
return chunk_id, self._ensure_chunk(root_np, chunk_id)
def _rebuild_static_chunk(self, chunk_id):
chunk = self.chunks.get(chunk_id)
if not chunk:
@ -241,8 +273,8 @@ class ObjectController:
pick_gnode = GeomNode(f"pick_{global_id}")
pick_gnode.add_geom(pick_geom, gnode.get_geom_state(gi))
chunk_id = global_id // self.chunk_size
chunk = self._ensure_chunk(scene_root, chunk_id)
world_pos = world_mat.get_row3(3)
chunk_id, chunk = self._allocate_spatial_chunk(scene_root, world_pos)
obj_np = chunk["dynamic_np"].attach_new_node(render_gnode)
obj_np.set_mat(world_mat)
@ -269,6 +301,7 @@ class ObjectController:
t2 = time.time()
print(f"[控制器] Static chunk flatten took {(t2 - t1) * 1000:.0f}ms")
print(f"[控制器] Built {len(self.chunks)} chunks, {global_id} objects")
print(f"[控制器] Spatial chunking: cell={self.chunk_world_size:.1f}, max_members={self.chunk_size}")
# Fill per-node aggregate IDs and build deterministic preorder list for UI.
self._aggregate_tree_ids(self.tree_root_key)

View File

@ -83,6 +83,11 @@ class SSBOEditor:
self._last_group_sync_mat = None
self._last_single_sync_gid = None
self._last_single_sync_mat = None
# Performance toggle: forcing shadow tasks every frame is expensive.
# Keep it off by default so frustum/content reduction has clearer FPS impact.
self.realtime_shadow_updates = False
self._scheduler_tasks_original = None
self._realtime_shadow_tasks_enabled = False
# Initialize ImGui Backend if not already present
if not hasattr(self.base, 'imgui_backend'):
@ -109,6 +114,13 @@ class SSBOEditor:
if model_path:
self.load_model(model_path)
def _capture_scheduler_tasks_snapshot(self):
scheduler = getattr(self.rp, "task_scheduler", None)
if not scheduler or not hasattr(scheduler, "_tasks"):
return
if self._scheduler_tasks_original is None:
self._scheduler_tasks_original = [list(frame_tasks) for frame_tasks in scheduler._tasks]
def _enable_realtime_shadow_tasks(self):
"""
Force PSSM-related scheduled tasks to run every frame to avoid visible
@ -118,6 +130,8 @@ class SSBOEditor:
if not scheduler or not hasattr(scheduler, "_tasks"):
return
self._capture_scheduler_tasks_snapshot()
required = {
"pssm_scene_shadows",
"pssm_distant_shadows",
@ -134,6 +148,26 @@ class SSBOEditor:
if changed:
print("[SSBOEditor] Realtime shadow tasks enabled (PSSM updates every frame).")
self._realtime_shadow_tasks_enabled = True
def _disable_realtime_shadow_tasks(self):
"""Restore scheduler layout captured before realtime shadow override."""
scheduler = getattr(self.rp, "task_scheduler", None)
if not scheduler or not hasattr(scheduler, "_tasks"):
return
if self._scheduler_tasks_original is None:
self._realtime_shadow_tasks_enabled = False
return
scheduler._tasks[:] = [list(frame_tasks) for frame_tasks in self._scheduler_tasks_original]
self._realtime_shadow_tasks_enabled = False
def set_realtime_shadow_updates(self, enabled):
"""Public toggle for aggressive per-frame shadow updates."""
self.realtime_shadow_updates = bool(enabled)
if self.realtime_shadow_updates:
self._enable_realtime_shadow_tasks()
else:
self._disable_realtime_shadow_tasks()
def load_font(self):
"""Load custom font for ImGui"""
@ -186,8 +220,8 @@ class SSBOEditor:
self.model.reparent_to(self.base.render)
# Keep shadow feedback responsive during interactive edits.
self._enable_realtime_shadow_tasks()
# Keep this off by default for better overall FPS/scaling with visibility.
self.set_realtime_shadow_updates(self.realtime_shadow_updates)
# NO rp.set_effect() — use RP default rendering for max FPS
# NO SSBO creation — vertex positions are baked