fix: restore rendering after shadow mapping attempt

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.
This commit is contained in:
emil28092005
2026-06-17 14:44:49 +03:00
parent 2d30dec01c
commit f905fe3040
3 changed files with 126 additions and 22 deletions
+1 -1
View File
@@ -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] 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] 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) - [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) ### Long-term (backlog)
+1 -1
View File
@@ -14,7 +14,7 @@ Size=250,398
Collapsed=0 Collapsed=0
[Window][Inspector] [Window][Inspector]
Pos=964,309 Pos=915,253
Size=300,400 Size=300,400
Collapsed=0 Collapsed=0
+124 -20
View File
@@ -21,6 +21,8 @@ namespace Engine.Graphics.RaylibBackend;
public sealed class RaylibRenderer : IRenderer public sealed class RaylibRenderer : IRenderer
{ {
private readonly Shader _shader; private readonly Shader _shader;
private readonly Shader _shadowShader;
private readonly RenderTexture2D _shadowMapRT;
private readonly Dictionary<Entity, Raylib_cs.Model> _modelCache = new(); private readonly Dictionary<Entity, Raylib_cs.Model> _modelCache = new();
private readonly Dictionary<string, Texture2D> _textureCache = new(); private readonly Dictionary<string, Texture2D> _textureCache = new();
private readonly int _materialColorLoc; private readonly int _materialColorLoc;
@@ -33,9 +35,12 @@ public sealed class RaylibRenderer : IRenderer
private readonly int _lightDirLoc; private readonly int _lightDirLoc;
private readonly int _lightIntensityLoc; private readonly int _lightIntensityLoc;
private readonly int _lightColorLoc; 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[] _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 ScreenshotRequest? _pendingScreenshot;
private int _frameCount; private int _frameCount;
@@ -49,6 +54,9 @@ public sealed class RaylibRenderer : IRenderer
public RaylibRenderer() public RaylibRenderer()
{ {
_shader = LoadShader(); _shader = LoadShader();
_shadowShader = LoadShadowShader();
_shadowMapRT = Raylib.LoadRenderTexture(1024, 1024);
Raylib.SetTextureFilter(_shadowMapRT.Texture, TextureFilter.Trilinear);
_materialColorLoc = Raylib.GetShaderLocation(_shader, "materialColor"); _materialColorLoc = Raylib.GetShaderLocation(_shader, "materialColor");
_useTextureLoc = Raylib.GetShaderLocation(_shader, "useTexture"); _useTextureLoc = Raylib.GetShaderLocation(_shader, "useTexture");
@@ -60,6 +68,8 @@ public sealed class RaylibRenderer : IRenderer
_lightDirLoc = Raylib.GetShaderLocation(_shader, "lightDirs"); _lightDirLoc = Raylib.GetShaderLocation(_shader, "lightDirs");
_lightIntensityLoc = Raylib.GetShaderLocation(_shader, "lightIntensities"); _lightIntensityLoc = Raylib.GetShaderLocation(_shader, "lightIntensities");
_lightColorLoc = Raylib.GetShaderLocation(_shader, "lightColors"); _lightColorLoc = Raylib.GetShaderLocation(_shader, "lightColors");
_lightSpaceMatrixLoc = Raylib.GetShaderLocation(_shader, "lightSpaceMatrix");
_useShadowLoc = Raylib.GetShaderLocation(_shader, "useShadow");
} }
public void RequestScreenshot(string outputPath) public void RequestScreenshot(string outputPath)
@@ -76,18 +86,30 @@ public sealed class RaylibRenderer : IRenderer
var camera = GetCamera(world); var camera = GetCamera(world);
Raylib.BeginDrawing(); 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.ClearBackground(new Color(25, 30, 40, 255));
Raylib.BeginMode3D(ToRaylib(camera)); Raylib.BeginMode3D(ToRaylib(camera));
Rlgl.DisableBackfaceCulling(); 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); CollectLights(world);
SetFrameLights(); SetFrameLights();
Raylib.SetShaderValue(_shader, _viewPosLoc, new float[] { camera.Position.X, camera.Position.Y, camera.Position.Z }, ShaderUniformDataType.Vec3); 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) => world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) =>
{ {
if (e.Name() == "Grid") if (e.Name() == "Grid")
@@ -112,9 +134,6 @@ public sealed class RaylibRenderer : IRenderer
axis = new Vector3(q.X, q.Y, q.Z); 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); SetMaterialUniforms(material, model);
Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White); 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; 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() private static Shader LoadShader()
{ {
const string VertexSource = @"#version 330 core const string VertexSource = @"#version 330 core
@@ -436,11 +549,11 @@ void main()
vec2 uv = vTexCoord * 4.0; vec2 uv = vTexCoord * 4.0;
albedo *= texture(texture0, uv).rgb; albedo *= texture(texture0, uv).rgb;
} }
vec3 viewDir = normalize(viewPos - vWorldPos); vec3 viewDir = normalize(viewPos - vWorldPos);
float rough = clamp(roughness, 0.05, 1.0); float rough = clamp(roughness, 0.05, 1.0);
float metal = clamp(metallic, 0.0, 1.0); float metal = clamp(metallic, 0.0, 1.0);
// Hemisphere ambient: low ambient for visible shading contrast
vec3 skyColor = ambientColor; vec3 skyColor = ambientColor;
vec3 groundColor = ambientColor * 0.2; vec3 groundColor = ambientColor * 0.2;
float hemisphere = 0.5 + 0.5 * normal.y; float hemisphere = 0.5 + 0.5 * normal.y;
@@ -453,32 +566,21 @@ void main()
{ {
vec3 L = normalize(-lightDirs[i]); vec3 L = normalize(-lightDirs[i]);
vec3 H = normalize(L + viewDir); vec3 H = normalize(L + viewDir);
float NdotL = max(dot(normal, L), 0.0); float NdotL = max(dot(normal, L), 0.0);
float NdotH = max(dot(normal, H), 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 HdotV = max(dot(H, viewDir), 0.0);
float diff = NdotL; float diff = NdotL;
float spec = pow(NdotH, shininess); float spec = pow(NdotH, shininess);
// Schlick Fresnel
float fresnel = F0.x + (1.0 - F0.x) * pow(1.0 - HdotV, 5.0); float fresnel = F0.x + (1.0 - F0.x) * pow(1.0 - HdotV, 5.0);
vec3 specularColor = mix(vec3(fresnel), albedo * fresnel, metal); vec3 specularColor = mix(vec3(fresnel), albedo * fresnel, metal);
vec3 diffuse = albedo * lightColors[i] * diff * lightIntensities[i] * 1.5; vec3 diffuse = albedo * lightColors[i] * diff * lightIntensities[i] * 1.5;
vec3 specular = specularColor * spec * lightIntensities[i]; vec3 specular = specularColor * spec * lightIntensities[i];
// Energy conservation
diffuse *= (1.0 - fresnel * (1.0 - metal * 0.5)); diffuse *= (1.0 - fresnel * (1.0 - metal * 0.5));
result += diffuse + specular; result += diffuse + specular;
} }
// ACES tonemapping + gamma correction
result = ACESFilm(result * 1.2); result = ACESFilm(result * 1.2);
result = pow(result, vec3(1.0 / 2.2)); result = pow(result, vec3(1.0 / 2.2));
finalColor = vec4(result, 1.0); finalColor = vec4(result, 1.0);
}"; }";
@@ -498,6 +600,8 @@ void main()
Raylib.UnloadTexture(texture); Raylib.UnloadTexture(texture);
_textureCache.Clear(); _textureCache.Clear();
Raylib.UnloadRenderTexture(_shadowMapRT);
Raylib.UnloadShader(_shadowShader);
Raylib.UnloadShader(_shader); Raylib.UnloadShader(_shader);
} }