Commit Graph
100 Commits
Author SHA1 Message Date
atdunbg f73e52926f Reorganize directory layout: editor panels/, grouped shaders, drop stale header
- App/editor: move 7 panels into panels/ (Editor core stays at root)
- shaders/: group into pbr/ post/ debug/ env/ (runtime load paths updated)
- Delete outdated duplicate Engine/src/Renderer/VulkanAssetUploader.h
2026-10-07 09:52:17 +08:00
atdunbg cc837bc605 Add animation controller system, material assets and skeleton visualization
- .animctrl state machine asset (params/transitions/blend trees, 1D+2D
  directional blending) with Unity Animator-style editor panel: state
  list, transitions, params, directional pad preview driving live
  CrossFade on the selected entity
- Split animation evaluation out of SceneRenderer into
  AnimationSystem/AnimationEvaluator; skin palettes with rest-pose
  fallback and blend-weight epsilon filtering
- MaterialAsset (.material) with reload-safe preview tile pool
  (DropMaterial clears dirty refs before instance destruction)
- EditorGizmoPass skeleton wires + viewport joint overlay; camera
  projection rework (baked Vulkan Y-flip, ortho support)
- Editor/game build split via HELLOVULKAN_EDITOR option
2026-10-07 01:50:04 +08:00
atdunbg a941207ae3 Raise bloom threshold to HDR 3.0; fix blit dst-mip layout warnings
The smooth brass case back of the pocket watch reflects large bright
sky regions of the Venice dawn environment at HDR values well above 1.
With the old threshold of 1.0 the entire metal surface passed the
bloom extract, and the blurred additive result washed it out to flat
white. Only genuinely hot highlights (HDR >= 3, near-white after ACES)
should bloom.

- Default bloomThreshold 1.0 -> 3.0; Stats slider range extended to 8
  with a logarithmic scale
- Texture mip generation: transition the whole mip chain to
  TRANSFER_DST up front; blitting into never-transitioned destination
  mips triggered one validation warning per level
2026-10-05 12:28:14 +08:00
atdunbg 6c80693618 Fix bloom glow flicker on static geometry under TAA jitter
TAA subpixel jitter makes rasterized HDR specular highlights oscillate
per frame. The old RGB min/max neighborhood clamp re-clamped the
converged history against jitter-driven extremes every frame, leaving a
periodic residual that the bloom threshold then amplified into visible
glow pulsing on static models.

- TAA: replace the RGB AABB clamp with YCoCg variance clipping
  (history clamped toward the stable neighborhood mean +/- 1.25 sigma,
  intersected with the min/max box as a ghosting upper bound)
- Bloom extract: 5-tap cross prefilter before the threshold spreads
  surviving 1px jitter energy over a ~4x4 footprint so the glow no
  longer pulses
2026-10-05 12:08:53 +08:00
atdunbg 5d88ed2057 Improve PBR quality: mip chains, anisotropic filtering, baseColorFactor
Root causes behind blurry/unphysical materials (4K pocket watch render
mushy, glass fully opaque, watch hands unreadable):

- Textures loaded via stbi had a single mip level: 4096x4096 albedo/MR
  maps sampled with no LOD -> minification aliasing turned fine detail
  (watch hands) into noise. Generate a full blit-based mip chain with
  per-level layout transitions.
- samplerAnisotropy was never enabled on the device and every sampler
  was created with anisotropy disabled: grazing-angle surfaces (watch
  face, ground) collapsed into a blur. Enable the device feature and
  16x anisotropic filtering on material texture samplers.
- glTF baseColorFactor was ignored: the glass material (alphaMode
  BLEND, no baseColor texture, baseColorFactor.a = 0.3) fell back to
  the default white texture with alpha = 1 and rendered fully opaque.
  MaterialParams gains a baseColor vec4 (rgb tints albedo, a scales
  the blend alpha) threaded through both PBR shaders.
- alphaMode MASK was treated as opaque: add a factors.w switch that
  performs an alpha cutout test in the opaque shader.
- Fix material cache key dropping factors.z (separate-roughness
  switch); it now encodes the full factors and baseColor vectors.
2026-10-05 12:01:07 +08:00
atdunbg a3037cd401 Input facade: add cursor capture, mouse delta and scroll delta
SetCursorCaptured / IsCursorCaptured / GetMouseDelta / ConsumeScrollDelta
now route through the Input facade, so the editor camera no longer takes
a Window& at all: Camera::Update and EditorCamera::Update lose their
window parameter and SandboxApp calls Update(dt) directly. App layer
now has zero Window/GLFW usage for input - everything goes through
Engine::Input.
2026-10-05 11:45:57 +08:00
atdunbg 641b255cb2 Unify input behind Engine::Input facade; fix Ctrl+P and Pause button
New Engine/Core/Input.h/.cpp: static input facade with a Key/MouseButton
enum (values map to GLFW key codes internally) so scripts and game logic
no longer touch GLFW directly - window backend becomes swappable and the
facade is the surface to expose when embedding scripting later. Wired up
in Application's constructor; Camera (editor camera WASD/RMB/MMB), the
WASDMove script and SandboxApp key handling now query Input only.

Ctrl+P hotkey fix: the !m_Playing guard made the hotkey dead exactly
when it was needed - after pressing Play. Now toggles both ways.

Toolbar Pause button now reads "Resume" while paused (tooltip follows).
2026-10-05 11:34:27 +08:00
atdunbg c083ee5cb7 Fix PIE deadlock when playing a scene without a primary camera
BuildFrame returned false (skip frame) when the game view had no
primary camera, skipping the WaitFence/ResetFence/submit chain - the
frame-slot fence never got signaled, so the next WaitFence on that
slot blocked forever. Reproduced instantly by playing an empty scene
with embedded PIE; latent in manual fullscreen preview too.

Game view now falls back to the editor camera instead of skipping the
frame, keeping the per-slot fence cadence intact. Viewport warning
text updated to mention the editor-view fallback.
2026-10-05 11:21:50 +08:00
atdunbg 1a278d5945 PIE: viewport-embedded playback with UE-style main toolbar
Play no longer switches to fullscreen direct rendering: PIE now keeps
the editor UI alive and renders the primary camera into the Viewport
panel (new SceneRenderer::SetGameView flag; kDisplayDirect stays a
manual fullscreen preview for later release form). Editor camera stops
being driven during PIE, and viewport click-pick is gated off while
playing (clicks are game input now).

The Play/Stop buttons move out of the menu bar into a dedicated main
toolbar row below it (centered, UE Main Toolbar semantics): green Play
when idle; Pause (yellow, highlighted while paused) + Stop (orange)
while playing. Ctrl+P hotkey kept. Stats "Pause Simulation" merged
into the toolbar Pause; viewport overlay shows PAUSED accordingly.
2026-10-05 11:14:24 +08:00
atdunbg 5ca5ac2a1e Editor: group Details parameters and reorganize Stats panel
Details: multi-parameter components (RigidBody/Joint/CharacterController/
Animation) now use SeparatorText category headers in UE Details style;
all parameter labels normalized to Pascal Case.

Stats: rearranged into View / Scene / Rendering / Physics / Performance
collapsing groups. Clear Color moved to Scene, Debug View to Rendering,
GPU Timings + Frame merged into Performance; bare top-level widgets and
the now-obsolete Environment group removed.
2026-10-05 01:20:00 +08:00
atdunbg 432089747f Environment settings become scene entities (UE Skybox/SkyLight semantics)
Skybox/SkyLight are now Actor-carried components instead of resident
global settings, matching UE where SkyAtmosphere/SkyLight/fog are
Actors added from Place Actors and configured in Details.

- New SkyboxComponent (active + envPath asset slot) and SkyLightComponent
  (active + ambientColor); first-active entity wins per frame
- SceneRenderer collects env from components into FrameSceneData
  (DrawSkybox/envPath/ambient); Renderer performs delta sync - env
  reloads only when the resolved path changes, neutral fallback when
  empty or unloadable
- App-side env plumbing removed: kEnvPaths table and getEnvIndex/
  setEnvIndex callbacks deleted; StatsPanel keeps ClearColor + debug
  view, env params move to Details
- Serializer: scene-level Settings stores ClearColor only; env
  components serialize per entity; legacy v1 scenes (Environment header
  + Settings.AmbientColor/DrawSkybox) auto-migrate into an Environment
  entity on load for visual parity
- Inspector: Skybox/Sky Light sections with .hdr asset slot dropdown
  and Add Component entries; Outliner "+ Create" popup offers
  Empty/Light/Camera/Environment
2026-10-05 01:02:21 +08:00
atdunbg b7d6f53591 Editor: rework default layout to UE5 arrangement
Panels renamed to UE semantics (docking identity change): Hierarchy ->
Outliner, Inspector -> Details, Asset Browser -> Content Browser.

Default DockBuilder layout now matches UE5 factory layout: Details
docked right (full height), Content Browser docked bottom spanning
left+center, Outliner top-left, Viewport center with Stats as a
side tab.

