Commit Graph
161 Commits
Author SHA1 Message Date
emil28092005 203c22fa98 fix: feed mouse input to ImGui — sliders and buttons now clickable
- ImGui.GetIO().MousePos set from IInputState.MouseX/MouseY each frame
- ImGui.GetIO().MouseDown[0-2] set from MouseLeft/Right/Middle
- DisplaySize also updated each frame (was only set at init)
- Done before BeginImGuiFrame() / ImGui.NewFrame()
2026-06-18 21:50:08 +03:00
emil28092005 29a58d204b feat: ImGui shadow parameters panel with clipboard copy
- Push constant block expanded: 160B → 176B (added vec4 shadowParams)
- shadowParams: x=bias, y=sampleRadius, z=farPlane, w=unused
- Fragment shader: bias and sampleRadius now read from push constants
  (dynamic, adjustable at runtime)
- Shadow.frag: uses pc.shadowParams.z for farPlane (dynamic)
- Slope-dependent bias: bias + bias*2*(1-NdotL) — adaptive
- VulkanRenderer: public ShadowBias, ShadowSampleRadius, ShadowFarPlane properties
- Program.cs: ImGui 'Shadow Parameters' panel with sliders:
  - Light Intensity (0-100), Range (5-100), RGB color
  - Shadow Bias (0.001-0.5), Sample Radius (0.001-0.1), Far Plane (10-120)
  - 'Copy Parameters to Clipboard' button — outputs all values as text
- 227 tests, all pass
2026-06-18 21:48:38 +03:00
emil28092005 8918126c58 fix: increase shadow cubemap resolution to 2048 — sharper edges
- ShadowMapSize: 1024 → 2048 (per face, 6 faces)
- 4x more texels per face — less aliasing on cube edges
- Memory: 6 * 2048² * 4B (R32) + 6 * 2048² * 4B (D32) = ~192MB
2026-06-18 21:42:23 +03:00
emil28092005 2454af1c89 fix: balanced shadow bias — no acne, no peter-panning
- Fragment bias: 0.08 + 0.15 * (1 - NdotL) — smaller base, moderate slope
  (was 0.3 + 0.5 — too much → peter-panning, shadows detached from objects)
- vkCmdSetDepthBias: 1.75/0/2.5 (was 2.5/0/3.5 — too aggressive)
- Removed early exit logic — was causing hard shadow edges
- Always run 16-tap PCF for consistent soft shadow quality
- Smaller sample radius: clamp(0.015 * dist/20, 0.003, 0.05)
2026-06-18 21:40:43 +03:00
emil28092005 23b38f8f17 fix: increase shadow bias — slope-dependent + larger depth bias
- Fragment shader: bias = 0.3 + 0.5 * (1 - NdotL) — slope-dependent
  Grazing angles get larger bias to prevent acne on cube faces
- vkCmdSetDepthBias: 2.5/0/3.5 (was 1.25/0/1.75) — more aggressive
  polygon offset for shadow map rendering
- Early exit tolerance reduced from 4x to 3x bias
2026-06-18 21:38:48 +03:00
emil28092005 7e33b64ac9 feat: soft shadows — 16-tap Poisson disk PCF on cubemap
- 16 precomputed Poisson disk offsets on tangent plane
- Tangent basis built from sampling direction (cross product trick)
- Sample radius scales with distance: closer = sharper, farther = softer
  radius = clamp(0.02 * (dist / 20), 0.005, 0.08)
- Early exit: skip PCF if clearly lit or clearly shadowed (performance)
- Bias increased to 0.15 for cubemap distance comparison
- Only triangle.frag changed — no C# or Vulkan resource changes
- 227 tests, all pass
2026-06-18 21:36:30 +03:00
emil28092005 f761973443 fix: Vulkan cubemap face directions — Y-up vectors corrected
Root cause: used OpenGL cubemap conventions (up=+Y) instead of Vulkan (up=-Y)
for X and Z faces. Also applied M22 *= -1 flip to shadow projection which
is wrong — that flip is only for swapchain rendering, not for offscreen
cubemap rendering.

