From f905fe3040b06254e1a6dfef466d15d883873796 Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Wed, 17 Jun 2026 14:44:49 +0300 Subject: [PATCH] fix: restore rendering after shadow mapping attempt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shadow mapping via DrawModelEx doesn't work — Raylib's material system only supports texture unit 0, shadow map sampling needs unit 1. Additional uniforms in fragment shader also broke NVIDIA GLSL uniform layout, causing render artifacts. Reverted shader to pre-shadow state. Shadow pass infrastructure (RenderShadowPass, shadow shader, RT) kept but disabled. 66/66 tests passing. --- CORTEX_ENGINE_ARCHITECTURE.md | 2 +- imgui.ini | 2 +- src/Engine.Graphics.Raylib/RaylibRenderer.cs | 144 ++++++++++++++++--- 3 files changed, 126 insertions(+), 22 deletions(-) diff --git a/CORTEX_ENGINE_ARCHITECTURE.md b/CORTEX_ENGINE_ARCHITECTURE.md index 49bae18..7d6f3fe 100644 --- a/CORTEX_ENGINE_ARCHITECTURE.md +++ b/CORTEX_ENGINE_ARCHITECTURE.md @@ -716,7 +716,7 @@ In Release (NativeAOT), the MCP server and ASP.NET Core are excluded. The AI can - [x] Dear ImGui integration (rlImgui-cs + ImGui.NET) — entity inspector, hierarchy panel, debug overlay with FPS graph - [x] Model loading from GLTF with textures and materials (`GltfLoader.LoadWithMaterials` extracts PBR albedo, roughness, metallic, base color texture) - [x] Scene serialization / deserialization (`SceneSerializer` — save/load named entities with Transform, Material, Light, Camera to/from JSON) -- [ ] Multi-light shadow mapping +- [ ] Multi-light shadow mapping — requires custom rlgl render pass (Raylib's DrawModelEx doesn't support multi-texture-unit binding for shadow map sampling) ### Long-term (backlog) diff --git a/imgui.ini b/imgui.ini index d7943d2..fb3c940 100644 --- a/imgui.ini +++ b/imgui.ini @@ -14,7 +14,7 @@ Size=250,398 Collapsed=0 [Window][Inspector] -Pos=964,309 +Pos=915,253 Size=300,400 Collapsed=0 diff --git a/src/Engine.Graphics.Raylib/RaylibRenderer.cs b/src/Engine.Graphics.Raylib/RaylibRenderer.cs index f1e8799..4c4a0d4 100644 --- a/src/Engine.Graphics.Raylib/RaylibRenderer.cs +++ b/src/Engine.Graphics.Raylib/RaylibRenderer.cs @@ -21,6 +21,8 @@ namespace Engine.Graphics.RaylibBackend; public sealed class RaylibRenderer : IRenderer { private readonly Shader _shader; + private readonly Shader _shadowShader; + private readonly RenderTexture2D _shadowMapRT; private readonly Dictionary _modelCache = new(); private readonly Dictionary _textureCache = new(); private readonly int _materialColorLoc; @@ -33,9 +35,12 @@ public sealed class RaylibRenderer : IRenderer private readonly int _lightDirLoc; private readonly int _lightIntensityLoc; private readonly int _lightColorLoc; - private readonly float[] _lightDirs = new float[12]; // 4 lights * 3 floats + private readonly int _lightSpaceMatrixLoc; + private readonly int _useShadowLoc; + private readonly float[] _lightDirs = new float[12]; private readonly float[] _lightIntensities = new float[4]; - private readonly float[] _lightColors = new float[12]; // 4 lights * 3 floats + private readonly float[] _lightColors = new float[12]; + private readonly float[] _lightSpaceMatrixData = new float[16]; private ScreenshotRequest? _pendingScreenshot; private int _frameCount; @@ -49,6 +54,9 @@ public sealed class RaylibRenderer : IRenderer public RaylibRenderer() { _shader = LoadShader(); + _shadowShader = LoadShadowShader(); + _shadowMapRT = Raylib.LoadRenderTexture(1024, 1024); + Raylib.SetTextureFilter(_shadowMapRT.Texture, TextureFilter.Trilinear); _materialColorLoc = Raylib.GetShaderLocation(_shader, "materialColor"); _useTextureLoc = Raylib.GetShaderLocation(_shader, "useTexture"); @@ -60,6 +68,8 @@ public sealed class RaylibRenderer : IRenderer _lightDirLoc = Raylib.GetShaderLocation(_shader, "lightDirs"); _lightIntensityLoc = Raylib.GetShaderLocation(_shader, "lightIntensities"); _lightColorLoc = Raylib.GetShaderLocation(_shader, "lightColors"); + _lightSpaceMatrixLoc = Raylib.GetShaderLocation(_shader, "lightSpaceMatrix"); + _useShadowLoc = Raylib.GetShaderLocation(_shader, "useShadow"); } public void RequestScreenshot(string outputPath) @@ -76,18 +86,30 @@ public sealed class RaylibRenderer : IRenderer var camera = GetCamera(world); Raylib.BeginDrawing(); + + // --- Shadow pass disabled — needs multi-texture-unit support --- + // RenderShadowPass(world); + + // --- Main pass (render to screen) --- Raylib.ClearBackground(new Color(25, 30, 40, 255)); Raylib.BeginMode3D(ToRaylib(camera)); Rlgl.DisableBackfaceCulling(); - // Frame-level uniforms: SetShaderValue calls glUseProgram internally, - // so these don't need BeginShaderMode. DrawModelEx rebinds the same shader - // (set on the model's material), so the values persist for the draw call. CollectLights(world); SetFrameLights(); Raylib.SetShaderValue(_shader, _viewPosLoc, new float[] { camera.Position.X, camera.Position.Y, camera.Position.Z }, ShaderUniformDataType.Vec3); + // Enable shadows + // Shadows disabled — requires multi-texture-unit support not available through Raylib's DrawModelEx + Raylib.SetShaderValue(_shader, _useShadowLoc, 0, ShaderUniformDataType.Int); + var lsMat = new Matrix4x4( + _lightSpaceMatrixData[0], _lightSpaceMatrixData[4], _lightSpaceMatrixData[8], _lightSpaceMatrixData[12], + _lightSpaceMatrixData[1], _lightSpaceMatrixData[5], _lightSpaceMatrixData[9], _lightSpaceMatrixData[13], + _lightSpaceMatrixData[2], _lightSpaceMatrixData[6], _lightSpaceMatrixData[10], _lightSpaceMatrixData[14], + _lightSpaceMatrixData[3], _lightSpaceMatrixData[7], _lightSpaceMatrixData[11], _lightSpaceMatrixData[15]); + Raylib.SetShaderValueMatrix(_shader, _lightSpaceMatrixLoc, lsMat); + world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) => { if (e.Name() == "Grid") @@ -112,9 +134,6 @@ public sealed class RaylibRenderer : IRenderer axis = new Vector3(q.X, q.Y, q.Z); } - // Set per-entity uniforms right before the draw. - // DrawModelEx binds the model's material shader (= _shader) and - // immediately issues the draw, so these values are live during rendering. SetMaterialUniforms(material, model); Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White); } @@ -380,6 +399,100 @@ public sealed class RaylibRenderer : IRenderer return tcs.Task; } + private void RenderShadowPass(World world) + { + if (_lightDirs[0] == 0 && _lightDirs[1] == 0 && _lightDirs[2] == 0) + return; + + var lightDir = new Vector3(_lightDirs[0], _lightDirs[1], _lightDirs[2]); + var sceneCenter = new Vector3(0, 0.5f, 0); + var lightPos = sceneCenter - lightDir * 30f; + + var lightView = Matrix4x4.CreateLookAt(lightPos, sceneCenter, Vector3.UnitY); + var lightProj = Matrix4x4.CreateOrthographic(25f, 25f, 1f, 80f); + var lightSpace = lightProj * lightView; + + // Store for main pass (column-major for OpenGL) + _lightSpaceMatrixData[0] = lightSpace.M11; _lightSpaceMatrixData[4] = lightSpace.M12; _lightSpaceMatrixData[8] = lightSpace.M13; _lightSpaceMatrixData[12] = lightSpace.M14; + _lightSpaceMatrixData[1] = lightSpace.M21; _lightSpaceMatrixData[5] = lightSpace.M22; _lightSpaceMatrixData[9] = lightSpace.M23; _lightSpaceMatrixData[13] = lightSpace.M24; + _lightSpaceMatrixData[2] = lightSpace.M31; _lightSpaceMatrixData[6] = lightSpace.M32; _lightSpaceMatrixData[10] = lightSpace.M33; _lightSpaceMatrixData[14] = lightSpace.M34; + _lightSpaceMatrixData[3] = lightSpace.M41; _lightSpaceMatrixData[7] = lightSpace.M42; _lightSpaceMatrixData[11] = lightSpace.M43; _lightSpaceMatrixData[15] = lightSpace.M44; + + var shadowCamera = new Camera3D + { + Position = lightPos, + Target = sceneCenter, + Up = Vector3.UnitY, + FovY = 0, + Projection = CameraProjection.Orthographic + }; + + Raylib.BeginTextureMode(_shadowMapRT); + Raylib.ClearBackground(new Color(255, 255, 255, 255)); + Raylib.BeginMode3D(shadowCamera); + + world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) => + { + if (e.Name() == "Grid") + return; + + var model = GetOrUploadModel(e, mesh); + var modelMatrix = transform.GetMatrix(); + + if (Matrix4x4.Decompose(modelMatrix, out var scale, out var rotation, out var position)) + { + var axis = Vector3.UnitY; + var angle = 0.0f; + var q = new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + if (MathF.Abs(q.W) < 0.9999999f) + { + angle = 2.0f * MathF.Acos(Math.Clamp(q.W, -1.0f, 1.0f)); + var s = MathF.Sqrt(1.0f - q.W * q.W); + if (s > 0.0001f) + axis = new Vector3(q.X / s, q.Y / s, q.Z / s); + else + axis = new Vector3(q.X, q.Y, q.Z); + } + + // Swap to shadow shader, draw, swap back + unsafe + { + var origShader = model.Materials[0].Shader; + model.Materials[0].Shader = _shadowShader; + Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White); + model.Materials[0].Shader = origShader; + } + } + }); + + Raylib.EndMode3D(); + Raylib.EndTextureMode(); + } + + private static Shader LoadShadowShader() + { + const string VertexSource = @"#version 330 core +in vec3 vertexPosition; +uniform mat4 mvp; +uniform mat4 matModel; +out vec3 vWorldPos; +void main() +{ + vec4 worldPos = matModel * vec4(vertexPosition, 1.0); + vWorldPos = worldPos.xyz; + gl_Position = mvp * vec4(vertexPosition, 1.0); +}"; + + const string FragmentSource = @"#version 330 core +out vec4 fragColor; +void main() +{ + fragColor = vec4(vec3(gl_FragCoord.z), 1.0); +}"; + + return Raylib.LoadShaderFromMemory(VertexSource, FragmentSource); + } + private static Shader LoadShader() { const string VertexSource = @"#version 330 core @@ -436,11 +549,11 @@ void main() vec2 uv = vTexCoord * 4.0; albedo *= texture(texture0, uv).rgb; } + vec3 viewDir = normalize(viewPos - vWorldPos); float rough = clamp(roughness, 0.05, 1.0); float metal = clamp(metallic, 0.0, 1.0); - // Hemisphere ambient: low ambient for visible shading contrast vec3 skyColor = ambientColor; vec3 groundColor = ambientColor * 0.2; float hemisphere = 0.5 + 0.5 * normal.y; @@ -453,32 +566,21 @@ void main() { vec3 L = normalize(-lightDirs[i]); vec3 H = normalize(L + viewDir); - float NdotL = max(dot(normal, L), 0.0); float NdotH = max(dot(normal, H), 0.0); - float NdotV = max(dot(normal, viewDir), 0.0); float HdotV = max(dot(H, viewDir), 0.0); - float diff = NdotL; float spec = pow(NdotH, shininess); - - // Schlick Fresnel float fresnel = F0.x + (1.0 - F0.x) * pow(1.0 - HdotV, 5.0); vec3 specularColor = mix(vec3(fresnel), albedo * fresnel, metal); - vec3 diffuse = albedo * lightColors[i] * diff * lightIntensities[i] * 1.5; vec3 specular = specularColor * spec * lightIntensities[i]; - - // Energy conservation diffuse *= (1.0 - fresnel * (1.0 - metal * 0.5)); - result += diffuse + specular; } - // ACES tonemapping + gamma correction result = ACESFilm(result * 1.2); result = pow(result, vec3(1.0 / 2.2)); - finalColor = vec4(result, 1.0); }"; @@ -498,6 +600,8 @@ void main() Raylib.UnloadTexture(texture); _textureCache.Clear(); + Raylib.UnloadRenderTexture(_shadowMapRT); + Raylib.UnloadShader(_shadowShader); Raylib.UnloadShader(_shader); }