Legacy imgui.ini (old window names, no new names) is removed once at
Init so the new default rebuilds cleanly; fresh or new-style ini is
untouched.
2026-10-05 00:47:33 +08:00
atdunbg 42757bab36 AssetManager: drop immediate SaveGUIDRegistry on registration
2268fa5 added the dirty-flag deferred flush but left the immediate
save in place, so new GUID registrations wrote the registry twice
(once inline, once at the next FlushGUIDRegistry). Remove the inline
save so the deferred path is the only writer.
2026-10-05 00:39:48 +08:00
atdunbg dec171cd1e Fix selection outline never showing for the first entity
entt entity 0's integral bits are 0, colliding with the "no selection"
sentinel: u32 selected == 0 meant "clear selection", so the scene's
first-created entity could never be outlined. Replaced with
std::optional<u32> + FrameSceneData::selectedEntityValid.

Also restore per-batch instance contiguity broken by the batchMats
flattening (e038cb3): entities sharing a mesh scatter across the flat
instance array in collection order. ViewScratch now tracks per-batch
singly-linked lists and appendView aggregates each batch into a
contiguous range, preserving the flat-array performance intent.

Hierarchy click selection now routes through SelectEntity so outline
stays in sync when picking from the panel.
2026-10-05 00:39:48 +08:00
atdunbg e038cb39fa SceneRenderer 实例收集扁平化:batchMats 从 vector<vector> 改为单 vector 按 (first,count) 切片,优化大场景下批次实例存储与 cache 命中率 2026-10-04 21:26:48 +08:00
atdunbg 3e85285e41 修改SceneRenderer 的存储的世界矩阵存储方式 2026-10-04 20:10:49 +08:00
atdunbg 2268fa5f76 资产改为保存场景或者关闭软件时候更新guid文件,vulkan/DescriptorManager 创建DescriptorPool时候注释eFreeDescriptorSet,PhysicsWorld初始化时候改为推荐的inNumBodyMutexes 2026-10-04 19:55:49 +08:00
atdunbg c1854453e5 修改场景实体查询为unordered_set存储,提升查询效率, 修改Physics的SyncScene也为unordered_set 2026-10-04 19:12:53 +08:00
atdunbg b78d9fb4c9 修改错误的TryBitsByBodyId返回值 2026-10-04 18:49:18 +08:00
atdunbg c8a1cd90bf 修复physics物理数据计算不正确的问题,同时为物理添加map索引 2026-10-04 18:42:23 +08:00
atdunbg ee5ab8e94e feat: parent-child transform hierarchy (UE Actor attach semantics)
- TransformComponent TRS = local (relative to parent); cached world SRT
  composed hierarchically (parent pos/rot/scale, no matrix decomposition
  shear under non-uniform parent scale)
- HierarchyComponent (parent + children, lazily attached); Scene::
  SetParent (keepWorld, cycle-safe) / GetParent / IsDescendantOf /
  SetWorldPose (world->local inverse, used by physics writeback & gizmo)
- UpdateTransforms: DFS from roots, recompose only dirty-or-dirty-
  ancestor subtrees (parents before children)
- DestroyEntity: descendants buried with parent (UE cascade)
- Serialization v2: optional Parent uuid key, two-pass relink on load
- Hierarchy panel: tree view, drag-drop reparent / drop-to-root detach,
  Create Child / Detach context menu; Inspector shows parent name
- Consumers moved to world-space reads: lights, camera pos, physics
  bodies/characters/joints/collider baking, gizmo, F-focus, AlignTo
2026-10-04 15:22:25 +08:00
atdunbg 97408e506c fix: ImGui hover jumps while cursor captured (RMB look / MMB orbit)
With GLFW_CURSOR_DISABLED the official GLFW backend keeps feeding
virtual unbounded cursor coords to ImGui each frame, so hover state
wanders across hidden panels while the physical cursor is invisible.
Append a "mouse left the window" pos event before ImGui::NewFrame
whenever the cursor is captured - ImGui treats the frame as mouseless.
2026-10-04 15:00:29 +08:00
atdunbg 230df67db7 fix: editor camera draw distance 100m -> 10km (UE semantics)
UE's far clip is effectively unlimited and shadow distance is an
independent parameter. Split kCameraFar (main projection, 10km) from
kShadowDistance (cascade splits, unchanged 100m) so distant geometry
is no longer clipped while shadow quality stays bit-identical.
Main projection now references the shared constants instead of
duplicated literals.
2026-10-04 14:49:27 +08:00
atdunbg 13055cc8ac fix: assimp INCOMPLETE flag no longer rejects animation-only FBX
Assimp flags every mesh-less scene as AI_SCENE_FLAGS_INCOMPLETE
(FBXConverter), so pure animation FBX never reached clip parsing -
the scene was discarded before ParseAnimations ran. Only treat null
scene / missing root node as hard failure; log INCOMPLETE as a
warning and continue. Final validity stays with ParsedModel::Valid().
2026-10-04 09:31:23 +08:00
atdunbg 175ce35def fix: animation-only FBX assets loadable; clip data viewer
Pure-animation FBX (no meshes, skeleton clips only) was rejected at
the cache layer: ParsedModel::Valid() required non-empty meshes, so
Model::FromParsed/LoadModel returned null and the asset never made it
into the cache. Animation assets are now first-class:

- ParsedModel::Valid() = meshes or clips non-empty; parse failures
  (both empty) still rejected. Empty-mesh consumers already guarded
  (rendering skips empty mesh loop, mesh collider falls back to Box)
- ParseFromDisk: fix null-deref when assimp returns a null scene
- AssetCache::Find(path) + AssetManager::GetModelByPath for panels
- Asset Browser hover card shows meshes/bones/clips for loaded models
- Inspector model line flags "animation-only" when meshes == 0
- Animation section: collapsible clip data table (name/duration/
  channel count) to verify bone-name matching of attached clips
2026-10-04 02:31:30 +08:00
atdunbg 2577809ee4 feat: Play-In-Editor (PIE) with in-memory scene snapshot
Editor toolbar gains Play/Stop (Ctrl+P) with UE semantics:

- Play: serialize current scene to an in-memory YAML snapshot via new
  SceneSerializer::SerializeToString, switch display to kDisplayDirect
  (primary camera entity renders), unfreeze time (timeScale 0 -> 1)
- Stop: DeserializeFromString atomically rebuilds the pre-play scene,
  back to kDisplayViewport (editor camera), refreeze time; selection
  cleared and gizmo drag reset since all entity handles dangle
- SceneSerializer refactored: SerializeDoc/ParseRoot shared by file
  and memory variants; file Save/Load behavior unchanged
- Running state disables gizmo + editor hotkeys in the viewport (input
  goes to game scripts), draws yellow PLAYING hint and a no-camera
  warning; File->Save/Load disabled while playing (UE behavior)

Physics bodies/constraints/characters and script instances are
reconciled automatically by the existing per-frame sync on restore.
2026-10-04 02:22:49 +08:00
atdunbg c949e8684b build: strip Jolt debug symbols from Release (55MB -> 11.3MB exe)
Jolt's GENERATE_DEBUG_SYMBOLS option defaults to ON and injects -g in
every config, so the Release binary carried ~44MB of DWARF sections
(.debug_info/.debug_line/...) from Jolt TUs alone -- 80% of the 55MB
exe. Force the option OFF for Release builds only (the vendored
submodule is untouched; Debug/RelWithDebInfo keep symbols).

Measured: 55.0MB -> 11.3MB, .text unchanged at 8.5MB, zero debug
sections left. The remaining ~1.6MB is the ELF symtab, useful for
crash stacks; plain strip takes it to 9.7MB if a distributable is
needed.
2026-10-04 02:10:08 +08:00
atdunbg 90097a756e fix(editor): rotation drag accumulates without wrap (UE session semantics)
Dragging rotation hit a wall at the wrap point: each frame re-read
euler from the quat and wrapped to [0,360), so dragging past 360
snapped the display back to 0 (and backwards dragging wrapped up to
360) -- values "jumped around" instead of accumulating.

Port UE TransformDetails session semantics: the rotation row shows a
local mirror value while the user edits it (accumulating freely in
both directions, never wrapping), and re-syncs from the quat only on
foreign changes (gizmo drag, physics writeback, entity switch).
Detection is exact component-wise quat equality: the mirror quat comes
from the same QuatFromEuler used by SetRotation, so an untouched pair
compares bit-equal, while any foreign writeback -- including sub-0.05
degree gizmo steps that a dot-product threshold would miss -- differs
in at least one float. Edit frames skip the check (mirror leads the
quat by one drag delta), gated on the previous frame's item-active
state.
2026-10-04 02:00:42 +08:00
atdunbg 4498bc7535 feat(editor): rotation display in UE's 0-360 convention
User pointed out the real gap: UE Details shows rotation in [0,360)
(-90 reads 270); our readback exposed [-180,180] with negative zeros.
GetRotation now wraps each axis to [0,360) via fmod -- strictly
equivalent math (each axis has 360-degree period), so matrix-rebuild
consumers like Camera::EulerRotMat are unaffected. The one consumer
needing a continuous domain is the free-look pitch clamp [-89,89] in
Camera::Update, which wraps back to [-180,180) before applying deltas.
2026-10-04 01:46:05 +08:00
atdunbg 4fc48c21ac fix(editor): euler display precision at gimbal lock (90 -> 89.98)
Typing 90 degrees on the pitch axis showed 89.98. The stored quat is
exact (matrices differ by 0.0) -- the noise is display-side: quat's
sin(90) components square to 0.99999994 in float, and asin() diverges
at |sin|->1, amplifying 6e-8 into 0.02 degrees. Yaw/roll use atan2 and
were always exact, matching the user's report being X-axis only.

