Commit Graph
100 Commits
Author SHA1 Message Date
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
atdunbg 89bf4256e8 feat: 解析式 PBR(Cook-Torrance GGX,metallic-roughness 工作流)
- 修 void* 指针算术警告(Texture::InitFromDds)
- Material:slot2 语义切为 metallicRoughness(glTF 约定 G=粗糙度
  B=金属度)+ 新增材质因子参数 UBO(binding 4,per-slot ring,
  创建时写死,metallic/roughness 因子乘 MR 贴图)
- simple_shader.frag:Blinn-Phong → Cook-Torrance(GGX 分布 +
  Smith 几何 + Schlick Fresnel,k=(r+1)²/8 解析光源版);金属不参与
  漫反射;环境光暂为常数(IBL 后续)
- Model:读 aiTextureType_UNKNOWN(glTF MR 落点)+ AI_MATKEY_
  METALLIC/ROUGHNESS_FACTOR(旧资产缺键退化为电介质 0/0.5);
  材质缓存键含因子
- main.cpp:演示场景换回 backpack(旧资产走因子路径验证 PBR 全链路)
- 旧 specular 贴图不再使用(语义被 MR 取代)
2026-09-23 18:01:14 +08:00
atdunbg 2c4efc5165 feat: 多光源——点光源类型(距离衰减,位置来自 Transform)
- LightComponent 加 Type(Directional/Point)+ radius;点光位置取
  实体 Transform(固有组件首次实际受益)
- LightData shader 契约扩为 4×vec4(color/direction/position/params,
  每灯 64B,未用字段置零);UBO 688→816B
- 光照循环按类型分支:方向光无衰减;点光 L 指向光源 + 平方距离衰减,
  衰减半径外跳过;CSM 阴影只来自第 0 盏方向光
- main.cpp:SunTemple 场景加暖橙火坑光 + 冷蓝填充光(点光);
  KeyLight/FillLight 改 designated initializer
2026-09-23 17:46:25 +08:00
atdunbg d8c481ce48 fix: Tracy 主线程命名(未命名线程在 GUI 显示 Unknown) 2026-09-23 16:44:55 +08:00
atdunbg b79df3c60e fix: tracy 子模块钉到 v0.14.1,与 GUI 协议版本对齐
master 开发提交(81c5005)的协议号已偏离 0.14.1 release,
GUI 连接报 incompatible protocol。子模块记录精确 commit hash,
clone --recursive 的机器拿到的一直是锁定的版本
2026-09-23 16:39:06 +08:00
atdunbg b6982d426f feat: Tracy profiler 接入(CPU 帧时间线)
- vendored tracy 子模块;TRACY_ENABLE 全局生效,宏关闭时零开销
- 插桩:Application::Run FrameMark(帧边界)、Renderer::Render、
  SceneRenderer::BuildFrame(ZoneScopedN)+ GPUDriven Collect
  (ZoneNamedN——同作用域第二个 zone 不能用 ZoneScopedN,变量名
  固定会重声明)、Scene::UpdateTransforms
- 覆盖 CPU 侧关键路径:帧时间线 / 场景收集 / transform 重算 / 录制提交
- 用法:游戏运行时打开 Tracy GUI(release 下载)即自动连接(8086 端口)
2026-09-23 16:10:46 +08:00
atdunbg 7d137fa9b1 refactor: Transform 字段封装 + dirty 标记(对齐 UE USceneComponent 真实现)
- 修正上一版"全量暴力重算":UE 实为 dirty 标记 + 惰性计算,前提是
  字段私有(SetRelativeLocation 等 setter 标脏 + 子级传播)
- TransformComponent:SRT 字段私有化,Set/Get/World()/IsDirty 访问器;
  Set* 变更才标脏;AddRotationY 便捷接口
- Scene::UpdateTransforms 只重算脏实体——静止实体零成本;
  层级(父子)传播在脏标记基础上扩展
