diff --git a/src/Engine.Graphics.OpenTK/OpenTKRenderer.cs b/src/Engine.Graphics.OpenTK/OpenTKRenderer.cs
index ec833aa..122161f 100644
--- a/src/Engine.Graphics.OpenTK/OpenTKRenderer.cs
+++ b/src/Engine.Graphics.OpenTK/OpenTKRenderer.cs
@@ -8,31 +8,29 @@ using Flecs.NET.Core;
using OpenTK.Graphics.OpenGL4;
using OTKMatrix = OpenTK.Mathematics.Matrix4;
using OTKVector3 = OpenTK.Mathematics.Vector3;
-using OTKVector4 = OpenTK.Mathematics.Vector4;
using EngineMaterial = Engine.Core.Components.Material;
using EngineMesh = Engine.Core.Components.Mesh;
using EngineTransform = Engine.Core.Components.Transform;
namespace Engine.Graphics.OpenTK;
-///
-/// OpenTK OpenGL renderer — full control over OpenGL state for shadow mapping.
-///
public sealed class OpenTKRenderer : IRenderer
{
private const int ShadowMapSize = 2048;
- private readonly int _program;
- private readonly int _shadowProgram;
- private readonly int _shadowFbo;
- private readonly int _shadowTexture;
+ private int _program;
+ private int _shadowProgram;
+ private int _shadowFbo;
+ private int _shadowTexture;
private readonly Dictionary _meshCache = new();
+ private readonly float[] _matrixBuf = new float[16];
+
// Uniform locations
- private readonly int _uMVP, _uModel, _uViewPos, _uMaterialColor, _uRoughness, _uMetallic;
- private readonly int _uAmbient, _uLightCount, _uLightDirs, _uLightIntensities, _uLightColors;
- private readonly int _uLightPositions, _uLightTypes, _uLightRanges;
- private readonly int _uLightViewProj, _uShadowMap, _uUseTexture;
+ private int _uMVP, _uModel, _uViewPos, _uMaterialColor, _uRoughness, _uMetallic;
+ private int _uAmbient, _uLightCount, _uLightDirs, _uLightIntensities, _uLightColors;
+ private int _uLightPositions, _uLightTypes, _uLightRanges;
+ private int _uLightViewProj, _uShadowMap, _uUseTexture;
// Light data
private readonly float[] _lightDirs = new float[12];
@@ -43,17 +41,16 @@ public sealed class OpenTKRenderer : IRenderer
private readonly float[] _lightRanges = new float[4];
private int _lightCount;
- private int _screenWidth = 1280;
- private int _screenHeight = 720;
+ private int _screenW = 1280, _screenH = 720;
private bool _disposed;
public OpenTKRenderer()
{
- GL.Enable(EnableCap.DepthTest);
-
- _program = CreateProgram(VertexShaderSource, FragmentShaderSource);
- _shadowProgram = CreateProgram(ShadowVertexSource, ShadowFragmentSource);
+ // Compile shaders
+ _program = CreateProgram(VertexSrc, FragmentSrc);
+ _shadowProgram = CreateProgram(ShadowVertSrc, ShadowFragSrc);
+ // Get uniform locations
_uMVP = GL.GetUniformLocation(_program, "mvp");
_uModel = GL.GetUniformLocation(_program, "model");
_uViewPos = GL.GetUniformLocation(_program, "viewPos");
@@ -72,9 +69,10 @@ public sealed class OpenTKRenderer : IRenderer
_uShadowMap = GL.GetUniformLocation(_program, "shadowMap");
_uUseTexture = GL.GetUniformLocation(_program, "useTexture");
- // Shadow FBO with depth texture
+ // Shadow FBO
_shadowFbo = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _shadowFbo);
+
_shadowTexture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, _shadowTexture);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent,
@@ -88,13 +86,17 @@ public sealed class OpenTKRenderer : IRenderer
GL.DrawBuffer(DrawBufferMode.None);
GL.ReadBuffer(ReadBufferMode.None);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+
+ // GL state
+ GL.Enable(EnableCap.DepthTest);
+ GL.Disable(EnableCap.CullFace);
}
- public void SetScreenSize(int w, int h) { _screenWidth = w; _screenHeight = h; }
+ public void SetScreenSize(int w, int h) { _screenW = w; _screenH = h; }
- public void RequestScreenshot(string outputPath) { }
+ public void RequestScreenshot(string path) { }
public bool IsScreenshotRequested => false;
- public IScreenshotProvider ScreenshotProvider => new OpenTKScreenshotProvider();
+ public IScreenshotProvider ScreenshotProvider => new DummyScreenshotProvider();
public void RenderWorld(World world)
{
@@ -102,19 +104,18 @@ public sealed class OpenTKRenderer : IRenderer
CollectLights(world);
var hasDirLight = _lightCount > 0 && _lightTypes[0] == (int)LightType.Directional;
- OTKMatrix lightViewProj = OTKMatrix.Identity;
+ OTKMatrix lightVP = OTKMatrix.Identity;
- // === PASS 1: Shadow map ===
+ // === PASS 1: Shadow ===
if (hasDirLight)
{
- var lightDir = new System.Numerics.Vector3(_lightDirs[0], _lightDirs[1], _lightDirs[2]);
- var sceneCenter = new System.Numerics.Vector3(0, 0.5f, 0);
- var lightPos = sceneCenter - lightDir * 30f;
- var up = MathF.Abs(System.Numerics.Vector3.Dot(lightDir, System.Numerics.Vector3.UnitY)) > 0.99f
- ? System.Numerics.Vector3.UnitZ : System.Numerics.Vector3.UnitY;
+ var lightDir = new Vector3(_lightDirs[0], _lightDirs[1], _lightDirs[2]);
+ var center = new Vector3(0, 0.5f, 0);
+ var lightPos = center - lightDir * 30f;
+ var up = MathF.Abs(Vector3.Dot(lightDir, Vector3.UnitY)) > 0.99f ? Vector3.UnitZ : Vector3.UnitY;
- lightViewProj = OTKMatrix.CreateOrthographicOffCenter(-15, 15, -15, 15, 1, 80)
- * OTKMatrix.LookAt(ToOTK(lightPos), ToOTK(sceneCenter), ToOTK(up));
+ lightVP = OTKMatrix.CreateOrthographicOffCenter(-15, 15, -15, 15, 1, 80)
+ * OTKMatrix.LookAt(ToV3(lightPos), ToV3(center), ToV3(up));
GL.Viewport(0, 0, ShadowMapSize, ShadowMapSize);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _shadowFbo);
@@ -122,34 +123,31 @@ public sealed class OpenTKRenderer : IRenderer
GL.UseProgram(_shadowProgram);
GL.CullFace(CullFaceMode.Front);
- var shadowMvpLoc = GL.GetUniformLocation(_shadowProgram, "mvp");
+ int sMvp = GL.GetUniformLocation(_shadowProgram, "mvp");
- world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) =>
+ world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform t) =>
{
if (e.Name() == "Grid" || e.Name() == "Floor") return;
- var glMesh = GetOrUploadMesh(e, mesh);
- var model = ToOTK(transform.GetMatrix());
- var mvp = lightViewProj * model;
- GL.UniformMatrix4(shadowMvpLoc, false, ref mvp);
- DrawMeshImmediate(glMesh);
+ var gm = GetOrUploadMesh(e, mesh);
+ var model = ToM4(t.GetMatrix());
+ var mvp = lightVP * model;
+ SetUniformMat4(sMvp, mvp);
+ DrawMesh(gm);
});
GL.CullFace(CullFaceMode.Back);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
}
- // === PASS 2: Main render ===
- GL.Viewport(0, 0, _screenWidth, _screenHeight);
- GL.Enable(EnableCap.DepthTest);
- GL.Disable(EnableCap.CullFace);
- GL.ClearColor(0.098f, 0.118f, 0.157f, 1);
+ // === PASS 2: Main ===
+ GL.Viewport(0, 0, _screenW, _screenH);
+ GL.ClearColor(0.098f, 0.118f, 0.157f, 1f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.UseProgram(_program);
- var view = OTKMatrix.LookAt(ToOTK(camera.Position), ToOTK(camera.Target), ToOTK(camera.Up));
+ var view = OTKMatrix.LookAt(ToV3(camera.Position), ToV3(camera.Target), ToV3(camera.Up));
var proj = OTKMatrix.CreatePerspectiveFieldOfView(camera.FieldOfView, camera.AspectRatio, camera.NearPlane, camera.FarPlane);
- // Frame uniforms
GL.Uniform3(_uViewPos, camera.Position.X, camera.Position.Y, camera.Position.Z);
GL.Uniform3(_uAmbient, 0.35f, 0.35f, 0.4f);
GL.Uniform1(_uLightCount, _lightCount);
@@ -162,123 +160,133 @@ public sealed class OpenTKRenderer : IRenderer
if (hasDirLight)
{
- GL.UniformMatrix4(_uLightViewProj, false, ref lightViewProj);
+ SetUniformMat4(_uLightViewProj, lightVP);
GL.ActiveTexture(TextureUnit.Texture1);
GL.BindTexture(TextureTarget.Texture2D, _shadowTexture);
GL.Uniform1(_uShadowMap, 1);
GL.ActiveTexture(TextureUnit.Texture0);
}
- world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) =>
+ world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform t) =>
{
if (e.Name() == "Grid") return;
- var material = e.Has() ? e.Get() : EngineMaterial.Default;
- var glMesh = GetOrUploadMesh(e, mesh);
- var model = ToOTK(transform.GetMatrix());
+ var mat = e.Has() ? e.Get() : EngineMaterial.Default;
+ var gm = GetOrUploadMesh(e, mesh);
+ var model = ToM4(t.GetMatrix());
var mvp = proj * view * model;
- GL.UniformMatrix4(_uMVP, false, ref mvp);
- GL.UniformMatrix4(_uModel, false, ref model);
- GL.Uniform4(_uMaterialColor, material.Albedo.X, material.Albedo.Y, material.Albedo.Z, 1.0f);
- GL.Uniform1(_uRoughness, material.Roughness);
- GL.Uniform1(_uMetallic, material.Metallic);
+ SetUniformMat4(_uMVP, mvp);
+ SetUniformMat4(_uModel, model);
+ GL.Uniform4(_uMaterialColor, mat.Albedo.X, mat.Albedo.Y, mat.Albedo.Z, 1f);
+ GL.Uniform1(_uRoughness, mat.Roughness);
+ GL.Uniform1(_uMetallic, mat.Metallic);
GL.Uniform1(_uUseTexture, 0);
- DrawMeshImmediate(glMesh);
+ DrawMesh(gm);
});
}
- private void DrawMeshImmediate(GLMesh mesh)
+ private void DrawMesh(GLMesh m)
{
- GL.BindVertexArray(mesh.Vao);
- GL.DrawElements(PrimitiveType.Triangles, mesh.IndexCount, DrawElementsType.UnsignedInt, 0);
+ GL.BindVertexArray(m.Vao);
+ GL.DrawElements(PrimitiveType.Triangles, m.Count, DrawElementsType.UnsignedInt, 0);
GL.BindVertexArray(0);
}
+ private void SetUniformMat4(int loc, OTKMatrix mat)
+ {
+ // OpenTK Matrix4 is row-major in memory; OpenGL expects column-major with transpose=false.
+ // Use transpose=true so OpenGL transposes our row-major data into column-major.
+ _matrixBuf[0] = mat.M11; _matrixBuf[1] = mat.M12; _matrixBuf[2] = mat.M13; _matrixBuf[3] = mat.M14;
+ _matrixBuf[4] = mat.M21; _matrixBuf[5] = mat.M22; _matrixBuf[6] = mat.M23; _matrixBuf[7] = mat.M24;
+ _matrixBuf[8] = mat.M31; _matrixBuf[9] = mat.M32; _matrixBuf[10] = mat.M33; _matrixBuf[11] = mat.M34;
+ _matrixBuf[12] = mat.M41; _matrixBuf[13] = mat.M42; _matrixBuf[14] = mat.M43; _matrixBuf[15] = mat.M44;
+ GL.UniformMatrix4(loc, 1, true, _matrixBuf);
+ }
+
private GLMesh GetOrUploadMesh(Entity e, EngineMesh mesh)
{
if (_meshCache.TryGetValue(e, out var existing))
return existing;
- var vao = GL.GenVertexArray();
+ int vao = GL.GenVertexArray();
GL.BindVertexArray(vao);
- // Position (location 0)
- var positions = new float[mesh.Vertices.Length * 3];
- for (var i = 0; i < mesh.Vertices.Length; i++)
+ // Position
+ float[] pos = new float[mesh.Vertices.Length * 3];
+ for (int i = 0; i < mesh.Vertices.Length; i++)
{
- positions[i * 3] = mesh.Vertices[i].Position.X;
- positions[i * 3 + 1] = mesh.Vertices[i].Position.Y;
- positions[i * 3 + 2] = mesh.Vertices[i].Position.Z;
+ pos[i*3] = mesh.Vertices[i].Position.X;
+ pos[i*3+1] = mesh.Vertices[i].Position.Y;
+ pos[i*3+2] = mesh.Vertices[i].Position.Z;
}
- var posVbo = GL.GenBuffer();
- GL.BindBuffer(BufferTarget.ArrayBuffer, posVbo);
- GL.BufferData(BufferTarget.ArrayBuffer, positions.Length * sizeof(float), positions, BufferUsageHint.StaticDraw);
+ int vboPos = GL.GenBuffer();
+ GL.BindBuffer(BufferTarget.ArrayBuffer, vboPos);
+ GL.BufferData(BufferTarget.ArrayBuffer, pos.Length * sizeof(float), pos, BufferUsageHint.StaticDraw);
GL.EnableVertexAttribArray(0);
- GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 0, 0);
- // Normal (location 1)
- var normals = new float[mesh.Vertices.Length * 3];
- for (var i = 0; i < mesh.Vertices.Length; i++)
+ // Normal
+ float[] nrm = new float[mesh.Vertices.Length * 3];
+ for (int i = 0; i < mesh.Vertices.Length; i++)
{
- normals[i * 3] = mesh.Vertices[i].Normal.X;
- normals[i * 3 + 1] = mesh.Vertices[i].Normal.Y;
- normals[i * 3 + 2] = mesh.Vertices[i].Normal.Z;
+ nrm[i*3] = mesh.Vertices[i].Normal.X;
+ nrm[i*3+1] = mesh.Vertices[i].Normal.Y;
+ nrm[i*3+2] = mesh.Vertices[i].Normal.Z;
}
- var nrmVbo = GL.GenBuffer();
- GL.BindBuffer(BufferTarget.ArrayBuffer, nrmVbo);
- GL.BufferData(BufferTarget.ArrayBuffer, normals.Length * sizeof(float), normals, BufferUsageHint.StaticDraw);
+ int vboNrm = GL.GenBuffer();
+ GL.BindBuffer(BufferTarget.ArrayBuffer, vboNrm);
+ GL.BufferData(BufferTarget.ArrayBuffer, nrm.Length * sizeof(float), nrm, BufferUsageHint.StaticDraw);
GL.EnableVertexAttribArray(1);
- GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
+ GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 0, 0);
- // Color (location 2)
- var colors = new float[mesh.Vertices.Length * 4];
- for (var i = 0; i < mesh.Vertices.Length; i++)
+ // Color
+ float[] col = new float[mesh.Vertices.Length * 4];
+ for (int i = 0; i < mesh.Vertices.Length; i++)
{
- colors[i * 4] = mesh.Vertices[i].Color.X;
- colors[i * 4 + 1] = mesh.Vertices[i].Color.Y;
- colors[i * 4 + 2] = mesh.Vertices[i].Color.Z;
- colors[i * 4 + 3] = 1.0f;
+ col[i*4] = mesh.Vertices[i].Color.X;
+ col[i*4+1] = mesh.Vertices[i].Color.Y;
+ col[i*4+2] = mesh.Vertices[i].Color.Z;
+ col[i*4+3] = 1f;
}
- var colVbo = GL.GenBuffer();
- GL.BindBuffer(BufferTarget.ArrayBuffer, colVbo);
- GL.BufferData(BufferTarget.ArrayBuffer, colors.Length * sizeof(float), colors, BufferUsageHint.StaticDraw);
+ int vboCol = GL.GenBuffer();
+ GL.BindBuffer(BufferTarget.ArrayBuffer, vboCol);
+ GL.BufferData(BufferTarget.ArrayBuffer, col.Length * sizeof(float), col, BufferUsageHint.StaticDraw);
GL.EnableVertexAttribArray(2);
- GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, 4 * sizeof(float), 0);
+ GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, 0, 0);
// Indices
- var ebo = GL.GenBuffer();
+ int ebo = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
GL.BufferData(BufferTarget.ElementArrayBuffer, mesh.Indices.Length * sizeof(uint), mesh.Indices, BufferUsageHint.StaticDraw);
GL.BindVertexArray(0);
- var glMesh = new GLMesh(vao, mesh.Indices.Length);
- _meshCache[e] = glMesh;
- return glMesh;
+ var gm = new GLMesh(vao, mesh.Indices.Length);
+ _meshCache[e] = gm;
+ return gm;
}
private void CollectLights(World world)
{
- var count = 0;
+ int count = 0;
world.Each((Entity e, ref Light light) =>
{
if (count >= 4) return;
- _lightDirs[count * 3] = light.Direction.X;
- _lightDirs[count * 3 + 1] = light.Direction.Y;
- _lightDirs[count * 3 + 2] = light.Direction.Z;
- _lightPositions[count * 3] = light.Position.X;
- _lightPositions[count * 3 + 1] = light.Position.Y;
- _lightPositions[count * 3 + 2] = light.Position.Z;
+ _lightDirs[count*3] = light.Direction.X;
+ _lightDirs[count*3+1] = light.Direction.Y;
+ _lightDirs[count*3+2] = light.Direction.Z;
+ _lightPositions[count*3] = light.Position.X;
+ _lightPositions[count*3+1] = light.Position.Y;
+ _lightPositions[count*3+2] = light.Position.Z;
_lightIntensities[count] = light.Intensity;
- _lightColors[count * 3] = light.Color.X;
- _lightColors[count * 3 + 1] = light.Color.Y;
- _lightColors[count * 3 + 2] = light.Color.Z;
+ _lightColors[count*3] = light.Color.X;
+ _lightColors[count*3+1] = light.Color.Y;
+ _lightColors[count*3+2] = light.Color.Z;
_lightTypes[count] = (int)light.Type;
_lightRanges[count] = light.Range;
count++;
});
-
if (count == 0)
{
_lightDirs[0] = 0.5f; _lightDirs[1] = -1; _lightDirs[2] = -0.5f;
@@ -286,51 +294,49 @@ public sealed class OpenTKRenderer : IRenderer
_lightTypes[0] = (int)LightType.Directional; _lightRanges[0] = 20;
count = 1;
}
- for (var i = count; i < 4; i++) { _lightIntensities[i] = 0; _lightTypes[i] = 0; }
+ for (int i = count; i < 4; i++) { _lightIntensities[i] = 0; _lightTypes[i] = 0; }
_lightCount = count;
}
private Camera GetCamera(World world)
{
- var cam = new Camera(new System.Numerics.Vector3(0, 0.75f, -30),
- new System.Numerics.Vector3(0, 0.5f, 0), System.Numerics.Vector3.UnitY,
- MathF.PI / 12, 16f / 9f, 0.1f, 100f);
+ var cam = new Camera(new Vector3(0, 0.75f, -30), new Vector3(0, 0.5f, 0), Vector3.UnitY, MathF.PI/12, 16f/9f, 0.1f, 100f);
world.Each((Entity e, ref Camera c) => cam = c);
return cam;
}
- private static OTKMatrix ToOTK(System.Numerics.Matrix4x4 m) => new(
+ private static OTKMatrix ToM4(System.Numerics.Matrix4x4 m) => new(
m.M11, m.M12, m.M13, m.M14,
m.M21, m.M22, m.M23, m.M24,
m.M31, m.M32, m.M33, m.M34,
m.M41, m.M42, m.M43, m.M44);
- private static OTKVector3 ToOTK(System.Numerics.Vector3 v) => new(v.X, v.Y, v.Z);
+ private static OTKVector3 ToV3(Vector3 v) => new(v.X, v.Y, v.Z);
- private static int CreateProgram(string vs, string fs)
+ private static int CreateProgram(string vsSrc, string fsSrc)
{
- var vertex = GL.CreateShader(ShaderType.VertexShader);
- GL.ShaderSource(vertex, vs);
- GL.CompileShader(vertex);
- GL.GetShader(vertex, ShaderParameter.CompileStatus, out int vStatus);
- if (vStatus == 0) throw new Exception($"VS: {GL.GetShaderInfoLog(vertex)}");
+ int vs = GL.CreateShader(ShaderType.VertexShader);
+ GL.ShaderSource(vs, vsSrc);
+ GL.CompileShader(vs);
+ GL.GetShader(vs, ShaderParameter.CompileStatus, out int vsOk);
+ if (vsOk == 0) throw new Exception($"VS compile: {GL.GetShaderInfoLog(vs)}");
- var fragment = GL.CreateShader(ShaderType.FragmentShader);
- GL.ShaderSource(fragment, fs);
- GL.CompileShader(fragment);
- GL.GetShader(fragment, ShaderParameter.CompileStatus, out int fStatus);
- if (fStatus == 0) throw new Exception($"FS: {GL.GetShaderInfoLog(fragment)}");
+ int fs = GL.CreateShader(ShaderType.FragmentShader);
+ GL.ShaderSource(fs, fsSrc);
+ GL.CompileShader(fs);
+ GL.GetShader(fs, ShaderParameter.CompileStatus, out int fsOk);
+ if (fsOk == 0) throw new Exception($"FS compile: {GL.GetShaderInfoLog(fs)}");
- var program = GL.CreateProgram();
- GL.AttachShader(program, vertex);
- GL.AttachShader(program, fragment);
- GL.LinkProgram(program);
- GL.GetProgram(program, GetProgramParameterName.LinkStatus, out int lStatus);
- if (lStatus == 0) throw new Exception($"Link: {GL.GetProgramInfoLog(program)}");
+ int prog = GL.CreateProgram();
+ GL.AttachShader(prog, vs);
+ GL.AttachShader(prog, fs);
+ GL.LinkProgram(prog);
+ GL.GetProgram(prog, GetProgramParameterName.LinkStatus, out int linkOk);
+ if (linkOk == 0) throw new Exception($"Link: {GL.GetProgramInfoLog(prog)}");
- GL.DeleteShader(vertex);
- GL.DeleteShader(fragment);
- return program;
+ GL.DeleteShader(vs);
+ GL.DeleteShader(fs);
+ return prog;
}
public void Dispose()
@@ -345,15 +351,16 @@ public sealed class OpenTKRenderer : IRenderer
GL.DeleteTexture(_shadowTexture);
}
- // Shaders (copied from Silk.NET OpenGL backend — backend-agnostic GLSL 330 core)
- private const string ShadowVertexSource = @"#version 330 core
+ // === Shaders ===
+ private const string ShadowVertSrc = @"#version 330 core
layout(location=0) in vec3 aPos;
uniform mat4 mvp;
-void main(){gl_Position=mvp*vec4(aPos,1.0);}";
- private const string ShadowFragmentSource = @"#version 330 core
+void main(){ gl_Position = mvp * vec4(aPos, 1.0); }";
+
+ private const string ShadowFragSrc = @"#version 330 core
void main(){}";
- private const string VertexShaderSource = @"#version 330 core
+ private const string VertexSrc = @"#version 330 core
layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aNormal;
layout(location=2) in vec4 aColor;
@@ -362,15 +369,16 @@ uniform mat4 model;
out vec3 vNormal;
out vec3 vWorldPos;
out vec4 vColor;
-void main(){
- vec4 wp=model*vec4(aPos,1.0);
- vWorldPos=wp.xyz;
- vNormal=mat3(transpose(inverse(model)))*aNormal;
- vColor=aColor;
- gl_Position=mvp*vec4(aPos,1.0);
+void main()
+{
+ vec4 wp = model * vec4(aPos, 1.0);
+ vWorldPos = wp.xyz;
+ vNormal = mat3(transpose(inverse(model))) * aNormal;
+ vColor = aColor;
+ gl_Position = mvp * vec4(aPos, 1.0);
}";
- private const string FragmentShaderSource = @"#version 330 core
+ private const string FragmentSrc = @"#version 330 core
in vec3 vNormal;
in vec3 vWorldPos;
in vec4 vColor;
@@ -390,48 +398,94 @@ uniform float lightRanges[4];
uniform mat4 lightViewProj;
uniform sampler2D shadowMap;
uniform int useTexture;
-vec3 ACESFilm(vec3 x){const float a=2.51,b=0.03,c=2.43,d=0.59,e=0.14;return clamp((x*(a*x+b))/(x*(c*x+d)+e),0.0,1.0);}
-float Attenuation(float dist,float range){float r=max(range,0.001),d=max(dist,0.001);float x=d/r,x2=x*x,x4=x2*x2;return clamp(1.0/(1.0+25.0*x4),0.0,1.0)*smoothstep(1.0,0.0,x);}
-float CalculateShadow(vec3 worldPos){vec4 lp=lightViewProj*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.005;vec2 ts=vec2(1.0/2048.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);
- 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=1.0;
- if(lightCount>0&&lightTypes[0]==0)shadow=CalculateShadow(vWorldPos);
- vec3 F0=mix(vec3(0.04),albedo,metal);
- float shininess=mix(8.0,256.0,1.0-rough);
- for(int i=0;i 1.0 || uvw.y < 0.0 || uvw.y > 1.0 || uvw.z > 1.0)
+ return 1.0;
+ float bias = 0.005;
+ vec2 ts = vec2(1.0 / 2048.0);
+ float s = 0.0;
+ for (int x = -1; x <= 1; x++) {
+ for (int y = -1; y <= 1; y++) {
+ float dpt = texture(shadowMap, uvw.xy + vec2(x, y) * ts).r;
+ s += (uvw.z - bias > dpt) ? 0.3 : 1.0;
+ }
}
- result=ACESFilm(result*1.2);
- result=pow(result,vec3(1.0/2.2));
- finalColor=vec4(result,1.0);
+ return s / 9.0;
+}
+
+void main()
+{
+ vec3 normal = normalize(vNormal);
+ 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 = 1.0;
+ if (lightCount > 0 && lightTypes[0] == 0)
+ shadow = CalculateShadow(vWorldPos);
+
+ 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;
+ float atten = 1.0;
+ if (lightTypes[i] == 1) {
+ vec3 toLight = lightPositions[i] - vWorldPos;
+ float dist = length(toLight);
+ L = toLight / max(dist, 0.001);
+ atten = Attenuation(dist, lightRanges[i]);
+ } else {
+ L = normalize(-lightDirs[i]);
+ }
+ float lightShadow = (i == 0 && lightTypes[0] == 0) ? shadow : 1.0;
+ 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 spec = pow(NdotH, shininess);
+ vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0);
+ vec3 specColor = mix(fresnel, albedo * fresnel, metal);
+ vec3 diffuse = albedo * lightColors[i] * NdotL * lightIntensities[i] * atten * 1.5 * lightShadow;
+ vec3 specular = specColor * spec * lightIntensities[i] * atten * lightShadow;
+ 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);
}";
- private readonly record struct GLMesh(int Vao, int IndexCount);
+ private readonly record struct GLMesh(int Vao, int Count);
- private sealed class OpenTKScreenshotProvider : IScreenshotProvider
+ private sealed class DummyScreenshotProvider : IScreenshotProvider
{
public Task CaptureAsync(string outputPath) => Task.FromResult(Array.Empty());
}
diff --git a/src/Engine.Graphics.OpenTK/OpenTKWindow.cs b/src/Engine.Graphics.OpenTK/OpenTKWindow.cs
index c62241e..b93b334 100644
--- a/src/Engine.Graphics.OpenTK/OpenTKWindow.cs
+++ b/src/Engine.Graphics.OpenTK/OpenTKWindow.cs
@@ -2,14 +2,9 @@ using System;
using Engine.Core;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
-using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Engine.Graphics.OpenTK;
-///
-/// OpenTK GameWindow-backed implementation of IWindow.
-/// Uses ProcessEvents() (non-blocking) instead of Run() to integrate with our main loop.
-///
public sealed class OpenTKWindow : GameWindow, IWindow, IDisposable
{
private readonly OpenTKInputState _input = new();
@@ -30,8 +25,13 @@ public sealed class OpenTKWindow : GameWindow, IWindow, IDisposable
Profile = ContextProfile.Core,
Flags = ContextFlags.ForwardCompatible,
Vsync = VSyncMode.Off,
+ NumberOfSamples = 0,
})
{
+ // GameWindow creates the GL context in the base constructor.
+ // MakeCurrent is called automatically by OpenTK on first ProcessEvents.
+ // We call it explicitly here to ensure it's ready before renderer creation.
+ MakeCurrent();
}
public void PumpEvents()