Two fixes, both aligned with how UE gets away with it (internally UE
is quat + equally pathological, its Details panel just shows one
decimal):
- GetRotation gimbal-lock threshold 1.0 -> 1-1e-6: pitch near +-90
  now takes the composite-angle atan2 branch (ratio-stable) instead
  of asin, so typing 90.0 reads back exactly 90.0. Probed: X=90 and
  X=-90 now round-trip to 0 error; generic poses stay ~1e-6 deg.
- Rotation row shows one decimal ("%.1f", UE Details default), masking
  the residual asin noise for angles near (not at) the pole; Ctrl+Click
  input stays full precision. Negative zero from atan2 cleaned up.

Remaining asin noise for e.g. 89.9 (~9e-4 deg) is below the 1-decimal
display and unreachable in practice.
2026-10-04 01:37:49 +08:00
atdunbg 90bf63e641 feat(animation): attach external animation files to a model
Animation-only FBX files (no mesh, clips only) could not be used:
clips are keyed by node index into the parsed model's node table, so
there was no path to get them onto a different model's skeleton.

Add AssetManager::AttachAnimationClips: parses the container with the
CPU-only path (no GPU upload -- empty meshes cannot build a Model),
remaps each clip's channels to the target model's node indices by
bone name, and appends to its clip list. Channels whose bone name is
absent are skipped and counted; clips with zero matching channels are
dropped. Same-source attach is idempotent via a new
AnimationClip::sourcePath marker; name clashes get the file stem
prefixed.

Inspector Animation block gains an attach slot (UE Details semantics)
listing model-container files plus a bullet list of attached sources;
already-attached paths are replayed idempotently when the entity is
selected, so clips survive restarts. The paths persist in the scene
(Animation.Attached); the clips themselves stay runtime state.

Limitations (documented, by design): same-skeleton retargeting only
-- bone names must match between source and target (same DCC export
or Mixamo-style per-animation downloads); cross-skeleton retargeting
is out of scope.
2026-10-04 01:25:04 +08:00
atdunbg 69671cf4eb feat(editor): manual mesh renderer setup + asset hover info card
Mesh Renderer was missing from the Add Component menu and its model
reference was read-only, so a mesh could only be attached by loading
a saved scene. Add it to the menu (Unity Add Component semantics) and
give the block a UE-Details-style asset slot: a dropdown listing all
model files under assets/, selecting applies LoadModel (cache deduped).

Asset browser files now show a UE Content Browser style hover card:
kind, size, modified time, and loaded state (new AssetCache::Contains
query, read-only -- hovering never triggers loading).

Fix latent FATALs unlocked by manual assignment: AssetCache::Get
aborts on invalid handles, so rendering, animation sampling, and mesh
collider build now guard mr.model.Valid() before calling it.
2026-10-04 01:07:38 +08:00
atdunbg 7d02d344e5 fix(scripts): prevent duplicate script attachment per entity
The per-entity script list allowed picking the same script class
twice; the name-keyed runtime silently ignores duplicates, leaving
the extra rows dead weight with unusable enable bits. Enforce
DisallowMultipleComponent semantics: the dropdown of each row only
offers unattached names (own name kept for echo), "+ Add Script"
disables when every registered script is attached, and scene loading
dedupes as a safety net for hand-edited files.
2026-10-04 00:57:45 +08:00
atdunbg fd2d8f9b34 fix(editor): asset browser directory navigation
The file list was hardwired to the root directory, so it was always
empty (all assets live in subdirectories). Directories are now
selectable from both the tree (click row) and the list (click [D]
row), with a breadcrumb + Root button. Prefix check done manually
(path::starts_with is missing in this libstdc++).
2026-10-04 00:55:19 +08:00
atdunbg ab7a43a686 feat(editor): multi-script entities + asset browser panel
ScriptComponent now holds a list of {name, active} items (Unity
semantics: one entity, many scripts, each with its own enable bit;
component-level active stays as the master switch). ScriptRuntime
reconciles instances by name per entity -- rename/remove destroys
only the affected instances, untouched ones keep ticking. Scenes
persist Script.Items; old single-script key still loads.

AssetBrowserPanel (lightweight Unity Project window): fixed roots
(assets/scenes) as tabs, recursive directory tree, file list with
type filter (models/textures/scenes) and name substring filter,
double-click preloads models/HDRs into the asset caches (path-
deduped). Scene files point to the File menu for now; drag & drop
is a later iteration. Docked with Stats by default in fresh layouts.
2026-10-04 00:43:06 +08:00
atdunbg 74a2044441 chore(scripts): rename script API to GetComponent/TryGetComponent; misc
Clearer accessor naming on ScriptBehavior (Get -> GetComponent,
TryGet -> TryGetComponent), WASDMove follows. Teleport marked const
(JPH Ref does not propagate constness). Spinner: auto-rotation
disabled for the moment, registration switched from the registration
macro to an explicit lambda to verify the mechanism.
2026-10-04 00:33:47 +08:00
atdunbg 134812dae3 feat(physics): debug-draw character controller capsule
CharacterVirtual is not a registered body, so PhysicsSystem::DrawBodies
never renders it (Jolt provides no CharacterVirtual::DebugDraw either).
Add CharacterController::DebugDraw (shape wireframe at foot-origin +
shapeOffset, green) and drive it from CollectDebugData under the
colliders bit.
2026-10-03 19:01:57 +08:00
atdunbg 29c413579d refactor(physics): rename isSensor to isTrigger (Unity/UE terminology)
UE calls it Trigger (Overlap Events / Trigger preset) and Unity's UI
says "Is Trigger"; "sensor" is the physics-backend term (Jolt sensor,
Box2D sensor). Rename the component/record flag and the Inspector
label. Scene files now persist "IsTrigger" with backward-compatible
read of the old "IsSensor" key.
2026-10-03 18:53:38 +08:00
atdunbg b6d0868e15 feat(ecs): per-component enable/disable + inspector rework
Add `active` flag to the 8 behavior components (Unity enabled / UE
SetActive semantics; structural Id/Name/Transform excluded). Consumers
skip disabled components: renderer (mesh/light/anim), primary camera
election, physics reconciliation (body/constraint/character destroyed
and rebuilt on toggle), scripts keep instance but stop ticking.

Inspector: component headers now carry an enable checkbox and a remove
button, Add-Component menu greys out attached entries, and conflict
hints added (RigidBody <-> CharacterController mutual exclusion, Joint
requires RigidBody, Animation requires MeshRenderer, unregistered
script name). Scenes persist Active per component (default true).
2026-10-03 18:45:03 +08:00
atdunbg cad0481bc0 feat(app): WASDMove script (WASD camera-relative movement)
ScriptBehavior gains App() host access (Application injected via
ScriptRuntime), TryGet<T> and EntityBits for physics facade addressing.
WASDMove maps WASD to the editor camera's horizontal heading (UE
gameplay convention): dynamic bodies go through the physics velocity
facade preserving the vertical component (zeroing Y would cancel
gravity mid-fall), bodiless entities translate their transform directly.
Editor-mode key conflict is naturally avoided: RMB-held flight consumes
WASD, release hands keys to the script.
2026-10-03 18:27:12 +08:00
atdunbg 3403822a52 feat(engine): C++ native script system (ScriptBehavior + registry)
ScriptComponent stores only a script name string (POD, serializable);
instances are reconciled by ScriptRuntime each frame (PhysicsWorld
pattern): orphan/rename cleanup with OnDestroy, per-name instantiation
from a file-level self-registering registry (ENGINE_REGISTER_SCRIPT),
OnCreate on bind, OnUpdate after physics step on scaled dt. Engine
framerate loop only sees the base interface, so future Lua/C# behaviors
plug in as foreign ScriptBehavior implementations with zero engine
changes. Ships a Spinner sample script and Inspector script picker.
2026-10-03 18:02:49 +08:00
atdunbg ea6beee593 fix(physics): editor drag teleports dynamic bodies
SyncScene only reconciled static/kinematic transforms into Jolt bodies;
dragging a dynamic entity (esp. while paused, where Writeback never runs)
moved the visual but left the body -- and the collider debug wireframe --
at the old pose. Dynamic bodies now get the same drag reconciliation
(teleport + zero velocity + activate), and Writeback syncs
lastPos/lastRot to the body pose so physics motion is never misread as
editor dragging (which would teleport the body back).
2026-10-03 17:51:11 +08:00
atdunbg d5a86c170e fix(renderer): mesh collider source positions in render space for skinned meshes
Skinned meshes keep vertices in raw mesh space (bakeTransform=false);
rendering transforms them via bind palette (global(bone) x invBind, folds
FBX unit scale). Physics MeshShape/ConvexHull and the mesh bounding sphere
consumed the raw positions directly -> M1911 hull measured ~10x the visual
size. ParseFromDisk now bakes bind-palette-skinned positions per vertex
(same formula as the skinning shader) into ParsedMesh::cpuPositions; Mesh
uses them for m_CpuPositions and bounds. GPU vertex data and rendering are
untouched.
2026-10-03 17:43:55 +08:00
atdunbg e1cef53158 physics: character controller (Jolt CharacterVirtual)
- CharacterControllerComponent: capsule params (radius/halfHeight, mass,
  max slope, step height); entity origin = feet via shape offset