- 迁移 Camera/SceneRenderer/main.cpp 到封装 API
2026-09-23 15:35:20 +08:00
atdunbg 57ab8901a9 refactor: transform world 矩阵改由帧循环统一重算,删除手动 Compose
- Scene::UpdateTransforms():渲染前全量 Compose(UE transform-update
  阶段模式),Application::Run 在 FlushDestroyQueue 后调用
- 用户代码永远不需要手动 Compose——Set*/直接写字段均安全;
  Compose 降级为引擎内部调用
- 选择全量而非 dirty 标记:字段直接写(t.rotation.y = x)无法可靠
  追踪脏,无条件重算零脚枪;万级实体内成本可忽略,更大规模再引入
  dirty+封装
- main.cpp/Camera 所有手动 Compose 调用点删除
2026-09-23 15:26:58 +08:00
atdunbg 1bc44e261b refactor: Camera 位姿统一到 TransformComponent(单一事实源)
- Camera 删除内部 position/yaw/pitch 状态:Update 把鼠标旋转与 WASD
  移动直接写回实体 Transform(rotation.y=yaw / rotation.x=pitch,
  移动基向量由实体旋转推导,有变更才 Compose)
- GetView(transform) = 实体世界矩阵的逆——相机即实体,视图矩阵无
  第二来源;Scene::GetPrimaryCamera 可选带出实体 Transform
- 默认朝向约定变化:rotation.y=0 = 看向 -Z(与旧 yaw=-90 等价)
- 消除 CameraComponent 位置与 Transform 脱节的遗留债
2026-09-23 15:14:29 +08:00
atdunbg 019c17c7e7 feat: 实体出生即挂 TransformComponent(UUID + Transform 固有化)
- CreateEntity 自动挂 IdComponent(已有)+ TransformComponent(identity),
  对齐 UE/Unity 语义(GameObject↔Transform 不可分离 / AActor 必有
  RootComponent)——游戏实体的本质是"空间中存在的东西"
- 调用点 AddComponent<Transform> 改 GetComponent(重复 emplace 会断言)
- 遗留不一致记录在案:CameraComponent 位置存于 Camera 内部而非
  Transform,后续统一
2026-09-23 15:07:00 +08:00
atdunbg 0ed7b4c5b8 perf: CSM 每级联独立剔除 + 光向 caster pad
- 场景剔除从 [主|阴影] 两视图扩展为 [主|级联0|级联1|级联2] 四视图:
  每级联只画自己盒内的 batch,消除原先 3× 全量阴影 draw 的浪费
- 实例数组拼接为四段,batch.first 加段基址保持段内连续;
  ViewScratch 分桶收集复用容量,稳态零堆分配
- ComputeCascades 沿光方向扩展级联深度范围(pad=30)——切片盒外
  caster(高树/城墙)投影进级联的影子不会被剔除裁掉,per-cascade
  剔除正确性的前提;只扩 z 不缩 XY,分辨率不受影响
- 遥测 shadow 计数 = 各级联求和
- SunTemple 1056 mesh 全量加载,validation 零警告
2026-09-23 14:59:08 +08:00
atdunbg c54f60c5ea feat: 材质系统 v1——4 纹理槽(albedo/normal/specular/emissive)+ 法线贴图
- Material:单贴图 → 4 槽(binding=Slot 枚举),MakeCacheKey 组合键
  保持材质去重(同贴图组合共享描述符集)
- Model:读取 aiTextureType DIFFUSE/NORMALS/SPECULAR/EMISSIVE 四类路径;
  aiProcess_CalcTangentSpace 生成切线(Vertex +tangent 属性,location 3);
  每槽默认纹理兜底(白/平法线(128,128,255)/黑/黑,1x1)
- 纹理 sRGB 语义按槽位:颜色图 SRGB 变体,数据图(Normal/Specular)线性
  UNORM——Texture 路径加载加 srgb 参数;修复 InitFromPixels 图文 format
  不一致的 validation 警告
- UBO 尾部追加 cameraPos(Blinn-Phong V 向量)
- simple_shader.frag:TBN 法线贴图(无切线/平法线退化回几何法线)+
  Blinn-Phong 高光(specular 图强度系数)+ 自发光直加