Fix:
- Faces 0,1,4,5 (+X,-X,+Z,-Z): up = (0,-1,0) — Vulkan cubemap convention
- Face 2 (+Y): up = (0,0,1) — was (0,0,-1), now correct
- Face 3 (-Y): up = (0,0,-1) — was (0,0,1), now correct
- Removed M22 *= -1 from shadow projection (not needed for offscreen cube)
- 227 tests, all pass
2026-06-18 21:32:18 +03:00
emil28092005 a2000b55b5 feat: cubemap shadows with R32_SFLOAT color attachment — linear distance
Root cause of broken shadows: depth buffer stores non-linear NDC depth,
not linear distance. closestDepth * 60.0 was wrong conversion.

Fix: switch from depth-only to R32_SFLOAT color attachment approach:
- Shadow vertex shader outputs world position to fragment
- Shadow fragment shader writes length(fragPos - lightPos) / farPlane
- Main fragment shader samples cubemap, multiplies by FAR_PLANE=60
- Separate color cube (R32_SFLOAT, sampled) + depth cube (D32_SFLOAT, depth test)
- Shadow pipeline: 1 color attachment (R) + depth attachment
- Color clear = 1.0 (max distance), depth clear = 1.0

Tests: 227 total, all pass
- ShadowMapFaceDirectionTests: 6 face directions, 90° FOV, up vectors,
  valid matrices, far plane consistency
- ShadowShaderTests: all 6 SPIR-V shaders exist
2026-06-18 21:27:48 +03:00
emil28092005 a572f9d360 fix: transition all 6 cubemap layers — shadows now work on floor
Root cause: TransitionImageLayoutDepth used LayerCount=1, so only
layer 0 of the 6-layer cubemap was transitioned to ShaderReadOnlyOptimal.
Layers 1-5 stayed in Undefined/DepthStencilAttachmentOptimal → sampling
returned garbage → no shadows on surfaces facing those directions.

Fix: added layerCount parameter to TransitionImageLayoutDepth (default=1).
Shadow cubemap transitions use layerCount=6.
2026-06-18 21:15:50 +03:00
emil28092005 fa937ecd94 feat: cubemap shadow mapping — true omnidirectional point light shadows
- VulkanShadowMap: 1024x1024x6 layer D32_SFLOAT cube image
  - CubeCompatible flag, cube view for sampling, 6 face views for rendering
  - GetFaceViewProj: 6 directions (+X, -X, +Y, -Y, +Z, -Z) with correct up vectors
- 6 shadow render passes per frame (one per cube face)
- Fragment shader: samplerCube instead of sampler2D
  - Shadow: direction from light to fragment, distance comparison
  - No more perspective frustum limitation — omnidirectional shadows