- CharacterController per-entity wrapper: gravity integration + grounded
  clamp, ExtendedUpdate (sweep/slide + stick-to-floor + walk-stairs),
  SetLinearVelocity/Jump/IsGrounded facade
- PhysicsWorld: SyncCharacters reconcile (create/rebuild on param
  change, editor-drag teleport), UpdateCharacters after physics Update
  (Jolt recommended order), writes back to Transform
- Inspector: params + grounded/velocity telemetry + jump debug button
- Serialization: params only, state rebuilt by reconcile
2026-10-03 16:34:20 +08:00
atdunbg 82910c41e8 physics: joint constraints (fixed/hinge/ball)
- JointComponent: type, target UUID, per-body local anchors, hinge axis
  + angle limits (data only, zero Jolt deps)
- PhysicsWorld::SyncConstraints: create/remove/rebuild on snapshot
  change; body rebuild contagion via invalidated-bits list; target
  resolved lazily by UUID each sync (no two-pass load needed)
- Jolt FixedConstraint/HingeConstraint/PointConstraint in
  LocalToBodyCOM space (anchors minus shape COM); fixed frames lock
  creation-time relative pose; hinge axis aligned across bodies
- Inspector: Joint section with target entity name dropdown
- Serialization: Joint map with UUID target, as<T> fallbacks
2026-10-03 16:26:20 +08:00
atdunbg a744b332eb physics: capsule/cylinder/compound collider shapes
- ColliderType + Capsule/Cylinder/Compound; CompoundChild list (Box/
  Sphere/Capsule, local pos/quat, non-recursive)
- CreateBody: CapsuleShape/CylinderShape (ctor param orders differ) +
  StaticCompoundShape; empty/failed compound falls back to Box
- BodyRecord caches radius/halfHeight/compound; SyncScene rebuilds on
  change (per-collider param extraction)
- Inspector: combo entries + shape params + compound child list editor
- Serialization: new fields with as<T> fallback, compound seq round-trip
2026-10-03 16:17:44 +08:00
atdunbg 6c81bfd70d physics: rigid body drive API (force/impulse/torque/velocity)
PhysicsWorld facade methods addressed by entity bits via body records:
AddForce/AddImpulse/AddTorque (dynamic bodies only, auto-wake), Set/
GetLinearVelocity/SetAngularVelocity (dynamic + kinematic). Inspector
RigidBody block gains impulse debug controls for manual response tests.
Transient by design — nothing serialized.
2026-10-03 16:10:22 +08:00
atdunbg fae6614e70 editor camera: dedicated Scene-view camera decoupled from camera entities
EditorCamera (UE FEditorViewportClient semantics) owned by SceneRenderer:
own TransformComponent + reused Camera controller. BuildFrame dispatches
render source by display mode — editor form always renders from the
editor camera (usable in empty scenes), game form renders from the
scene's primary camera entity. AlignTo copies an entity pose into the
editor camera ("Align Viewport to Camera" button in Inspector). F focus
and viewport gizmo now follow the same dispatch. main.cpp stripped of
dangling sandbox camera/spinner state; onSceneLoaded hook removed.
2026-10-03 15:52:28 +08:00
atdunbg e2a9c21e68 fix(serialization): load failure on hardened yaml-cpp + flow-style arrays
yaml-cpp master hardens zombie nodes: operator=, size(), Type() and
chained operator[] all throw InvalidNode instead of silently passing.
Storing per-component nodes via Node::operator= threw for every key
that was absent, so all 7 entities were skipped on load (0 loaded).
Reads are now level-by-level guarded (copy-init + IsDefined), which
only uses the safe surface (as<T>(fallback), begin(), IsDefined).

Save side switched from node-tree streaming to a manual Emitter:
vector/quat fields emit as single-line flow arrays ([0, 1.2, -6])
and model refs as inline maps, with 9-digit float precision for
lossless float32 round-trips.

Verified offline against the previously saved scene: 7/7 entities
parse with correct component counts; emitter output round-trips.
2026-10-03 15:30:36 +08:00
atdunbg 007d175314 serialization: scene save/load (Unity-style YAML + UE-style GUID registry)
Engine-layer SceneSerializer (yaml-cpp submodule): entities/components
to versioned YAML, atomic clear-and-rebuild on load with per-entity
fault tolerance, missing-field defaults, unknown-component skip and
UUID dedup. Asset references are {Guid, Path} pairs resolved through
the new AssetManager GUID registry (assets/asset_registry.yaml) with
path fallback; procedural:// pseudo-paths rebuild from parameters.
Editor File menu gets Save/Load Scene modals; app re-resolves stale
entity handles via onSceneLoaded callback.
2026-10-03 14:47:02 +08:00
atdunbg ffe33e9967 camera: UE Scene-view semantics — RMB fly, MMB focus-orbit, wheel dolly, F focus
Key conflict fix (UE semantics): while RMB is held the keyboard belongs
to the camera, so WASD no longer collides with the gizmo W/E/R tool
hotkeys. Editor mode gates fly movement behind RMB; game mode keeps
always-on WASD. Viewport tool hotkeys (W/E/R/T/F) are suppressed while
RMB is held.

Focus orbit (pivot): MMB drag rotates the camera around a pivot keeping
distance; first orbit picks a pivot 5m ahead on the view ray. Shift+MMB
pans in screen plane (camera and pivot move together). Mouse wheel
dollies toward/away from the pivot with exponential steps (no pivot:
move along view ray). F focuses the selected entity (frame-selection):
pivot set to its position, camera slides along the view ray; orbit
distance persists.

Window gains a polled scroll-delta channel (ConsumeScrollDelta) that
respects ImGui mouse capture; Camera::Update mutates orbit state (no
longer const).
2026-10-03 13:02:55 +08:00
atdunbg 9ff08651f0 editor: revert self-drawn scale gizmo, clip degenerate axes instead
User feedback: the self-drawn scale handles clash visually with the
ImGuizmo translate/rotate rods (different size/style, clearly not one
widget). Keep the library widget for all three ops for visual
consistency and prevent mis-drags by masking degenerate axes via
SetAxisMask:

- translate: axis ~ parallel to camera-to-object line (sin < 0.25)
- scale: axis ~ perpendicular to camera-to-object line (|dot| < 0.1,
  intersection numerator identically zero -> grab fails/values jump)
  OR axis ~ parallel to view direction (|dot| > 0.98, zero screen
  displacement -> value never changes)
- rotate: screen-space math, no degeneracy, no mask

Masked axes are neither drawn nor activatable; a slight view change
restores them. Center uniform-scale square (MT_SCALE_XYZ, pure screen
space) stays available in any view.
2026-10-03 12:46:07 +08:00
atdunbg c9d4eed043 editor: scale gizmo fully self-drawn (screen-space, view-independent)
ImGuizmo scale hover/drag relies on ray-plane intersection with the
axis as plane normal; in front views (axis ⟂ camera-to-object) the
intersection numerator is identically zero, so handles can't be
grabbed and values jump randomly; axes parallel to the view have zero
screen displacement. Submodule is read-only and AxisMask can only hide
degenerate axes, not replace them.

Replace the library scale path entirely with a screen-space gizmo:
- three axis rods (line + end square) + center uniform-scale square,
  constant screen size in any view
- axis drag: mouse delta projected onto the axis' screen direction
- axis pointing at camera: radial mapping (drag away = grow)
- center square: uniform scale (library MT_SCALE_XYZ semantics)
- Ctrl snapping quantizes the result; scale clamped >= 0.01
- pick suppressed while hovering/dragging the self-drawn handles
2026-10-03 12:23:00 +08:00
atdunbg 21b33d7d7f fix(editor): 缩放轴病态判据与平移互补,按 op 分派 AxisMask
缩放独有老问题根因(源码实证):缩放 hover(GetScaleType)与拖拽
(HandleScale ratio 分母)依赖"法线=轴"平面求交,交点距离分子 numer
= -dist·dot(camToObj, 轴)。正对视角相机与物体无横偏时 numer≡0 →
len=0/垃圾 → 杆 hover 不到、ratio 分子分母乱跳(数值随机猛变)。
平移 hover 是纯屏幕距离、平面法线经正交化自适应;旋转是屏幕角数学
——故唯独缩放有此病。SCALE op 下判据换 |dot(camToObj,轴)|<0.1,
ROTATE 不 mask,TRANSLATE 保持 sinθ<0.25。中心块等比缩放
(MT_SCALE_XYZ,屏幕空间数学)不受 mask 恒可用。
2026-10-03 12:11:16 +08:00
atdunbg a130850276 feat(editor): Gizmo 退化轴自动隐藏(调用层 SetAxisMask,UE 语义)
正对视角 + 物体竖直偏移时,单轴拖拽灵敏度 sinθ(轴与相机连线夹角)→0:
拖拽投影系数趋 0("数值不变"),激活平面与鼠标射线共面(IntersectRayPlane
denom→0 返回 -1 被当交点距离)。按灵敏度 <0.25(θ<14.5°)将病态轴
SetAxisMask 隐藏(库公开 API,按轴关绘制+激活,保留含该轴的平面手柄
与中心块兜底),相机移动后自动恢复——可激活性决定可见性,零库改动。
2026-10-03 12:03:07 +08:00
atdunbg 1d360369c9 revert: 删除 SetPlaneLimit 覆盖,Gizmo 参数与 Horizon 对齐(全默认)
对照 Horizon 实现(同官方 ImGuizmo submodule、零定制、Manipulate 全
默认参数)确认库默认行为就是可用的出货行为。0.034 把轴隐藏阈值从
11.5° 抬到 20°,常规视角轴被过早隐藏("箭头又没了");之前 0.35 同
根因。两次阈值折腾全部推翻:自行覆盖库内部可见度参数本身就是错的方向。
2026-10-03 02:27:46 +08:00
atdunbg af241857af fix: 轴可见度阈值 0.35→0.034(量纲修正,0.35 会隐藏所有轴)
前次 0.35 的错误:把阈值当成 0~1 的 sinθ 直接比较。实际库内 gizmo
恒定屏幕尺寸(mGizmoSizeClipSpace=0.1),可见判定 =
sinθ > 阈值/0.1 —— 0.35 ⇒ sinθ>3.5 永假 ⇒ 所有轴消失。