- SunTemple 实测:148 张 DDS 全槽位加载(96 张线性变体)、1056 mesh、
  validation 零警告
2026-09-23 14:50:10 +08:00
atdunbg 8253c31ad5 fix: 视锥剔除平面归一化(透视因数误剔)+ 材质去重 + 描述符池可增长
- Frustum::FromMatrix 平面归一化:Gribb-Hartmann 提取的 left/right/
  bottom/top 法线模长含 1/tan(fov/2) 因子(>1),未归一化时包围球
  距离测试被放大,视锥边缘物体被误剔(SunTemple 全场景可见)——
  修正注释:必须传 [0,1] 重映射矩阵
- 材质去重:Model 新增 MaterialCache(按贴图路径共享 Material),
  Mesh 改持 Material* 非拥有指针。1056 mesh 渲染只需 48 个描述符集
  (对齐 UE/Unity 材质实例化思路,大场景第一道闸门)
- DescriptorManager 池改可增长分块(chunk=4096,OutOfPool/Fragmented
  自动续块):材质集数量不可预估(1G 资产可能上万),固定上限迟早爆
2026-09-23 14:32:35 +08:00
atdunbg a0c92af5db feat: DDS 纹理加载器(BC 压缩直传 + mip 链,参考 Horizon 实现)
- Assets::Dds 解析器:中性 DdsFormat 枚举(Assets 层禁 Vulkan 头铁律),
  支持 legacy fourcc(DXT1/3/5/ATI1/2)+ DX10 头(BC1..BC7/RGBA8/BGRA8)
  + 未压缩 32bpp + 2D mip 链;数据不解码,直接上传由硬件采样解码
- Texture:按扩展名分发(.dds → InitFromDds);渲染侧负责格式映射与
  sRGB 语义(颜色图走 SRGB 变体,BC4/5/6H 数据图保持线性);view/sampler
  覆盖完整 mip 链;逐 mip copyBufferToImage(紧凑排布)
- Model::GetOrLoadTexture:FBX/DCC 路径反斜杠归一化(否则 Linux 全挂)
- DescriptorManager 池 1000 → 4096(SunTemple 1056 材质集打爆过;
  材质去重属材质系统任务)
- main.cpp 临时挂 SunTemple 场景验证:48 张 DDS 全部加载(2048²、12 mips),
  1056 mesh 渲染正常,validation 零警告
2026-09-23 14:21:39 +08:00
atdunbg 0328d4aaeb feat: CSM 级联阴影(3 级联 × 1024²,实用分割 + 纹素对齐)
- SceneRenderer::ComputeCascades:相机视锥实用分割(λ=0.6)→ 反投影
  取切片角点 → 光空间 AABB 拟合 → 纹素对齐(消除相机移动时阴影边缘
  闪烁)→ [0,1] 深度重映射
- Shadow pass 单 pass 渲 3 个 region,每级联 push 各自光空间 VP
- Forward:UBO lightSpace[3]+cascadeSplits(视图深度选级联),Set2
  三张级联图按 binding 0/1/2 采样;vertex 输出 fragViewDepth
- 光源视锥剔除用最远级联(覆盖全阴影范围);双视锥独立剔除铁律保留
- 旧单张 2048 全场景图退役;近处级联分辨率 ~128px/unit(约 2× 提升)
- 编译零错误、validation 零警告、8 秒冒烟通过(10 pass 全活跃)
2026-09-23 13:48:43 +08:00
atdunbg 27b7f8785c feat: TAA 时域抗锯齿(velocity + 邻域 clamp + 子像素抖动)
- Velocity buffer:Forward 第二颜色附件,vertex 计算 clip 空间运动向量
  (UBO 尾部追加 projNoJitter/prevView/prevProj;上一帧实例矩阵 SSBO
  Set0 binding2,与当前实例数组平行对齐——转动的物体速度正确不鬼影)
- 子像素抖动:SceneRenderer Halton(2,3)×8 序列,抖动只进光栅化投影
- TAA resolve pass:velocity 反推历史位置 + 3×3 邻域 clamp 去鬼影 +
  mix(cur,hist,0.9) 时域累积;TAAColor 自引用历史
