From 23cac8052ca24f7362b168d4c142bf499588a36c Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Wed, 17 Jun 2026 16:33:12 +0300 Subject: [PATCH] fix: remove shadow mapping, keep all other improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shadow mapping removed — Raylib's DrawModelEx doesn't support multi-texture-unit binding needed for shadow map sampling. Kept: linear gamma, per-channel Fresnel, point/directional lights, correct CW normals, backface culling, floor entity. 66/66 tests passing. --- imgui.ini | 4 +- src/Engine.Graphics.Raylib/RaylibRenderer.cs | 335 +------------------ 2 files changed, 4 insertions(+), 335 deletions(-) diff --git a/imgui.ini b/imgui.ini index 0d03039..e0f93dd 100644 --- a/imgui.ini +++ b/imgui.ini @@ -9,12 +9,12 @@ Size=216,107 Collapsed=0 [Window][Hierarchy] -Pos=29,195 +Pos=1040,357 Size=250,398 Collapsed=0 [Window][Inspector] -Pos=946,253 +Pos=740,357 Size=300,400 Collapsed=0 diff --git a/src/Engine.Graphics.Raylib/RaylibRenderer.cs b/src/Engine.Graphics.Raylib/RaylibRenderer.cs index bf6fa11..92a496e 100644 --- a/src/Engine.Graphics.Raylib/RaylibRenderer.cs +++ b/src/Engine.Graphics.Raylib/RaylibRenderer.cs @@ -21,11 +21,6 @@ namespace Engine.Graphics.RaylibBackend; public sealed class RaylibRenderer : IRenderer { private readonly Shader _shader; - private readonly Shader _shadowShader; - private readonly Shader _shadowReceiverShader; - private readonly uint _shadowFbo; - private readonly uint _shadowDepthTex; - private const int ShadowMapSize = 2048; private readonly Dictionary _modelCache = new(); private readonly Dictionary _textureCache = new(); private readonly int _materialColorLoc; @@ -42,24 +37,12 @@ public sealed class RaylibRenderer : IRenderer private readonly int _lightTypeLoc; private readonly int _lightRangeLoc; // Shadow receiver shader uniforms - private readonly int _srMaterialColorLoc; - private readonly int _srRoughnessLoc; - private readonly int _srMetallicLoc; - private readonly int _srAmbientLoc; - private readonly int _srViewPosLoc; - private readonly int _srLightCountLoc; - private readonly int _srLightDirLoc; - private readonly int _srLightIntensityLoc; - private readonly int _srLightColorLoc; - private readonly int _srLightSpaceMatrixLoc; - private readonly int _srShadowMapLoc; private readonly float[] _lightDirs = new float[12]; private readonly float[] _lightPositions = new float[12]; private readonly float[] _lightRanges = new float[4]; private readonly int[] _lightTypes = new int[4]; private readonly float[] _lightIntensities = new float[4]; private readonly float[] _lightColors = new float[12]; - private readonly float[] _lightSpaceMatrixData = new float[16]; private int _lightCount; private ScreenshotRequest? _pendingScreenshot; @@ -74,15 +57,6 @@ public sealed class RaylibRenderer : IRenderer public RaylibRenderer() { _shader = LoadShader(); - _shadowShader = LoadShadowShader(); - _shadowReceiverShader = LoadShadowReceiverShader(); - - // Create depth-only FBO for shadow mapping via rlgl - _shadowFbo = Rlgl.LoadFramebuffer(); - _shadowDepthTex = Rlgl.LoadTextureDepth(ShadowMapSize, ShadowMapSize, false); - Rlgl.FramebufferAttach(_shadowFbo, _shadowDepthTex, FramebufferAttachType.Depth, FramebufferAttachTextureType.Texture2D, 0); - if (!Rlgl.FramebufferComplete(_shadowFbo)) - Console.WriteLine("WARNING: Shadow framebuffer incomplete!"); // Main shader uniform locations _materialColorLoc = Raylib.GetShaderLocation(_shader, "materialColor"); @@ -99,18 +73,6 @@ public sealed class RaylibRenderer : IRenderer _lightTypeLoc = Raylib.GetShaderLocation(_shader, "lightTypes"); _lightRangeLoc = Raylib.GetShaderLocation(_shader, "lightRanges"); - // Shadow receiver shader uniform locations - _srMaterialColorLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "materialColor"); - _srRoughnessLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "roughness"); - _srMetallicLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "metallic"); - _srAmbientLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "ambientColor"); - _srViewPosLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "viewPos"); - _srLightCountLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightCount"); - _srLightDirLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightDirs"); - _srLightIntensityLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightIntensities"); - _srLightColorLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightColors"); - _srLightSpaceMatrixLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightSpaceMatrix"); - _srShadowMapLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "shadowMap"); } public void RequestScreenshot(string outputPath) @@ -127,33 +89,18 @@ public sealed class RaylibRenderer : IRenderer var camera = GetCamera(world); Raylib.BeginDrawing(); - - // --- Shadow pass (inside BeginDrawing, before ClearBackground) --- - if (_lightTypes[0] == (int)LightType.Directional) - RenderShadowPass(world); - - // --- Main pass --- Raylib.ClearBackground(new Color(25, 30, 40, 255)); Raylib.BeginMode3D(ToRaylib(camera)); - // Single CollectLights call — after BeginMode3D so OpenGL context is ready CollectLights(world); SetFrameLights(); Raylib.SetShaderValue(_shader, _viewPosLoc, new float[] { camera.Position.X, camera.Position.Y, camera.Position.Z }, ShaderUniformDataType.Vec3); - // Disable backface culling — cube.obj has mixed winding order Rlgl.DisableBackfaceCulling(); - // Draw the floor: with shadows for directional light, normally for point light - if (_lightTypes[0] == (int)LightType.Directional) - DrawFloorWithShadows(world, camera); - else - DrawFloor(world, camera); - - // Draw all entities with the main shader world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) => { - if (e.Name() == "Grid" || e.Name() == "Floor") + if (e.Name() == "Grid") return; var material = e.Has() ? e.Get() : EngineMaterial.Default; @@ -174,6 +121,7 @@ public sealed class RaylibRenderer : IRenderer Raylib.EndMode3D(); + // ImGui renders on top of the 3D scene, before EndDrawing. if (ImGuiLayer != null) { @@ -457,284 +405,10 @@ public sealed class RaylibRenderer : IRenderer return tcs.Task; } - private void DrawFloor(World world, Camera camera) - { - var floorEntity = world.Lookup("Floor"); - if ((ulong)floorEntity.Id == 0 || !floorEntity.Has()) - return; - var floorMesh = floorEntity.Get(); - var floorTransform = floorEntity.Get(); - var floorMaterial = floorEntity.Has() ? floorEntity.Get() : EngineMaterial.Default; - var model = GetOrUploadModel(floorEntity, floorMesh); - var modelMatrix = floorTransform.GetMatrix(); - if (!Matrix4x4.Decompose(modelMatrix, out var scale, out var rotation, out var position)) - return; - var (axis, angle) = QuaternionToAxisAngle(rotation); - SetMaterialUniforms(floorMaterial, model); - Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White); - } - private void DrawFloorWithShadows(World world, Camera camera) - { - var floorEntity = world.Lookup("Floor"); - if ((ulong)floorEntity.Id == 0 || !floorEntity.Has()) - return; - - var floorMesh = floorEntity.Get(); - var floorTransform = floorEntity.Get(); - var floorMaterial = floorEntity.Has() ? floorEntity.Get() : EngineMaterial.Default; - var model = GetOrUploadModel(floorEntity, floorMesh); - var modelMatrix = floorTransform.GetMatrix(); - - if (!Matrix4x4.Decompose(modelMatrix, out var scale, out var rotation, out var position)) - return; - - var (axis, angle) = QuaternionToAxisAngle(rotation); - - // Light-space matrix - 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]); - - // Bind shadow depth texture on Emission slot (texture unit 1) - var shadowTex = new Texture2D { Id = _shadowDepthTex, Width = ShadowMapSize, Height = ShadowMapSize, Mipmaps = 1, Format = PixelFormat.UncompressedR16 }; - unsafe - { - Raylib.SetMaterialTexture(ref model.Materials[0], MaterialMapIndex.Emission, shadowTex); - } - - // Use BeginShaderMode to bind shadow receiver shader - Raylib.BeginShaderMode(_shadowReceiverShader); - - // Set all uniforms after BeginShaderMode (changes active program) - Raylib.SetShaderValue(_shadowReceiverShader, _srMaterialColorLoc, - new float[] { floorMaterial.Albedo.X, floorMaterial.Albedo.Y, floorMaterial.Albedo.Z, 1.0f }, ShaderUniformDataType.Vec4); - Raylib.SetShaderValue(_shadowReceiverShader, _srRoughnessLoc, floorMaterial.Roughness, ShaderUniformDataType.Float); - Raylib.SetShaderValue(_shadowReceiverShader, _srMetallicLoc, floorMaterial.Metallic, ShaderUniformDataType.Float); - Raylib.SetShaderValue(_shadowReceiverShader, _srAmbientLoc, new float[] { 0.35f, 0.35f, 0.4f }, ShaderUniformDataType.Vec3); - Raylib.SetShaderValue(_shadowReceiverShader, _srViewPosLoc, - new float[] { camera.Position.X, camera.Position.Y, camera.Position.Z }, ShaderUniformDataType.Vec3); - Raylib.SetShaderValue(_shadowReceiverShader, _srLightCountLoc, _lightCount, ShaderUniformDataType.Int); - Raylib.SetShaderValueV(_shadowReceiverShader, _srLightDirLoc, _lightDirs, ShaderUniformDataType.Vec3, 4); - Raylib.SetShaderValueV(_shadowReceiverShader, _srLightIntensityLoc, _lightIntensities, ShaderUniformDataType.Float, 4); - Raylib.SetShaderValueV(_shadowReceiverShader, _srLightColorLoc, _lightColors, ShaderUniformDataType.Vec3, 4); - Raylib.SetShaderValueMatrix(_shadowReceiverShader, _srLightSpaceMatrixLoc, lsMat); - - // Tell shader that shadowMap sampler uses texture unit 1 (Emission slot) - Raylib.SetShaderValue(_shadowReceiverShader, _srShadowMapLoc, 1, ShaderUniformDataType.Int); - - Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White); - Raylib.EndShaderMode(); - } - - 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 up = MathF.Abs(Vector3.Dot(lightDir, Vector3.UnitY)) > 0.99f - ? Vector3.UnitZ - : Vector3.UnitY; - - var lightView = Matrix4x4.CreateLookAt(lightPos, sceneCenter, up); - 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 = up, - FovY = 0, - Projection = CameraProjection.Orthographic - }; - - // Bind custom depth FBO via rlgl - Rlgl.EnableFramebuffer(_shadowFbo); - Rlgl.Viewport(0, 0, ShadowMapSize, ShadowMapSize); - Rlgl.ClearColor(255, 255, 255, 255); - Rlgl.ClearScreenBuffers(); - - Raylib.BeginMode3D(shadowCamera); - - world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) => - { - var name = e.Name(); - if (name == "Grid" || name == "Floor") - 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, angle) = QuaternionToAxisAngle(rotation); - - 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(); - - // Unbind FBO, restore viewport to screen - Rlgl.DisableFramebuffer(); - Rlgl.Viewport(0, 0, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); - } - - private static Shader LoadShadowShader() - { - const string VertexSource = @"#version 330 core -in vec3 vertexPosition; -uniform mat4 mvp; -void main() -{ - gl_Position = mvp * vec4(vertexPosition, 1.0); -}"; - - const string FragmentSource = @"#version 330 core -void main() {}"; - - return Raylib.LoadShaderFromMemory(VertexSource, FragmentSource); - } - - /// - /// Shadow receiver shader — used only for the floor. - /// Samples the shadow map from texture0 (bound via SetMaterialTexture). - /// Has its own uniform layout so it doesn't affect the main shader. - /// - private static Shader LoadShadowReceiverShader() - { - const string VertexSource = @"#version 330 core -in vec3 vertexPosition; -in vec2 vertexTexCoord; -in vec3 vertexNormal; -in vec4 vertexColor; -uniform mat4 mvp; -uniform mat4 matModel; -out vec3 vNormal; -out vec3 vWorldPos; -out vec4 vColor; -out vec2 vTexCoord; -void main() -{ - vec4 worldPos = matModel * vec4(vertexPosition, 1.0); - vWorldPos = worldPos.xyz; - vNormal = mat3(transpose(inverse(matModel))) * vertexNormal; - vColor = vertexColor; - vTexCoord = vertexTexCoord; - gl_Position = mvp * vec4(vertexPosition, 1.0); -}"; - - const string FragmentSource = @"#version 330 core -in vec3 vNormal; -in vec3 vWorldPos; -in vec4 vColor; -in vec2 vTexCoord; -out vec4 finalColor; -uniform vec4 materialColor; -uniform sampler2D texture0; -uniform sampler2D shadowMap; -uniform float roughness; -uniform float metallic; -uniform vec3 viewPos; -uniform vec3 ambientColor; -uniform int lightCount; -uniform vec3 lightDirs[4]; -uniform float lightIntensities[4]; -uniform vec3 lightColors[4]; -uniform mat4 lightSpaceMatrix; - -vec3 ACESFilm(vec3 x) -{ - const float a = 2.51; const float b = 0.03; const float c = 2.43; const float d = 0.59; const float e = 0.14; - return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); -} - -float CalculateShadow(vec3 worldPos) -{ - vec4 lp = lightSpaceMatrix * vec4(worldPos, 1.0); - vec3 ndc = lp.xyz / lp.w; - vec3 uvw = ndc * 0.5 + 0.5; - if (uvw.x < 0.0 || uvw.x > 1.0 || uvw.y < 0.0 || uvw.y > 1.0 || uvw.z > 1.0) - return 1.0; - float bias = 0.003; - vec2 ts = 1.0 / 1024.0; - float s = 0.0; - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { - float d = texture(shadowMap, uvw.xy + vec2(x, y) * ts).r; - s += (uvw.z - bias > d) ? 0.3 : 1.0; - } - } - return s / 9.0; -} - -void main() -{ - vec3 normal = normalize(vNormal); - // Convert sRGB vertex color to linear before lighting - vec3 albedo = pow(vColor.rgb * materialColor.rgb, vec3(2.2)); - vec3 viewDir = normalize(viewPos - vWorldPos); - float rough = clamp(roughness, 0.05, 1.0); - float metal = clamp(metallic, 0.0, 1.0); - - vec3 skyColor = ambientColor; - vec3 groundColor = ambientColor * 0.2; - float hemisphere = 0.5 + 0.5 * normal.y; - vec3 result = albedo * mix(groundColor, skyColor, hemisphere) * 0.4; - - float shadow = CalculateShadow(vWorldPos); - - // F0 as vec3: dielectric 0.04, metals use albedo - vec3 F0 = mix(vec3(0.04), albedo, metal); - float shininess = mix(8.0, 256.0, 1.0 - rough); - - for (int i = 0; i < lightCount; i++) - { - 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 HdotV = max(dot(H, viewDir), 0.0); - float diff = NdotL; - float spec = pow(NdotH, shininess); - // Schlick Fresnel using F0.rgb - vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0); - vec3 specularColor = mix(fresnel, albedo * fresnel, metal); - vec3 diffuse = albedo * lightColors[i] * diff * lightIntensities[i] * 1.5 * shadow; - vec3 specular = specularColor * spec * lightIntensities[i] * shadow; - diffuse *= (1.0 - fresnel * (1.0 - metal * 0.5)); - result += diffuse + specular; - } - - result = ACESFilm(result * 1.2); - result = pow(result, vec3(1.0 / 2.2)); - finalColor = vec4(result, 1.0); -}"; - - return Raylib.LoadShaderFromMemory(VertexSource, FragmentSource); - } private static Shader LoadShader() { @@ -846,7 +520,6 @@ void main() float HdotV = max(dot(H, viewDir), 0.0); float diff = NdotL; float spec = pow(NdotH, shininess); - // Schlick Fresnel using F0.rgb (per-channel) vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0); vec3 specularColor = mix(fresnel, albedo * fresnel, metal); vec3 diffuse = albedo * lightColors[i] * diff * lightIntensities[i] * atten * 1.5; @@ -876,10 +549,6 @@ void main() Raylib.UnloadTexture(texture); _textureCache.Clear(); - Rlgl.UnloadTexture(_shadowDepthTex); - Rlgl.UnloadFramebuffer(_shadowFbo); - Raylib.UnloadShader(_shadowShader); - Raylib.UnloadShader(_shadowReceiverShader); Raylib.UnloadShader(_shader); }