仿真量纲(MVP 复刻 + ScreenFactor 机制确认):⊥视线轴 clip 长度
恒 ≈0.97·screenFactor=gizmoSize;视线向轴 = gizmoSize·sinθ。
0.034 ⇒ sinθ>0.34 ⇒ θ<20° 隐藏(UE 正对轴隐藏语义),≥20° 可拖。
2026-10-03 02:02:37 +08:00
atdunbg 5cc9b14d73 fix: 旋转 snap 单位修正(度非弧度)+ 轴可见度阈值对齐 UE 正对轴隐藏语义
两个实锤问题(源码数学 + 数值仿真定位):
1. 旋转 snap 传了 glm::radians(15°)≈0.26,而库按"度"解释
  (docs/documentation.md:snap angles are in degrees,库内自行
   DEG2RAD)→ 实际步进 0.26° 形同虚设。改传 15.0
2. 正对视角两症状同根因(view-aligned axis degeneracy):
   - 轴⊥视线:悬停检测的射线-垂直平面求交 denom=0 → len=-1 →
     无法 hover(动视角即恢复)
   - 轴∥视线:屏幕投影无一阶位移 → 怎么拖都不动
   默认 mPlaneLimit=0.02 太小,"有高度差"视角下投影 sinθ≈0.17~0.34
   刚好高于阈值 = 看得见但几何上不可操作。提至 0.35:夹角 <20° 的
   轴自动隐藏(UE 同语义),>20° 正常可拖
2026-10-03 01:49:42 +08:00
atdunbg 83c1600947 feat: Gizmo local/world 空间切换 + Ctrl 吸附 + Inspector 轴色手输行
- GizmoState.localSpace(默认 local,模型轴旋转直观),T 键切换
  Manipulate 的 LOCAL/WORLD 模式;无父层级两模式 world 矩阵同源,
  decompose 写回链路不变
- Ctrl 按住吸附(UE Grid Snap 语义):平移 0.25m / 旋转 15° /
  缩放 0.1x;ImGuizmo 约定旋转 snap 单位为弧度
- 视口左上角 drawlist 叠加状态提示(Space/Snap,不占布局行)
- Inspector Transform 三行升级 UE 风轴色输入行(对齐 Gizmo 轴色):
  拖拽粗调 + Ctrl+Click 键入精确值
2026-10-03 00:56:27 +08:00
atdunbg e5161439dd refactor: TransformComponent 旋转存储改四元数(UE FTransform 语义)
欧拉角内部存储每帧矩阵↔欧拉往返,是 Gizmo 拖拽乱跳/缩放串轴的
结构性根源(74d2f6c 的单点公式修复治标不治本)。对齐 UE/Unity
方案:内部存 glm::quat,欧拉只在 UI 表皮。

- Components.h:m_Rotation 改 quat;Compose 用 mat4_cast;
  SetRotation(euler) 转存(q=qy·qx·qz);新增 SetRotationQuat/
  GetRotationQuat;GetRotation() 改派生欧拉(YXZ 序矩阵反解,
  仅 UI 显示/编辑用)
- ViewportPanel:Gizmo 写回 decompose 的 quat 直写,删除整个
  欧拉提取块
- PhysicsWorld:删 QuatToEulerYXZ/EulerYXZToQuat,改 ToJolt/
  ToGlm 分量直转;物理写回 SetRotationQuat;BodyRecord.lastRot
  改存 quat

验证(1 万随机姿态 round-trip):Gizmo 链路矩阵差 7.9e-07
(旧欧拉方案 3.56e-04,好 3 个数量级);万向锁 pitch=±90° 穿
表皮恒定;欧拉表皮误差 4.5e-04 为欧拉参数化固有病态(随
pitch→±90 指数增长,仅影响 UI 显示数值),热路径不经过。
2026-10-03 00:47:11 +08:00
atdunbg 74d2f6cdaa fix: Gizmo 旋转/缩放写回乱跳——eulerAngles 序约定与 Compose YXZ 失配
glm::eulerAngles 假定 XYZ 系约定,与 TransformComponent::Compose 的
YXZ 旋转序不一致:复合旋转下(如 Dynamic 实体被物理写入非零姿态)
decompose 出的角度按错误序重组 → 写回的 R ≠ 拖拽时的 R → 下一帧
Manipulate 拿到歪矩阵 → 旋转乱跳、缩放串轴。

改为按引擎 YXZ 序(R = Ry·Rx·Rz)从旋转矩阵直接提取欧拉角:
- 常规区:atan2 分子分母 cx 约掉,pitch 任意角精确(含 ±90° 邻域)
- float 真奇异(|pitch|=90°):组合角归 yaw、roll=0(UE 同策略),
  ±90° 两支 atan2 符号不同

验证(round-trip 1e4 样本,矩阵 Frobenius 差判据):
- 欧拉链 2.67e-04、全 SRT 链 3.56e-04(float32 极限)
- 万向锁邻域专项覆盖
2026-10-03 00:27:16 +08:00
atdunbg ce5e07879c fix: Gizmo 无法拖动 + Dynamic 提示行顶视口抖动
- Editor::Draw 每帧首调 ImGuizmo::BeginFrame():不调则
  mbOverGizmoHotspot hover 一次杆后永久置位(仅 BeginFrame 清零),
  GetMoveType 恒返 MT_NONE → 杆可见但永远抓不住
- Dynamic 刚体提示改 drawlist 叠加(Image 内左下角、纯英文):
  TextDisabled 占布局行 → 滚动条出现/消失循环 → avail 抖动 →
  渲染分辨率每帧重报(视口疯狂顶动);且默认字体无中文字形
2026-10-02 23:30:04 +08:00
atdunbg 3ec96acc0f feat: Editor Gizmo——ImGuizmo 平移/旋转/缩放操纵杆
- ImGuizmo 接入:Lib/ImGuizmo(src/ 布局)静态库,IMGUI_DEFINE_MATH_OPERATORS
- ViewportPanel:Vulkan→GL 投影转换(Y 翻回 + 深度 [-1,1])、
  SetDrawlist/SetRect 叠加绘制、decompose 写回 TransformComponent
- W/E/R 切换(UE 语义);拖拽中 BlockingMouse 门控(输入归 Gizmo)
- Dynamic 刚体选中提示:物理每帧写回,编辑被覆盖
- 修复:GizmoState.op 初值 0 无轴位(ImGuizmo::TRANSLATE=7)导致杆不绘制
- 点击拾取门控:抓杆时(IsOver/IsUsing)不发 pick 请求
2026-10-02 23:16:52 +08:00
atdunbg 56bb31638e 添加windows构建适配,禁用ImGui多视口功能 2026-10-02 21:53:26 +08:00
atdunbg b0d5ed4550 refactor: ShaderManager 引用计数句柄——shader 生命周期自动化
- ShaderObject 值类型 → ShaderRef(shared_ptr 语义,UE TShaderRef
  对标):拷贝共享所有权,最后一个引用析构时自动销毁 VkShaderEXT,
  持有方零清理代码——"新 pass 忘销毁"整类 bug 被机制消灭
- 删除公开 Destroy 与全部持有方手工 destroyShaderEXT(8 处);
  各类析构只清 sampler/layout/set 等其余原生句柄
- RecordFullscreen 参数收窄为 const ShaderRef&(裸 handle 无法
  再被误传);bind 点统一 GetHandle()
- Outline/SelectionMask 撤掉上一轮补的 Device&(不再需要)
- 验证:5s 探针自动退出 → exit 0、无 VUID-05137 泄漏、无 SIGSEGV
2026-10-02 20:20:07 +08:00
atdunbg 47dd879977 fix: 退出 SIGSEGV 与 VkShaderEXT 泄漏——生命周期三层秩序
- 根因:~Application 体内 Logger::Shutdown() 先于成员析构执行,
  Device 销毁期间 Vulkan validation 回调仍打日志 → spdlog 悬空调用
- 修复:app 置于 main 独立作用域,所有成员析构完毕后再 Shutdown
- 退出序 = waitIdle(~Application 体内)→ 成员析构(shader 等
  Device 子对象销毁)→ Logger::Shutdown
