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] 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)
+1 -1
View File
@@ -14,7 +14,7 @@ Size=250,398
Collapsed=0
[Window][Inspector]
Pos=964,309
Pos=915,253
Size=300,400
Collapsed=0
+124 -20
View File
@@ -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<Entity, Raylib_cs.Model> _modelCache = new();
private readonly Dictionary<string, Texture2D> _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);
}