- shadow.vert: same push constant block (160B), uses lightViewProj per face
- triangle.vert: removed fragLightSpacePos (not needed for cubemap)
- Reduced shadow map size to 1024 (6x memory vs single 2048)
- Floor/Grid excluded from shadow casting
2026-06-18 20:35:01 +03:00
emil28092005 c32c75cd69 fix: single push constant range (0-160, Vertex|Fragment) — fixes validation errors
- Vulkan spec: each stage can only appear in ONE push constant range
- Merged 3 ranges into 1: offset=0, size=160, stageFlags=Vertex|Fragment
- Both main and shadow pipelines use same single range
- Push constants packed into 160-byte buffer and sent in one call
- Shadow pass: restored vertex/index buffer binding + push constants
- Fixes: device lost, validation errors, missing shadow geometry
2026-06-18 20:21:54 +03:00
emil28092005 dd86f6e9ad fix: exclude Floor and Grid from shadow pass — no more blocked light
- drawCalls now include castShadow flag (bool)
- Floor and Grid entities: castShadow=false (skip in shadow pass)
- Shadow pass: foreach with 'if (!dc.castShadow) continue;'
- Main pass: renders all objects (floor receives shadows but doesn't cast)
2026-06-18 20:19:16 +03:00
emil28092005 df4b30ecc3 feat: light moves in chaotic circle — dynamic shadows
- Light position animated with sin/cos combinations at different frequencies
- XZ plane: figure-8 pattern (sin*0.7 + cos*0.3, cos*0.6 + sin*0.4)
- Y: oscillates 7-17 with sin*0.5
- Shadows move dynamically as light orbits the scene
2026-06-18 20:15:36 +03:00
emil28092005 d4eafe1f31 fix: reduce light intensity from 50 to 15 2026-06-18 20:13:46 +03:00
emil28092005 7c33a52ff7 feat: static light at top (y=20) instead of physics ball
- Light moved to (0, 20, 0) — static, no physics body
- Intensity 50, range 60, warm white color
- Shadow map renders from this fixed position
- LightBall removed, replaced with MainLight entity
2026-06-18 20:12:59 +03:00
emil28092005 e6e305dfa9 feat: shadow mapping — depth-only render pass from light POV + PCF
- VulkanShadowMap.cs: 2048x2048 D32_SFLOAT image, sampler (clamp-to-border),
  separate shadow pipeline (depth-only, no color, depth bias enabled, CULL_NONE)
- Shadow shaders: shadow.vert (lightViewProj * model * pos), shadow.frag (empty)
- Main shaders updated: fragLightSpacePos output from vertex, PCF 3x3 in fragment
- Push constants expanded to 160B: model(64) + lightPos(16) + lightColor(16) + lightViewProj(64)
- 3 push constant ranges: Vertex(model), Fragment(light), Vertex|Fragment(lightViewProj)
- Descriptor set: 2 bindings — UBO(vp) + CombinedImageSampler(shadowMap)
- Shadow pass: before main pass, renders scene depth from light position
- Image transitions: shadow map UNDEFINED→DEPTH→SHADER_READ_ONLY each frame
- vkCmdSetDepthBias(1.25, 0, 1.75) for acne prevention
- Light VP: CreateLookAt(lightPos, origin, up) * Perspective(60°, 1.0, 0.1, 60)
- 0 validation errors on build
2026-06-18 20:09:32 +03:00
emil28092005 1dde764342 fix: move light data from UBO to push constants — fixes std140 layout issue
Root cause: GLSL std140 layout adds padding after mat4 for vec4 fields,
causing light data to be read at wrong offsets → NaN/artifacts.

Fix:
- UBO back to 64 bytes (mat4 vp only)
- Light data (pos + color, 32 bytes) sent via push constants at offset 64
- Two push constant ranges: Vertex (0-64, model) + Fragment (64-96, light)
- Both vertex and fragment shaders read from same push_constant block
- PBR shader fully restored with Cook-Torrance BRDF
- Dynamic point light follows physics ball
2026-06-18 19:53:45 +03:00
emil28092005 ad8a14a4f7 debug: hardcoded light in fragment shader — no UBO light data
Light at (0,10,0) hardcoded in GLSL. Tests if lighting math works.
If light works: problem is UBO layout for light data (std140 padding).
If artifacts: problem is in fragWorldPos or fragNormal.
2026-06-18 19:50:20 +03:00
emil28092005 7ed81040ed debug: simple diffuse point light — test if light data reaches fragment 2026-06-18 19:47:29 +03:00
emil28092005 b162d681aa debug: albedo-only fragment shader — tests if vp matrix works with 128B UBO
If objects are colored: vp matrix is fine, problem is in lighting
If objects are black: vp matrix is broken by 128B UBO change
2026-06-18 19:46:05 +03:00
emil28092005 4771371021 fix: restore MemoryCopy for UBO + PBR shader with point light
- UBO packing: MemoryCopy for vp (64B) + lightPos (16B) + lightColor (16B)
- PBR fragment shader with Cook-Torrance BRDF + point light
- No directional light, near-zero ambient — only point light illuminates
- Guards against div-by-zero in attenuation and specular
- Depth buffer properly configured in pipeline + rendering info
2026-06-18 19:43:31 +03:00
emil28092005 e37e7a4eed debug: fragment shader outputs light position as color — test UBO
- If objects are colored (not black/artifacts), UBO data reaches fragment shader
- Also: manual float-by-float UBO packing instead of MemoryCopy
- vp matrix packed as M11..M44 (row-major, no row_major in GLSL = transpose)
2026-06-18 19:36:57 +03:00
emil28092005 ff8bec00f6 fix: use MemoryCopy instead of Marshal.StructureToPtr for UBO packing
- Marshal.StructureToPtr may not handle Matrix4x4 correctly (record struct)
- Replaced with MemoryCopy from local copies of vp, lightPos, lightColor
- Removed per-frame light debug logging
- Simple diffuse shader still active for testing
2026-06-18 19:35:44 +03:00
emil28092005 321b1f62cc debug: simple diffuse fragment shader to test point light UBO 2026-06-18 19:33:37 +03:00
emil28092005 aa214cb777 fix: UBO descriptor visible to fragment shader — point light now works
Root cause: descriptor set layout binding had stageFlags=Vertex only.
Fragment shader needs pointLightPos/pointLightColor from UBO but couldn't
access it. Changed to Vertex|Fragment.
2026-06-18 19:29:57 +03:00
emil28092005 905dd839c8 fix: remove static MainLight, increase LightBall intensity to 30, range 40
- Only one light source: the physics LightBall
- Intensity 30 (was 20), range 40 (was 30) for stronger illumination
- Static MainLight removed so renderer picks the dynamic ball
2026-06-18 19:28:17 +03:00
emil28092005 6a7d6ee3eb feat: single point light only — dark scene, dynamic light ball visible
- Removed directional light from fragment shader
- Ambient reduced to near-zero (0.01) — scene is dark
- Only light source is the physics LightBall falling from y=15
- Objects near the ball are illuminated, far objects are in darkness
- Dramatic contrast shows dynamic lighting clearly
2026-06-18 19:27:05 +03:00
emil28092005 e6cc9e6c4e feat: dynamic point light on a physics ball — falls and illuminates
- LightBall entity: sphere mesh + DynamicSphere physics + Light.Point
- Renderer reads light position from Transform (not static Light.Position)
  so the light moves with the physics body
- Light intensity 20, range 30, warm color (1.0, 0.9, 0.7)
- Ball falls from y=15, bounces on floor, light follows it
- Renderer: prefers Light+Transform pair for dynamic position, falls back to Light only
2026-06-18 19:25:02 +03:00
emil28092005 f718e6de49 feat: PBR point light — Unity-style attenuation, warm glow
- UBO expanded from 64 to 128 bytes: vp(64) + lightPos(16) + lightColor(16)
- Vertex shader: passes point light UBO data through
- Fragment shader: refactored calcLight() function, shared by directional + point
  - Point light: Unity-style attenuation pow(1 - dist/range, 2)
  - Directional light reduced intensity (0.4) to balance with point light
  - Point light: position (0,8,0), warm color (1,0.9,0.7), intensity 15, range 25
- Renderer: reads Light component from ECS, packs into UBO with Marshal.StructureToPtr
- Scene: MainLight entity with Light.Point at (0,8,0)
- 0 C# struct changes — pure UBO layout + shader upgrade
2026-06-18 19:23:00 +03:00
emil28092005 22748ed9ba feat: PBR shading — Cook-Torrance BRDF with ACES tonemapping
- Vertex shader: passes world position, world normal, albedo to fragment
- Fragment shader: full PBR implementation
  - D term: Trowbridge-Reitz GGX distribution
  - G term: Smith geometry with Schlick-GGX
  - F term: Schlick Fresnel approximation
  - kD/kS split based on metallic
  - Directional light (0.5, 0.8, 0.3) with warm color
  - Ambient term (0.15, 0.18, 0.22) for fill light
  - ACES filmic tonemapping
  - Gamma 2.2 correction
- Fixed roughness=0.5, metallic=0.1 (per-object materials = future)
- No C# code changes — pure shader upgrade
2026-06-18 19:18:01 +03:00
emil28092005 50e77e705b fix: MCP server starts after 3rd frame to avoid blocking Vulkan init
- MCP server (Kestrel) launched on background thread after 3 frames
  to avoid conflicting with Vulkan/SDL3 initialization
- --mcp-port 0 (default) = disabled, scene works as before
- --mcp-port 5000 = MCP starts after 3rd frame
- Reduced extra balls from 30 to 10 for stability
- Engine.AI/McpEngineServerHost reverted to original (no WebApplicationOptions)
2026-06-18 19:12:11 +03:00
emil28092005 1f7538383e feat: integrate AI/MCP — 7 tools available via --mcp-port flag
- Engine.AI referenced in CortexEngine.App.csproj
- AiCommandProcessor created with world + mesh loader + screenshot callback
- AiCommandQueue marshals commands from MCP thread to main thread
- MCP HTTP server (Kestrel) started on separate background thread
- --mcp-port N flag (default 0 = disabled) controls MCP server
- queue.ProcessPending() called before render each frame
- queue.CompletePendingScreenshots() called after render
- ImGui overlay shows MCP status (port or disabled)
- DummyScreenshotProvider placeholder (real capture = future checkpoint)
- 7 MCP tools: spawn_model, set_transform, set_material, delete_entity,
  list_entities, get_world_state, capture_screenshot
- MCP server has 2s startup delay to avoid blocking Vulkan init
- Default: MCP disabled (--mcp-port 0), scene + physics + ImGui work
2026-06-18 18:56:53 +03:00
emil28092005 bfde04b381 feat: ImGui debug overlay — FPS, camera position, entity count
- ImGui.NET 1.91.6.1 integrated into Engine.Graphics.Vulkan
- VulkanImGui.cs: font atlas upload (staging → VkImage), sampler,
  descriptor set (COMBINED_IMAGE_SAMPLER), separate pipeline with
  alpha blending, no depth write, dynamic scissor
- ImGui shaders: imgui.vert (ortho MVP push constant) + imgui.frag
  (font texture sampler)
- Dynamic vertex/index buffers (HOST_VISIBLE, grow on demand)
- IRenderer interface: BeginImGuiFrame/EndImGuiFrame default methods
- Program.cs: debug window with FPS, camera pos/target, entity count
- 0 critical validation errors, ~750 FPS with physics + ImGui
2026-06-18 17:34:42 +03:00
emil28092005 e67e312d28 feat: Jolt physics — cubes and spheres fall with gravity, hit floor
- Engine.Physics (JoltPhysicsSharp) integrated into CortexEngine.App
- 4 cubes: DynamicBox, mass proportional to scale, fall from height 5-10
- 3 spheres: DynamicSphere, mass 1.5, fall from height 7-12
- Floor: StaticBox (20x0.5x20), friction 0.8
- Torus knot + grid: no physics (visual only)
- Physics bodies initialized lazily on first frame (IsInitialized flag)
- e.Set() moved outside world.Each to avoid mutation during iteration
- physicsWorld.Update + SyncTransforms every frame
- Removed manual rotation — physics handles movement now
- Camera at (0, 5, -15) for better view of falling objects
- 0 validation errors, ~1300 FPS
2026-06-18 17:18:59 +03:00
emil28092005 609b11e3a3 feat: full engine scene — torus knot + 4 cubes + 3 spheres + floor + grid
- 10 entities in ECS World with Transform + Mesh components
- Torus knot (center, scale 1.5) rotating around Y
- 4 cubes (left/right/front/back) rotating at different speeds
- 3 spheres (procedural, 32x16) rotating around X
- Floor (20x20 flat cube) + grid (ProceduralMesh.CreateGrid)
- Camera at (0, 3, -12) looking at (0, 0.5, 0)
- Mesh cache: buffers created lazily per entity ID on first frame
- Diffuse lighting on all objects
- 0 validation errors, ~2300 FPS
2026-06-18 17:10:35 +03:00
emil28092005 e36cbc7774 feat: diffuse lighting — normals passed to fragment shader
- Vertex shader: pass normal through mat3(model) to fragment
- Fragment shader: directional light (0.5, 0.8, 0.3) with 0.25 ambient
  + 0.75 diffuse = faces now have depth and 3D form
- Torus knot has smooth normals (vn in OBJ), cubes have face normals
- 0 validation errors, ~2300 FPS
2026-06-18 17:03:15 +03:00
emil28092005 a4914f28c5 feat: ECS scene with 7 objects — torus knot + 6 rotating cubes
- VulkanRenderer: mesh cache by entity ID, lazy buffer creation
- RenderWorld iterates entities with Transform+Mesh, draws each with
  per-entity model matrix via push constants
- Program.cs: creates scene with 1 torus knot (center, scale 1.5) +
  6 cubes at different positions, all rotating around Y at different speeds
- Camera at (0, 2, -8) looking at origin, FreeFly controls
- 0 validation errors, ~2100 FPS with 7 objects
2026-06-18 17:00:14 +03:00
emil28092005 398cc88ae2 fix: remove row_major from GLSL — fixes fish-eye and broken projection
Root cause: System.Numerics.Matrix4x4 uses row-major storage with row-vector
convention (v * M). GLSL with layout(row_major) does column-vector (M * v),
which effectively transposes the matrix, breaking the projection.

Fix: remove row_major from layout qualifiers. GLSL then interprets the
row-major data as column-major (effectively transposing it), so M * v in
GLSL equals v * M in System.Numerics — correct result.

The Y-flip (M22 *= -1) is still needed for Vulkan's Y-down clip space.
2026-06-18 16:56:44 +03:00
emil28092005 44bae31495 fix: flip projection Y for Vulkan (M22 *= -1) — fixes fish-eye distortion
System.Numerics.CreatePerspectiveFieldOfView uses DirectX convention (Y up).
Vulkan clip space has Y down. Negating M22 corrects the projection.
Depth is already [0,1] in System.Numerics (DirectX-style), no remap needed.
2026-06-18 16:53:48 +03:00
emil28092005 fcbc42959e feat: FreeFlyCameraController — interactive WASD + mouse look
- Program.cs: create Camera entity in ECS World, FreeFlyCameraController
  with WASD movement, Q/E up/down, Shift boost, right-click mouse look
- VulkanRenderer.RenderWorld: queries Camera from World, computes VP
  from Camera.GetViewMatrix() * GetProjectionMatrix()
- Camera aspect ratio auto-updated on window resize
- Fallback to hardcoded VP if no Camera entity found
- Torus knot still rotating via push constant model matrix
- 0 validation errors
2026-06-18 16:49:54 +03:00
emil28092005 2886dd77f2 feat: render torus knot (4800 verts, 9600 faces, smooth normals)
- Load Content/torusknot.obj instead of cube.obj
- 28800 vertices (duplicated per-face), 28800 indices
- OBJ has vn lines so smooth normals are used (not face normals)
- Removed slide animation, kept Y-axis rotation
- Camera z=-6, FOV 45°
- 0 validation errors, ~3000 FPS
2026-06-18 16:46:27 +03:00
emil28092005 0978b82322 fix: remove projection matrix hacks — back to clean FOV 45° + z=-5
- Removed M22 Y-flip and M33/M43 depth remap that caused edge distortion
- System.Numerics.CreatePerspectiveFieldOfView works as-is with row_major
- FOV 45°, camera z=-5, 1 cube sliding left-right + rotating
2026-06-18 13:29:26 +03:00
emil28092005 00f23187d5 feat: 1 cube slowly sliding left-right (sin wave) + rotating 2026-06-18 13:27:36 +03:00
emil28092005 a801e7fb16 fix: Vulkan projection matrix — Y-flip + depth [0,1] remap
- Negate proj.M22 for Vulkan Y-down coordinate system
- Remap depth from OpenGL [-1,1] to Vulkan [0,1]: M33*0.5+0.5, M43*0.5+0.5
- FOV 45°, camera z=-5, cubes at x=±2
- 0 validation errors
2026-06-18 13:25:59 +03:00
emil28092005 ce3dc8c41b fix: FOV 72°, camera z=-10, cubes 6 units apart 2026-06-18 13:24:26 +03:00
emil28092005 1a277abc7a fix: wider FOV (81°) for more distance 2026-06-18 13:23:19 +03:00
emil28092005 c9358bf080 fix: wider FOV (60°) + camera z=-6 for better view 2026-06-18 13:22:41 +03:00
emil28092005 51f94b8923 fix: move camera to z=-12 2026-06-18 13:21:39 +03:00
emil28092005 45fcccbd34 fix: move camera to z=-7 for more distance 2026-06-18 13:20:50 +03:00
emil28092005 edfcf33578 feat: render 2 rotating cubes side by side
- Push constant changed from float angle (4B) to mat4 model (64B)
- Two draw calls with different model matrices (x=-1.5 and x=+1.5)
- Both cubes rotate around Y axis at 0.2 rad/s
- 0 validation errors
2026-06-18 13:20:22 +03:00