- 顺带注释掉窗口全透明(调试期遗留)
2026-10-02 20:09:12 +08:00
atdunbg 1449bea86d feat: HitProxy GPU 拾取 + UE 选中描边 + 编辑器拾取交互
- HitProxy 拾取:RG 管线渲染实体 id → host readback,点击帧
  PickEntity 精确命中;拾取按视觉几何(Model 联合包围球射线求交),
  与 Collider 解耦(UE/Unity 编辑器语义),命中打印实体名+碰撞体参数
- 选中描边(UE 语义):SelectionMask pass(R8Unorm 完整投影轮廓,
  无深度测试透墙可见)→ Outline pass(边缘检测 + alpha 叠加,
  必须注册在 Tonemap 后);edgeCut 贴视口边缘;widthPx=6 可调
- shader object 实证修复:VUID-08879 同程序 set layouts 超集一致、
  VUID-10065 push block 4×float 平铺、新 pass 析构销毁 VkShaderEXT
- RG:SideEffect() 保活"产出无 RG 内消费者"的 pass 链
- Editor:层次面板点击选中、Inspector 姿态编辑、Stats 面板拾取信息
- 相机 Update 改 const(姿态写入 Transform 引用,Camera 本身无状态变更)
2026-10-02 20:09:05 +08:00
atdunbg 1c4b6f37ff feat: Jolt 物理系统——刚体/碰撞体/物理调试图元 + 引擎时间单源
- PhysicsWorld:Jolt 集成,Dynamic/Kinematic/Static 刚体,姿态写回
  Transform(Unity FixedUpdate 语义,吃 scaled dt,暂停自动冻结)
- 碰撞体:Box(默认/手动 halfExtents)+ Mesh 碰撞体(Static→精确
  MeshShape / Dynamic·Kinematic→ConvexHull,数据源 = Mesh ctor 缓存
  的 CPU 位置副本,退化三角剔除,失败回退 Box 并告警)
- PhysicsDebugPass:物理调试图元每帧上传绘制(debug_line shader)
- Time:引擎时间单源(BeginFrame 落定 FrameTime,scaled/unscaled 分离)
- JoltPhysics 以 submodule 接入(官方 url)
2026-10-02 20:08:54 +08:00
atdunbg bfbb18f1a4 build: meshoptimizer 改为 git submodule(--depth=1)
Lib 依赖一律 submodule 不入库;历史中的在树源码记录已用 filter-branch 全量清洗
2026-09-30 09:22:30 +08:00
atdunbg 20f0035baa feat: ImGui docking Editor + 显示链/帧同步/输入语义重构
- App/editor: Hierarchy/Inspector/Viewport/Stats 四面板 + DockBuilder 默认布局,main.cpp 瘦身为场景搭建与生命周期
- 显示链: Tonemap→PresentTarget 经 ImGui::Image 呈现(零色偏),backbuffer 仅承载 ImGui;Direct/Viewport 双显示模式 + 面板分辨率上报
- 帧首同步 BeginFrameSync: resize/vsync/尺寸/模式意图统一帧首落定(修描述符悬垂);修复 vsync 意图未落 swapchain 导致的每帧重建交换链
- 输入: Editor 每帧判定键盘/鼠标归属(打字让位、Viewport hover/focus 放行、Fullscreen 全放行)
- 材质: roughness 独立槽位 + 非 glTF 金属/粗糙贴图映射;透明队列 depthWrite=FALSE + translucent 独立 shader + scratch 清帧
- FBX: 非蒙皮网格烘焙节点链全局变换(Z-up 旋转/单位换算落顶点)
- Hierarchy 行 ImGui ID 以 UUID 作用域(重名不冲突、改名不丢选中)
2026-09-30 00:24:53 +08:00
atdunbg b25fa29fe5 feat: 透明材质 alpha blending——独立队列 + 线性 over 混合 + 距离排序
透明来源:glTF alphaMode=BLEND(解析期读取,OBJ/FBX 恒不透明——
资产格式定义写死);MASK 后续演进。Material 加透明语义(入缓存键),
程序化测试资产 CreateTransparentPlane(RGBA 纯色纹理)。

SceneRenderer:透明材质实例收集进独立 scratch(仅主视图,不投
阴影——alpha 阴影后续演进),batch 按平均距离降序排序(远→近,
batchMats 同步重排),SSBO 扁平化顺序 = 排序后顺序。

ForwardPass 两段绘制:不透明(无混合 + 深度写)→ 透明(Shader
Object 动态状态切 SRC_ALPHA/ONE_MINUS_SRC_ALPHA 线性 over + 深度
只测不写);velocity 附件透明段直写(透明表面 velocity 即自身运动)。
frag alpha = albedo 贴图 a 直通,预乘在 tonemap 段(hard constraint
不变)。已知边界:TAA alpha 时域混合(透明边缘几帧渐显);同 batch
多实例不做实例级排序。
2026-09-25 16:57:27 +08:00
atdunbg 8dbfb9f554 feat: LOD 运行时开关(ImGui Features 横排,关 = 实例收集恒选 LOD0) 2026-09-25 16:39:16 +08:00
atdunbg a975845214 perf: BuildFrame 收集段优化——实体级粗剔除 + 数组化 batch 查找 + 合并矩阵写
定位数据(100 背包 + SunTemple,RTX 5060):GPU 全帧仅 ~3ms
(Forward 1.91 + Shadow 0.82),CPU BuildFrame 收集段 5.94ms 才是
帧率瓶颈。三段细分测量:pre 0.2 / each+cull 5.0 / flatten 0.4。

优化(collect 5.94 → 4.6ms,-23%):
1. Mesh 全局连续槽位(GetSlot);ViewScratch batch 查找 hash →
   数组寻址(slot × kMeshLodCount 槽)
2. Model 级联合包围球(各 mesh bounds 并集):实体在四视图全部
   不可见 → 跳过整个 mesh 循环(多 mesh 模型的粗剔除)
3. worlds/prevWorlds 合并为 stride-2 InstanceMatrices 单次写
   (每实例每视图 2 次随机堆访问 → 1 次,cache miss 减半)

遥测:Renderer::GetCollectMs 暴露收集段耗时。剩余 4.6ms 的进一步
压缩(并行收集 / GPU-driven culling)单独立项。
2026-09-25 08:08:51 +08:00
atdunbg d9b1742327 feat: LOD 网格分级——meshopt 解析期简化 + 按距离/级联选级
vendored meshoptimizer 1.2(submodule 环境故障改直接 vendor)。Mesh
多 LOD:各级索引连续拼进同一 index buffer(共享顶点,LodRange 区间
选择),解析期 meshopt_simplify 生成链(LOD0=原始,每级目标减半,
相对误差 1e-2,小网格复用上级);都在 worker 线程,加载零额外卡顿。

SceneRenderer 按 (mesh, LOD) 聚合 batch(lookup key = 指针<<8|lod):
主视图按屏占比近似 radius/dist 选级(阈值 10/20 倍半径);阴影级联
按级联序降级(越远越简)。Draw 按 batch.lod 选索引区间。

A/B 对比(100 背包 10×10,RTX 5060 Laptop,vsync off):
- 纯背包:LOD 开 93-95 fps / 关 90-107 fps —— 无显著差异
- 加 SunTemple:LOD 开 52-56 / 关 51-55 —— 无显著差异
结论:当前场景瓶颈不在顶点数(5060 顶点吞吐富余),LOD 基础设施
保留(几何复杂资产/低端 GPU/更大场景生效),帧率问题转向 Tracy
GPU 时间线定位真实瓶颈。

坑:LodRange 记录"LOD 不足复用最后一级"时 first 不得继续累加
(buffer 无重复数据,累加必越界——VUID-08798 抓的)。
2026-09-24 20:53:19 +08:00
atdunbg 34b593f734 fix: TRACY_ON_DEMAND——GPU context 宣告随连接重发
GpuNewContext 消息只在 TRACY_ON_DEMAND 下 DeferItem(连接后重发)。
程序启动即建 Tracy GPU context 而 GUI 晚连接,宣告丢失 → GUI 不认识
GPU context → pass zone 全部不显示(只见 CPU lane)。启用按需剖析:
连接建立后的帧才活跃,语义即 tracy 官方推荐用法。
2026-09-24 20:16:30 +08:00
atdunbg 3bfe28b027 feat: Tracy GPU 时间线——RG pass 级 GPU zone
RenderGraph 新增 IGpuProfiler 可选注入(pass 边界 Begin/End 钩子 +
帧尾 Collect,zone 覆盖 barrier+lambda+postBarriers),RG 本体不感知
任何 profiler 实现。Renderer 侧 TracyGpuProfiler 实现:TracyVkContext
(校准用临时 cb,池必须带 RESET_COMMAND_BUFFER_BIT——tracy 校准流程
对同一 cb 连续 begin 三次)+ TracyVkZoneTransient(pass 名是运行时
字符串)。TRACY_ENABLE 关闭全链路退化为空。