- RG 历史读补 post-barrier:采样后恢复镜像 resting layout(TAA 的历史
  是 ColorAttachment layout,不恢复则下帧 entry oldLayout 脱节)
- 管线:Forward(HDR+Velocity) → TAA(TAAColor) → Bloom/Tonemap 改源 TAAColor
- 编译零错误、validation 零警告、8 秒冒烟通过(10 pass 全活跃)
2026-09-23 13:29:31 +08:00
atdunbg a29aed2803 feat: Pause Animation 开关(帧历史 diff 冻结验证用) 2026-09-23 13:11:29 +08:00
atdunbg c75832e73e feat: RenderGraph 帧历史资源(ReadHistory)+ TemporalDiff 验证 pass
- PassBuilder::ReadHistory:读上一帧槽位 ((slot-1+F) mod F) 的物理镜像,
  TAA/运动模糊等时域效果的前置能力,RG 最后一个已知边界补齐
- 历史读不建拓扑边(跨帧依赖由同队列提交序保证)、不入本帧状态链、
  不改生命周期;剔除照常参与(生产者存活判定)
- 历史 barrier:首帧走 first(old=Undefined),之后走 steady 且
  src=ALL_COMMANDS|MEMORY_WRITE 显式闭合跨帧可用性+可见性;
  有历史读的资源,常规 entry steady 的 src 同样升级(历史读打破了
  同槽位 fence 全等保前提)
- TemporalDiff 调试 pass:|上一帧−当前帧|×增益,静止=黑/运动边缘发光
  即链路正确;Tonemap binding2 经 push 开关切调试视图
- 编译零错误、validation 零警告、8 秒冒烟通过(9 pass 全活跃)
2026-09-23 13:05:10 +08:00
atdunbg 0de8498350 feat: 显存分配器迁移到 VMA(P1 路线第一项)
- vendored VulkanMemoryAllocator 子模块,唯一实现编译单元 VmaImpl.cpp
- MemoryAllocator 重写为 VMA 后端:接口从"Allocate(reqs)+手绑"升级为
  "创建即分配即绑定"(CreateBuffer/CreateImage/Destroy/Flush),
  VMA 要求资源与内存成对释放,约束成单一出口
- Buffer/Texture/RenderGraph 三处调用方迁移;staging 持久映射 + 按分配
  精确 flush(原来按整块 flush)
- 获得:TLSF 通用分配、DedicatedAllocation 自动启用、memory budget 感知、
  内存别名基础(TAA/复用前置)
- 编译零错误、validation 零警告、8 秒冒烟通过
2026-09-23 12:51:11 +08:00
atdunbg 6f10fd624d chore: gitignore 屏蔽本地 Docs 目录 2026-09-23 12:43:42 +08:00
atdunbg 718a658f5d refactor: 解耦重构——还清四处架构债(对齐 UE/Unity 分层)
- SceneRenderer:场景→渲染数据翻译层(UE FSceneRenderer / Unity CullingResults
  模式),收口相机/灯光/光空间矩阵/双视图剔除,Renderer 只编排
- pass 对象化(Unity ScriptableRenderPass 模式):8 个 pass lambda 拆为
  Passes::{Shadow,Forward,Bloom,Tonemap,ImGui,Present},BuildGraph 超级函数消失;
  SceneGpu/FullscreenGpu 承接共享装配
- 中性 AssetRef(Core 层):ECS 组件不再依赖 Assets 模块(Unity InstanceID 思路)
- IAssetUploader 接口:AssetManager 不感知 Vulkan 具体类,上传服务由渲染层
  VulkanAssetUploader 提供
- Renderer 公共 API 不变(App 层零改动),编译零错误 validation 零警告
2026-09-23 12:02:49 +08:00
atdunbg 91880d88fe fix: tonemap 输出预乘 alpha 匹配 PRE_MULTIPLIED 合成模式 + 调整地面位置 2026-09-23 11:35:05 +08:00