用法:Tracy GUI(0.14.1 配对)连接后按 pass 名看 GPU 段时间线,
与 ImGui 数表互补;LOD 调优的测量前置。
2026-09-24 19:45:17 +08:00
atdunbg 99e6712cdf feat: 模型异步加载——assimp 解析移后台线程(SunTemple 3s 卡顿消除)
Model 拆两段式:ParseFromDisk(static 纯 CPU,assimp 解析 → 几何 +
材质引用,worker 可调)/ FromParsed(GPU 构建:纹理上传 / mesh 缓冲 /
材质描述符,主线程),同步路径等价两段串行。第二个异步任务类型落地,
线程收割骨架抽成 Core/AsyncTask<T>(thread + atomic + Take,覆盖式
Run;pending 接力语义留调用方)。

实测(SunTemple 1056 mesh):解析 2.5s 全程在 worker,主线程持续
渲染;GPU 构建 0.56s。assets 层经 IAssetUploader/AssetManager 重载
传预解析产物(路径缓存去重,路径已缓存则产物丢弃)。

已知边界:纹理解码(backpack 4k PNG ~1s)仍在 GPU 段主线程——
Texture 需支持预解码像素注入,后续演进。
2026-09-24 19:31:14 +08:00
atdunbg 6df3be2846 feat: 透明窗口 + 实例预算扩容(修复斜光阴影静默截断)
透明窗口:SceneSettings 加 ClearColor/DrawSkybox,随 FrameSceneData
每帧下发到 SkyboxPass(替换硬编码清屏色);GLFW 透明帧缓冲已开,
tonemap 预乘 alpha 直通已有,链路全通。TAA alpha 时域混合为已知边界。

实例预算:kMaxInstances 16384 → 65536(四视图共享预算,100 实体
斜光时需求 16.4k 超限,cascade 2 实例被静默丢弃 = 用户看到的
"拖灯光方向部分背包阴影消失")。截断状态每帧落遥测 + ImGui 红字
(原 warn-once 对动态穿越阈值的场景等于没有告警)。
2026-09-24 18:54:01 +08:00
atdunbg fa9681aa41 fix: GPUDriven batch 实例连续性——多实体共享 mesh 时实例数组错乱
实例数组按实体序收集([实体A的mesh][实体B的mesh]),而 GPU 端
batch 语义要求同一 mesh 的实例连续(firstInstance 基址 +
gl_InstanceIndex 直查 SSBO)。多实体共享 Model 后 mesh i 的各实例
分散在数组各处,batch.first+i 落到别的 mesh 的变换上——第二颗
背包的几何全画到第一颗的相邻 mesh 位置(碎裂/缺失/近看缺块)。

修:剔除期实例先归入各自 batch(ViewScratch 持有 per-batch 实例
列表),拼接时按 batch 扁平化为连续区间,batch.first = 扁平化基址。
单实体路径行为不变(每 mesh 单实例天然连续),容量跨帧复用。
2026-09-24 16:23:47 +08:00
atdunbg 25a365fce7 feat: 环境异步加载——HDR 解码移后台线程,运行时切换不卡主线程
decode 是切换耗时大头(4k ≈ 130-160ms),搬出主线程后运行时切换
只剩 GPU 段(~40ms)。IblGenerator 拆两段式:DecodeHdr(static,
纯 CPU 无线程状态,worker 可调)+ FromDecoded(GPU 生成,主线程);
FromHdrEquirect 保留为同步便捷路径。Assets 层经 IAssetUploader
重载 + AssetManager 重载传递预解码产物(路径已缓存则丢弃去重)。
App 层单 in-flight 状态机:worker 只产出纯 CPU 像素数据,忙肋期
新请求记 pending 接力(只保留最终意图),OnUpdate 每帧收割 +
SetEnvironment 换绑,析构 join。

实测(RTX 5060 Laptop,4 实例网格 + 运行中连切 3 环境):
~180fps 无感知尖峰;0 validation。
2026-09-24 14:32:39 +08:00
atdunbg 641f79d89e fix: GPUDriven batch lookup 索引语义混淆——多实体共享 mesh 越界崩溃
ViewScratch.lookup 误存 worlds 索引却被当 batches 索引用。单实体时
每个 mesh 恰好只出现一次、两序号恒等,潜伏不炸;多实体共享同一
Model(2x2 实例网格实测)后 worlds 与 batches 分叉,first-insert
用 worlds.size() 索引 batches → vector 断言越界。
修:lookup 存 batches 索引,batch.first(GPU firstInstance 基址)
保持 worlds 索引,两语义各归其位。
2026-09-24 14:32:39 +08:00
atdunbg 08439ed015 fix: ImGuiLayer 析构提前到 Logger Shutdown 之前 + UI 计时段收窄
m_ImGuiLayer 在 Application 析构体显式 reset(否则成员析构序在
Logger::Shutdown 之后);UI 计时范围从 Run 循环收窄到实际 ImGui
构建段(ImGui 未启用时不再计零段)。
2026-09-24 13:44:15 +08:00
atdunbg b65856b8a5 feat: ImGui 调试面板分组整理
Scene/Rendering/Environment/GPU Timings/Frame 五个 CollapsingHeader;
功能开关用 checkbox 条件显隐联动参数(Bloom 勾选才出现 threshold/
strength),无参数开关横排平铺。
2026-09-24 13:44:15 +08:00
atdunbg 1d690067bd feat: IBL 预积分 GPU compute 化 + 环境资产入 Assets 层(动态切换)
CPU 逐像素卷积(venice 4k 阻塞 7.36s)→ 4 个 compute shader 一次性
提交:equirect→cube / irradiance / prefilter×5mip / BRDF LUT。
Cubemap 瘦身为纯资源持有类,生成职责移交 IblGenerator;
环境经 AssetManager 缓存(EnvRef),Renderer::SetEnvironment
动态换绑(RenderPass::OnEnvironmentChanged 广播)。
调试视图 +4:EnvCube / Irradiance / Prefilter / BRDF LUT。

性能前后对比(RTX 5060 Laptop,venice_dawn_1_4k.hdr):
- 改造前 CPU:7.36 s(主线程阻塞)
- 改造后 GPU:HDR decode 127-134ms + 资源 22-35ms + compute 17-19ms
  ≈ 165-190ms(约 40×);Neutral 兜底 ~6ms
- 运行中动态切换环境同量级(ms 级 GPU,decode 占大头)

冒烟:两构建目录 0 警告;运行期 0 validation(含两次环境切换 +
调试视图 9-12 轮播自动化验证)。
2026-09-24 13:23:59 +08:00
atdunbg 705250df76 feat: spdlog 显式初始化——控制台彩色 + 文件双 sink
Application 构造时 Logger::Initialize(APP logger:stderr 彩色 +
HelloVulkan.log 双写,warn 以上立即 flush),析构 Shutdown。
2026-09-24 13:23:52 +08:00
atdunbg a1c6ad20be feat: 特性开关 + Pass 调试视图接口(ImGui 可控,统一帧契约)
三个运行时开关(Shadows/TAA/Bloom)与 9 种调试视图(Final/
TemporalDiff/HDR/TAAColor/Bloom/Velocity/ShadowMap0-2)。

统一设计:开关状态收敛 FrameSceneData 契约,SceneRenderer 唯一
写入、各 pass 只读(零虚接口路由);调试视图经 tonemap push
int 路由已绑定的中间目标(声明超集、运行时零重绑);HDR 域
资源过同一 tonemap 保证观感可比。

- Shadows:ShadowPass lambda 空转 + frag cascadeSplits.w 短路
- TAA:alpha=0 直通 + SceneRenderer 停投影抖动(同一持有者同步)
- Bloom:3 子 pass 空转 + tonemap 强度归零
- Renderer 门面 Get/Set 转发,App ImGui 3 Checkbox + Combo
- PostProcessParams 瘦身为纯数值参数,showTemporalDiff 被取缔
2026-09-24 13:03:00 +08:00
atdunbg 708d714459 fix: 阴影采样器 CompareOp 反转(诊断残留 eGreater)+ 移除截图调试设施
根因:shadow sampler 停留在诊断期的 eGreater——比较语义全反,
全场 shade 被压到 0.3 变暗,唯独真影区 lit=1 反而全亮,
即"影子比环境亮、像光源"的现象。修回 eLess 后影子正常。

顺带清理:第 60 帧截图管线(HDRColor/ShadowMap0 → BMP)、
CPU 投影诊断日志、GetCaptureTarget 及 TRANSFER_SRC extraUsage。
2026-09-24 12:42:24 +08:00
atdunbg d3c3d0cdd9 fix: 影子中镜面高光残留导致"影子比天空亮"
掠射角下 GGX spec 分母 (4·NdotV·NdotL) 变小,HDR 值可达 8+;
阴影只把它乘 0.3,30% 的刺眼白仍比天空亮——观感即"影子发白"。

- spec 项按 shade 重映射(0.3→0,1.0→1):光源被遮挡,其镜面像
  应完全消失;diffuse 保留 0.3 作间接近似
- ShadowPass 改正面剔除渲阴影图(标准 acne 治法):封闭网格只写
  背面深度,正面深度天然比 ref 大一个表面厚度,从根上消自阴影
  acne;代价是单面薄片在光背面时漏写(plane 单面朝上不受影响)
2026-09-24 12:10:15 +08:00
atdunbg 42fd9d5f30 fix: 阴影光源错误绑定 FillLight + 截图 usage 警告 + nodiscard
- LightComponent 增加 castShadow 标志;灯收集排序键改为
  castShadow 方向光 > 方向光 > 点光(此前稳定排序把收集序靠前的
  FillLight 排到 lights[0],KeyLight 方向滑条自然不影响阴影)
- TextureDesc 增加 extraUsage:HDRColor 声明 TRANSFER_SRC,
  消除截图 copy 的 3 条 usage 不匹配运行时警告
- waitForFences nodiscard 返回值显式丢弃,消除编译警告
2026-09-24 11:59:18 +08:00
atdunbg 4d3963e291 fix: 阴影不可见——灯序依赖遍历顺序 + 光比失衡,新增帧截图调试能力
- 灯序修复(根因):entt Each 遍历序不保证创建序,lights[0] 曾落到
  点光(params.x=1)→ shader 阴影分支整个跳过。收集后 stable_sort
  方向光排前,确立"lights[0] = CSM 阴影光源"契约
- 光比失衡:IBL 环境与点光淹没主光阴影。KeyLight 5.0 + exposure
  默认 0.6 + ambientColor 0.07;阴影区环境光同步衰减(简化遮蔽)
- ShadowPass depth bias 0.006(acne 均值形态 vs 推过头丢阴影的折中)
- Renderer 帧截图能力:第 60 帧自动截 HDRColor/ShadowMap 落 BMP
  (ACES+gamma 还原屏幕观感),视觉调试自闭环不再依赖肉眼
- KeyLight 增加 intensity slider(0-20 运行时可调)
2026-09-24 01:49:11 +08:00
atdunbg 8c0a11d2e8 feat: CSM 软阴影——sampler2DShadow 硬件 PCF + 3×3 纹素 tap
- 阴影采样器开启硬件深度比较(compareOp=eLess + linear filter):
  单次采样 = 2×2 深度比较插值,clamp-to-border 白色语义不变
- ShadowFactor 改 3×3 纹素偏移 tap(等效 36 次比较),影中保留
  0.3 环境感并按 PCF 结果插值——阴影边缘软过渡,消除二值硬边锯齿
2026-09-23 23:18:36 +08:00
atdunbg 960bdf2662 refactor: 移除贴图文件名探测——资产读取保持确定性
按用户决定:OBJ/FBX 按材质定义(MTL/assimp 槽位)写死读取,
不做文件名猜测补槽;贴图缺失走默认纹理 + INFO 日志可见。
补槽职责归将来手动材质编辑接口。
2026-09-23 19:51:03 +08:00
atdunbg 2b875c9284 feat: OBJ 全量贴图接入——Material 加 AO 槽 + OBJ 工作流适配
- Material 槽位 4→5(+AO),参数 UBO binding 4→5;shader ambient
  diffuse 项乘 ao(IBL 遮蔽),AO 默认纹理白色
- OBJ 槽落点补全:map_Bump→HEIGHT(5)、map_Ns→SHININESS(7)、
  map_Ka→AMBIENT(3)(assimp OBJ importer 约定)——backpack 的
  法线贴图此前也从未加载
- MTL 导出遗漏(roughness.jpg/ao.jpg 在目录未被引用):约定文件名
  探测补槽(roughness/ao/occlusion × jpg/png),命中留 INFO 锚点
- 因子按工作流分支:glTF 用声明因子(MR 合图 B=金属度);
  OBJ metallic=0(specular 工作流无金属概念),roughness 有图→1,
  无图→Blinn-Phong Ns 换算 R=sqrt(2/(Ns+2))(补偿默认贴图 G=0.5)
2026-09-23 19:49:36 +08:00
atdunbg 9cc36af3f0 fix: 金属渲染塑料感——glTF MR 贴图未加载 + UV 翻转错误
- assimp 5.x glTF2 importer 把 MR 合图落在 METALNESS(15)/
  DIFFUSE_ROUGHNESS(16),不是 UNKNOWN(18)——此前 metallic 恒 0
  全走电介质路径。槽位查找改为优先级数组(先到先得)
- glTF 的 UV 本就是左上原点,aiProcess_FlipUVs 会把全部贴图上下
  颠倒(法线/MR 分布全反)——按扩展名区分 flip
- 材质加载 INFO 日志(贴图槽缺失会静默退化默认纹理,留诊断锚点)
2026-09-23 19:35:06 +08:00
atdunbg d2b878c077 refactor: Cubemap 拆掉 17 参超级构造——资源三元组 EnvTexture
- image/view/显存分配收口为 EnvTexture 单元(RHI 惯例:三者同生共死),
  Cubemap = 4 个 EnvTexture + 2 采样器的组合,构造 17 参 → 8 参
- UploadCube/UploadTexture2D 直接返回完整资源单元(view 创建收进
  helper),工厂函数只做"生成数据 → 上传 → 组装"
- 析构循环成对释放,杜绝半套泄漏;删除无调用者的 Get()/GetFaceSize()/
  GetPrefilteredMipCount()(shader mip 上限以常量契约)
2026-09-23 19:25:32 +08:00
atdunbg bd4ac6aa12 fix: IBL 全黑根因——RadicalInverseVdC 误用 bit_cast,Hammersley 序列错乱
- bit_cast<f32>(bits) 把反转后的整数当 IEEE754 位型解读(LearnOpenGL
  原文 float(bits) 是整数值转换)→ xi.y 越出 [0,1] → sqrt(负数) →
  BRDF LUT NaN + pre-filter 数据全垃圾 → ambient NaN 整屏黑
- 分离出的积分程序实测:texel(0,2) 样本 10 xi.y=2 → NaN;修后无 NaN
- 加 CPU 侧 fp16 NaN 自检(上传前最后一道防线),数据异常拒绝加载
  走中性兜底(不再解引用空指针)
- 中性兜底 1×1 图只建 1 mip(修 mipLevels=5 validation 警告)
- prefilter mip 拷贝偏移改累积制(此前用当前 dim×序号错位)
- prefilter/irradiance 绑定换带 mip 的环境采样器(shadowSampler
  maxLod=0 把 textureLod clamp 到最锐 mip)
2026-09-23 19:18:06 +08:00
atdunbg 6e107ac2ba fix: IBL 模型全黑——prefilter mip 拷贝偏移错位 + 采样器 maxLod 错误
- UploadCube 的 mip buffer 偏移误用当前 mip 尺寸×序号(mip 面递减,
  正确应为累积偏移)→ 各 mip 拷到 staging 错位数据,含未初始化 fp16
  NaN → 污染 ambient 输出整屏黑
- prefilter/irradiance 绑定改用环境采样器(maxLod=mipCount-1):
  shadowSampler maxLod=0 会把 textureLod 显式 lod clamp 到 0,
  pre-filter 永远停在最锐 mip
2026-09-23 19:07:53 +08:00
atdunbg 9269afaa18 feat: IBL 阶段③——pre-filtered cubemap + BRDF LUT,split-sum 镜面项接入
- pre-filter:GGX 重要性采样(Hammersley + van der Corput 位反转),
  128²×5 mip 分级 roughness,采样数 32<<mip 上限 256;UploadCube 泛化
  支持 mip 链([mip: 6面] 紧凑排布逐 mip 逐面拷贝)
- BRDF LUT:128² R16G16Sfloat 积分表((NdotV,roughness)→F 缩放/偏置),
  纯数学与环境无关,中性兜底同样生成;专用 clamp-to-edge 采样器
  (共享 border 白采样器在 uv=1.0 混入白色导致高 roughness 偏大)
- shader:specular = prefiltered(R, roughness×4) × (F·A+B),
  ambient = (diffuse + specular) × ambientColor
- Hammersley 分母修正为当次积分采样数(低 mip 偏差 bug)
- Debug 构建预积分 26s→14s(采样预算收敛 + pow 改乘法链;
  env 1024² 重采样为大头,compute 迁移后归零)
2026-09-23 19:04:25 +08:00
atdunbg fa25cb3348 feat: IBL 阶段②——漫反射辐照度卷积,PBR ambient 接入环境光
- Cubemap 构造期同步 CPU 半球 Lambert 积分(64×32 网格 ≈2048 采样/像素)
  生成 32² irradiance cube(漫反射是超低频信号,分辨率无需高)
- shader:kD 用粗糙度感知 FresnelSchlickRoughness + (1-metallic),
  N 采样 irradianceMap(Set2 binding 3)替换常数 ambient;
  ambientColor 保留为强度/色调乘子
- CreateNeutral 兜底:无 HDR 资产时 1×1 常数 cube,描述符恒有效
- FaceDir 改为返回单位方向(equirect 查表要求,修掉未归一化的畸变)
2026-09-23 18:50:34 +08:00
atdunbg 4b5990e6c8 feat: 环境背景管线(IBL 阶段①)——HDR 等距柱状转 cube + Skybox pass
- Cubemap:stbi_loadf 加载 .hdr,CPU 双线性重采样 6 面(GL 规范方向表),
  f32→fp16 转换后整块上传 eCubeCompatible image(R16G16B16A16Sfloat)
- SkyboxPass:HDRColor 的 first writer,承担清屏职责;无环境贴图自动
  退化为纯清屏(Forward 不受影响)
- frag 逐像素 NDC 反投影取世界方向(invVP push,投影变换不可线性插值)
- Forward color 附件 Clear→Load:几何合成在天空之上
- SceneRenderer 产出 skyInvVP = inverse(proj × mat4(mat3(view)))
2026-09-23 18:37:01 +08:00