diff --git a/Chapter4/4.1-Framebuffers/4-1-FrameBuffers.csproj b/Chapter4/4.1-Framebuffers/4-1-FrameBuffers.csproj
new file mode 100644
index 0000000..c074929
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/4-1-FrameBuffers.csproj
@@ -0,0 +1,32 @@
+
+
+ WinExe
+ LearnOpenTK
+ LearnOpenTK
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/Chapter4/4.1-Framebuffers/OpenTK.dll.config b/Chapter4/4.1-Framebuffers/OpenTK.dll.config
new file mode 100644
index 0000000..7098d39
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/OpenTK.dll.config
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter4/4.1-Framebuffers/Program.cs b/Chapter4/4.1-Framebuffers/Program.cs
new file mode 100644
index 0000000..728e2c5
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/Program.cs
@@ -0,0 +1,25 @@
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.Desktop;
+
+namespace LearnOpenTK
+{
+ public static class Program
+ {
+ private static void Main()
+ {
+ var nativeWindowSettings = new NativeWindowSettings()
+ {
+ Size = new Vector2i(800, 600),
+ Title = "LearnOpenTK - Frame Buffers",
+ // This is needed to run on macos
+ Flags = ContextFlags.ForwardCompatible,
+ };
+
+ using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
+ {
+ window.Run();
+ }
+ }
+ }
+}
diff --git a/Chapter4/4.1-Framebuffers/Resources/container.jpg b/Chapter4/4.1-Framebuffers/Resources/container.jpg
new file mode 100644
index 0000000..50aa4c9
Binary files /dev/null and b/Chapter4/4.1-Framebuffers/Resources/container.jpg differ
diff --git a/Chapter4/4.1-Framebuffers/Resources/metal.png b/Chapter4/4.1-Framebuffers/Resources/metal.png
new file mode 100644
index 0000000..58f02e2
Binary files /dev/null and b/Chapter4/4.1-Framebuffers/Resources/metal.png differ
diff --git a/Chapter4/4.1-Framebuffers/Shaders/framebuffers.fs b/Chapter4/4.1-Framebuffers/Shaders/framebuffers.fs
new file mode 100644
index 0000000..b342078
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/Shaders/framebuffers.fs
@@ -0,0 +1,11 @@
+#version 330 core
+out vec4 FragColor;
+
+in vec2 TexCoords;
+
+uniform sampler2D texture1;
+
+void main()
+{
+ FragColor = texture(texture1, TexCoords);
+}
\ No newline at end of file
diff --git a/Chapter4/4.1-Framebuffers/Shaders/framebuffers.vs b/Chapter4/4.1-Framebuffers/Shaders/framebuffers.vs
new file mode 100644
index 0000000..bdc4d20
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/Shaders/framebuffers.vs
@@ -0,0 +1,15 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec2 aTexCoords;
+
+out vec2 TexCoords;
+
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+
+void main()
+{
+ TexCoords = aTexCoords;
+ gl_Position = vec4(aPos, 1.0) * model * view * projection;
+}
\ No newline at end of file
diff --git a/Chapter4/4.1-Framebuffers/Shaders/framebuffers_screen.fs b/Chapter4/4.1-Framebuffers/Shaders/framebuffers_screen.fs
new file mode 100644
index 0000000..a23a2a8
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/Shaders/framebuffers_screen.fs
@@ -0,0 +1,12 @@
+#version 330 core
+out vec4 FragColor;
+
+in vec2 TexCoords;
+
+uniform sampler2D screenTexture;
+
+void main()
+{
+ vec3 col = texture(screenTexture, TexCoords).rgb;
+ FragColor = vec4(col, 1.0);
+}
\ No newline at end of file
diff --git a/Chapter4/4.1-Framebuffers/Shaders/framebuffers_screen.vs b/Chapter4/4.1-Framebuffers/Shaders/framebuffers_screen.vs
new file mode 100644
index 0000000..1376b62
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/Shaders/framebuffers_screen.vs
@@ -0,0 +1,11 @@
+#version 330 core
+layout (location = 0) in vec2 aPos;
+layout (location = 1) in vec2 aTexCoords;
+
+out vec2 TexCoords;
+
+void main()
+{
+ TexCoords = aTexCoords;
+ gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/Chapter4/4.1-Framebuffers/Window.cs b/Chapter4/4.1-Framebuffers/Window.cs
new file mode 100644
index 0000000..968605b
--- /dev/null
+++ b/Chapter4/4.1-Framebuffers/Window.cs
@@ -0,0 +1,352 @@
+using LearnOpenTK.Common;
+using OpenTK.Graphics.OpenGL4;
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.Desktop;
+using OpenTK.Windowing.GraphicsLibraryFramework;
+using System;
+
+namespace LearnOpenTK
+{
+ public class Window : GameWindow
+ {
+ // set up vertex data (and buffer(s)) and configure vertex attributes
+ // ------------------------------------------------------------------
+ float[] cubeVertices = {
+ // positions // texture Coords
+ -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
+ 0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
+ 0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
+ 0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
+ -0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
+ -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
+
+ -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
+ 0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
+ 0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
+ 0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
+ -0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
+ -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
+
+ -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
+ -0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
+ -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
+ -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
+ -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
+ -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
+
+ 0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
+ 0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
+ 0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
+ 0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
+ 0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
+ 0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
+
+ -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
+ 0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
+ 0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
+ 0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
+ -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
+ -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
+
+ -0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
+ 0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
+ 0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
+ 0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
+ -0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
+ -0.5f, 0.5f, -0.5f, 0.0f, 1.0f
+ };
+ float[] planeVertices = {
+ // positions // texture Coords
+ 5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
+ -5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
+ -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
+
+ 5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
+ -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
+ 5.0f, -0.5f, -5.0f, 2.0f, 2.0f
+ };
+ float[] quadVertices = { // vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates. NOTE that this plane is now much smaller and at the top of the screen
+ // positions // texCoords
+ -0.3f, 1.0f, 0.0f, 1.0f,
+ -0.3f, 0.7f, 0.0f, 0.0f,
+ 0.3f, 0.7f, 1.0f, 0.0f,
+
+ -0.3f, 1.0f, 0.0f, 1.0f,
+ 0.3f, 0.7f, 1.0f, 0.0f,
+ 0.3f, 1.0f, 1.0f, 1.0f
+ };
+
+ // renderCube() renders a 1x1 3D cube in NDC.
+ // -------------------------------------------------
+ private Texture cubeTexture, floorTexture;
+ // timing
+ float deltaTime = 0.0f;
+ float lastFrame = 0.0f;
+ float prevMouseWheel;
+
+ private Shader shader, screenShader;
+ private int cubeVAO, planeVAO, quadVAO;
+ private int cubeVBO, planeVBO, quadVBO;
+ private Camera _camera;
+
+ private int framebuffer;
+ private int textureColorBuffer;
+ private int renderBuffer;
+
+ private bool _firstMove = true;
+
+ private Vector2 _lastPos;
+
+ public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
+ : base(gameWindowSettings, nativeWindowSettings)
+ {
+ }
+
+ private static void SetupBuffer(ref int vao, ref int vbo, int positionSize, float[] vertices)
+ {
+ vao = GL.GenVertexArray();
+ vbo = GL.GenBuffer();
+ GL.BindVertexArray(vao);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
+ GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
+ // position coords
+ GL.EnableVertexAttribArray(0); // aPos
+ GL.VertexAttribPointer(0, positionSize, VertexAttribPointerType.Float, false, (2 + positionSize) * sizeof(float), 0);
+ // texture coords
+ GL.EnableVertexAttribArray(1); // aTexCoords
+ GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, (2 + positionSize) * sizeof(float), (positionSize * sizeof(float)));
+ }
+
+ protected void InitGL()
+ {
+ // cube VAO
+ SetupBuffer(ref cubeVAO, ref cubeVBO, 3, cubeVertices);
+ // plane VAO
+ SetupBuffer(ref planeVAO, ref planeVBO, 3, planeVertices);
+ // screen quad VAO
+ SetupBuffer(ref quadVAO, ref quadVBO, 2, quadVertices);
+
+ // load textures
+ // -------------
+ cubeTexture = Texture.LoadFromFile("Resources/container.jpg");
+ floorTexture = Texture.LoadFromFile("Resources/metal.png");
+
+ // build and compile shaders
+ // -------------------------
+ shader = new Shader("Shaders/framebuffers.vs", "Shaders/framebuffers.fs");
+ screenShader = new Shader("Shaders/framebuffers_screen.vs", "Shaders/framebuffers_screen.fs");
+
+ shader.Use();
+ shader.SetInt("texture1", 0);
+
+ screenShader.Use();
+ screenShader.SetInt("screenTexture", 0);
+
+
+ // framebuffer configuration
+ // -------------------------
+ framebuffer = GL.GenFramebuffer();
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebuffer);
+ // create depth texture
+ textureColorBuffer = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2D, textureColorBuffer);
+ GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, 800, 600, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, textureColorBuffer, 0);
+
+ // create a renderbuffer object for depth and stencil attachment (we won't be sampling these)
+
+ renderBuffer = GL.GenRenderbuffer();
+ GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, renderBuffer);
+ GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, 800, 600); // use a single renderbuffer object for both a depth AND stencil buffer.
+ GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, renderBuffer); // now actually attach it
+ // now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now
+ if (GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferErrorCode.FramebufferComplete)
+ Console.WriteLine("ERROR::FRAMEBUFFER:: Framebuffer is not complete!\n");
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+ }
+ protected override void OnLoad()
+ {
+ base.OnLoad();
+
+ InitGL();
+
+ _camera = new Camera(new Vector3(0.0f, 0.0f, 3.0f), Size.X / (float)Size.Y);
+ //WindowState = WindowState.Maximized;
+ }
+
+ protected override void OnRenderFrame(FrameEventArgs e)
+ {
+ base.OnRenderFrame(e);
+
+ // per-frame time logic
+ // --------------------
+ //deltaTime = currentFrame - lastFrame;
+ //lastFrame = currentFrame;
+
+
+ // first render pass: mirror texture.
+ // bind to framebuffer and draw to color texture as we normally
+ // would, but with the view camera reversed.
+ // bind to framebuffer and draw scene as we normally would to color texture
+ // ------------------------------------------------------------------------
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebuffer);
+ GL.Enable(EnableCap.DepthTest);
+
+ // make sure we clear the framebuffer's content
+ GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+ shader.Use();
+
+ _camera.Yaw += 180.0f; // rotate the camera's yaw 180 degrees around
+
+ var view = _camera.GetViewMatrix();
+ shader.SetMatrix4("view", view);
+
+ //_camera.ProcessMouseMovement(0, 0, false); // call this to make sure it updates its camera vectors, note that we disable pitch constrains for this specific case (otherwise we can't reverse camera's pitch values)
+ _camera.Yaw -= 180.0f; // reset it back to its original orientation
+ //camera.ProcessMouseMovement(0, 0, true);
+ Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(_camera.Fov), 800f/600f, 0.1f, 100.0f);
+
+ shader.SetMatrix4("projection", projection);
+
+ renderScene();
+
+ // second render pass: draw as normal
+ // ----------------------------------
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+ GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+ shader.SetMatrix4("view", _camera.GetViewMatrix());
+ renderScene();
+
+ // now draw the mirror quad with screen texture
+ // --------------------------------------------
+ GL.Disable(EnableCap.DepthTest); // disable depth test so screen-space quad isn't discarded due to depth test.
+
+ screenShader.Use();
+ GL.BindVertexArray(quadVAO);
+ GL.BindTexture(TextureTarget.Texture2D, textureColorBuffer); // use the color attachment texture as the texture of the quad plane
+ GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
+
+ // GL.fw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
+ // -------------------------------------------------------------------------------
+ SwapBuffers();
+ }
+
+ // renders the 3D scene
+ // --------------------
+ void renderScene()
+ {
+
+ var model = Matrix4.Identity;
+ // cubes
+ GL.BindVertexArray(cubeVAO);
+ cubeTexture.Use(TextureUnit.Texture0);
+ model *= Matrix4.CreateTranslation(-1.0f, 0.0f, -1.0f);
+ shader.SetMatrix4("model", model);
+ GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
+ model = Matrix4.CreateTranslation(2.0f, 0.0f, 0.0f);
+ shader.SetMatrix4("model", model);
+ GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
+
+ // floor
+ GL.BindVertexArray(planeVAO);
+ floorTexture.Use(TextureUnit.Texture0);
+ shader.SetMatrix4("model", Matrix4.Identity);
+ GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
+ GL.BindVertexArray(0);
+
+ }
+
+ protected override void OnUpdateFrame(FrameEventArgs e)
+ {
+ base.OnUpdateFrame(e);
+
+ if (!IsFocused)
+ {
+ return;
+ }
+
+ var input = KeyboardState;
+
+ if (input.IsKeyDown(Keys.Escape))
+ {
+ Close();
+ }
+
+ const float cameraSpeed = 1.5f;
+ const float sensitivity = 0.2f;
+
+ if (input.IsKeyDown(Keys.W))
+ {
+ _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
+ }
+ if (input.IsKeyDown(Keys.S))
+ {
+ _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
+ }
+ if (input.IsKeyDown(Keys.A))
+ {
+ _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
+ }
+ if (input.IsKeyDown(Keys.D))
+ {
+ _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
+ }
+ if (input.IsKeyDown(Keys.Space))
+ {
+ _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
+ }
+ if (input.IsKeyDown(Keys.LeftShift))
+ {
+ _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
+ }
+
+ var mouse = MouseState;
+
+ if (_firstMove)
+ {
+ _lastPos = new Vector2(mouse.X, mouse.Y);
+ _firstMove = false;
+ }
+ else
+ {
+ var deltaX = mouse.X - _lastPos.X;
+ var deltaY = mouse.Y - _lastPos.Y;
+ _lastPos = new Vector2(mouse.X, mouse.Y);
+
+ _camera.Yaw += deltaX * sensitivity;
+ _camera.Pitch -= deltaY * sensitivity;
+ }
+ }
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ base.OnMouseWheel(e);
+
+ if (prevMouseWheel - e.OffsetY < 0)
+ _camera.Fov --;
+ else if (prevMouseWheel - e.OffsetY > 0)
+ _camera.Fov ++;
+
+ prevMouseWheel = e.OffsetY;
+ }
+
+ protected override void OnResize(ResizeEventArgs e)
+ {
+ base.OnResize(e);
+
+ GL.Viewport(0, 0, Size.X, Size.Y);
+ if (_camera != null)
+ _camera.AspectRatio = Size.X / (float)Size.Y;
+ }
+ }
+}
+
+
+
diff --git a/Chapter5/3.1.3-ShadowMapping/3-1-AdvancedLighting-ShadowMapping.csproj b/Chapter5/3.1.3-ShadowMapping/3-1-AdvancedLighting-ShadowMapping.csproj
new file mode 100644
index 0000000..d9a2e9e
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/3-1-AdvancedLighting-ShadowMapping.csproj
@@ -0,0 +1,30 @@
+
+
+ WinExe
+ LearnOpenTK
+ LearnOpenTK
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/Chapter5/3.1.3-ShadowMapping/OpenTK.dll.config b/Chapter5/3.1.3-ShadowMapping/OpenTK.dll.config
new file mode 100644
index 0000000..7098d39
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/OpenTK.dll.config
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter5/3.1.3-ShadowMapping/Program.cs b/Chapter5/3.1.3-ShadowMapping/Program.cs
new file mode 100644
index 0000000..5f8b59e
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Program.cs
@@ -0,0 +1,25 @@
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.Desktop;
+
+namespace LearnOpenTK
+{
+ public static class Program
+ {
+ private static void Main()
+ {
+ var nativeWindowSettings = new NativeWindowSettings()
+ {
+ Size = new Vector2i(800, 600),
+ Title = "LearnOpenTK - Shadow Mapping",
+ // This is needed to run on macos
+ Flags = ContextFlags.ForwardCompatible,
+ };
+
+ using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
+ {
+ window.Run();
+ }
+ }
+ }
+}
diff --git a/Chapter5/3.1.3-ShadowMapping/Resources/wood.png b/Chapter5/3.1.3-ShadowMapping/Resources/wood.png
new file mode 100644
index 0000000..c785f41
Binary files /dev/null and b/Chapter5/3.1.3-ShadowMapping/Resources/wood.png differ
diff --git a/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.debug_quad.vs b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.debug_quad.vs
new file mode 100644
index 0000000..9f93e29
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.debug_quad.vs
@@ -0,0 +1,11 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec2 aTexCoords;
+
+out vec2 TexCoords;
+
+void main()
+{
+ TexCoords = aTexCoords;
+ gl_Position = vec4(aPos, 1.0);
+}
\ No newline at end of file
diff --git a/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.debug_quad_depth.fs b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.debug_quad_depth.fs
new file mode 100644
index 0000000..f63882b
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.debug_quad_depth.fs
@@ -0,0 +1,22 @@
+#version 330 core
+out vec4 FragColor;
+
+in vec2 TexCoords;
+
+uniform sampler2D depthMap;
+uniform float near_plane;
+uniform float far_plane;
+
+// required when using a perspective projection matrix
+float LinearizeDepth(float depth)
+{
+ float z = depth * 2.0 - 1.0; // Back to NDC
+ return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane));
+}
+
+void main()
+{
+ float depthValue = texture(depthMap, TexCoords).r;
+ FragColor = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective
+ FragColor = vec4(vec3(depthValue), 1.0); // orthographic
+}
\ No newline at end of file
diff --git a/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping.fs b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping.fs
new file mode 100644
index 0000000..dafd559
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping.fs
@@ -0,0 +1,92 @@
+#version 330 core
+out vec4 FragColor;
+
+in VS_OUT {
+ vec3 FragPos;
+ vec3 Normal;
+ vec2 TexCoords;
+ vec4 FragPosLightSpace;
+} fs_in;
+
+uniform sampler2D diffuseTexture;
+uniform sampler2D shadowMap;
+
+uniform vec3 lightPos;
+uniform vec3 viewPos;
+
+float ShadowCalculation(vec4 fragPosLightSpace)
+{
+ // perform perspective divide
+ vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
+ // transform to [0,1] range
+ projCoords = projCoords * 0.5 + 0.5;
+ // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
+ float closestDepth = texture(shadowMap, projCoords.xy).r;
+ // get depth of current fragment from light's perspective
+ float currentDepth = projCoords.z;
+ // calculate bias (based on depth map resolution and slope)
+ vec3 normal = normalize(fs_in.Normal);
+ vec3 lightDir = normalize(lightPos - fs_in.FragPos);
+ float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
+ // check whether current frag pos is in shadow
+ // float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
+ // PCF
+ float shadow = 0.0;
+ vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
+ for(int x = -1; x <= 1; ++x)
+ {
+ for(int y = -1; y <= 1; ++y)
+ {
+ float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
+ shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
+ }
+ }
+ shadow /= 9.0;
+
+ // keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
+ if(projCoords.z > 1.0)
+ shadow = 0.0;
+
+ return shadow;
+}
+float ShadowCalculation2(vec4 fragPosLightSpace)
+{
+ // perform perspective divide
+ vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
+ // transform to [0,1] range
+ projCoords = projCoords * 0.5 + 0.5;
+ // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
+ float closestDepth = texture(shadowMap, projCoords.xy).r;
+ // get depth of current fragment from light's perspective
+ float currentDepth = projCoords.z;
+ // check whether current frag pos is in shadow
+ float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
+
+ return shadow;
+}
+
+void main()
+{
+ vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
+ vec3 normal = normalize(fs_in.Normal);
+ vec3 lightColor = vec3(0.3);
+ // ambient
+ vec3 ambient = 0.3 * lightColor;
+ // diffuse
+ vec3 lightDir = normalize(lightPos - fs_in.FragPos);
+ float diff = max(dot(lightDir, normal), 0.0);
+ vec3 diffuse = diff * lightColor;
+ // specular
+ vec3 viewDir = normalize(viewPos - fs_in.FragPos);
+ vec3 reflectDir = reflect(-lightDir, normal);
+ float spec = 0.0;
+ vec3 halfwayDir = normalize(lightDir + viewDir);
+ spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
+ vec3 specular = spec * lightColor;
+
+ // calculate shadow
+ float shadow = ShadowCalculation(fs_in.FragPosLightSpace);
+ vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;
+
+ FragColor = vec4(lighting, 1.0);
+}
\ No newline at end of file
diff --git a/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping.vs b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping.vs
new file mode 100644
index 0000000..e1ce622
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping.vs
@@ -0,0 +1,27 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec3 aNormal;
+layout (location = 2) in vec2 aTexCoords;
+
+out vec2 TexCoords;
+
+out VS_OUT {
+ vec3 FragPos;
+ vec3 Normal;
+ vec2 TexCoords;
+ vec4 FragPosLightSpace;
+} vs_out;
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform mat4 model;
+uniform mat4 lightSpaceMatrix;
+
+void main()
+{
+ vs_out.FragPos = vec3(vec4(aPos, 1.0) * model);
+ vs_out.Normal = aNormal * transpose(inverse(mat3(model)));
+ vs_out.TexCoords = aTexCoords;
+ vs_out.FragPosLightSpace = vec4(vs_out.FragPos, 1.0) * lightSpaceMatrix;
+ gl_Position = vec4(aPos, 1.0) * model * view * projection;
+}
\ No newline at end of file
diff --git a/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping_depth.fs b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping_depth.fs
new file mode 100644
index 0000000..ffae5f0
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping_depth.fs
@@ -0,0 +1,6 @@
+#version 330 core
+
+void main()
+{
+ // gl_FragDepth = gl_FragCoord.z;
+}
\ No newline at end of file
diff --git a/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping_depth.vs b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping_depth.vs
new file mode 100644
index 0000000..d794128
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Shaders/3.1.3.shadow_mapping_depth.vs
@@ -0,0 +1,11 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+
+uniform mat4 lightSpaceMatrix;
+uniform mat4 model;
+
+void main()
+{
+ //gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
+ gl_Position = vec4(aPos, 1.0) * model * lightSpaceMatrix;
+}
\ No newline at end of file
diff --git a/Chapter5/3.1.3-ShadowMapping/Window.cs b/Chapter5/3.1.3-ShadowMapping/Window.cs
new file mode 100644
index 0000000..44bbc13
--- /dev/null
+++ b/Chapter5/3.1.3-ShadowMapping/Window.cs
@@ -0,0 +1,439 @@
+using LearnOpenTK.Common;
+using OpenTK.Graphics.OpenGL4;
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.GraphicsLibraryFramework;
+using OpenTK.Windowing.Desktop;
+using System.IO;
+using System;
+
+namespace LearnOpenTK
+{
+ // In this tutorial we set up some basic lighting and look at how the phong model works
+ // For more insight into how it all works look at the web version. If you are just here for the source,
+ // most of the changes are in the shaders, specifically most of the changes are in the fragment shader as this is
+ // where the lighting calculations happens.
+ public class Window : GameWindow
+ {
+ const int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
+ // settings
+ float[] quadVertices = {
+ // positions // texture Coords
+ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
+ };
+
+
+ // set up vertex data (and buffer(s)) and configure vertex attributes
+ // ------------------------------------------------------------------
+ float[] planeVertices = {
+ // positions // normals // texcoords
+ 25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
+ -25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ -25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f,
+
+ 25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
+ -25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f,
+ 25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 25.0f
+ };
+ // renderCube() renders a 1x1 3D cube in NDC.
+ // -------------------------------------------------
+ int cubeVAO = 0;
+ int cubeVBO = 0;
+ private Texture woodTexture;
+ private int depthMapFBO;
+ private int depthMap;
+ // timing
+ float deltaTime = 0.0f;
+ float lastFrame = 0.0f;
+ Matrix4 lightSpaceMatrix;
+ float prevMouseWheel;
+
+ // renderQuad() renders a 1x1 XY quad in NDC
+ // -----------------------------------------
+ int quadVAO = 0;
+ int quadVBO;
+
+ // lighting info
+ // -------------
+ private readonly Vector3 lightPos = new Vector3(-2.0f, 4.0f, -1.0f);
+
+ private int _vertexBufferObject;
+
+ private int _vaoModel;
+
+ private int _vaoLamp;
+
+ private Shader shader, simpleDepthShader, debugDepthQuad;
+ private int planeVAO;
+ private int planeVBO;
+ private Camera _camera;
+
+ private bool _firstMove = true;
+
+ private Vector2 _lastPos;
+
+ public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
+ : base(gameWindowSettings, nativeWindowSettings)
+ {
+ }
+
+ protected void InitGL()
+ {
+ // configure global opengl state
+ // -----------------------------
+ GL.Enable(EnableCap.DepthTest);
+
+ // build and compile shaders
+ // -------------------------
+ shader = new Shader("Shaders/3.1.3.shadow_mapping.vs", "Shaders/3.1.3.shadow_mapping.fs");
+
+ simpleDepthShader = new Shader("Shaders/3.1.3.shadow_mapping_depth.vs", "Shaders/3.1.3.shadow_mapping_depth.fs");
+ debugDepthQuad = new Shader("Shaders/3.1.3.debug_quad.vs", "Shaders/3.1.3.debug_quad_depth.fs");
+
+ // plane VAO
+ planeVAO = GL.GenVertexArray();
+ planeVBO = GL.GenBuffer();
+ GL.BindVertexArray(planeVAO);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, planeVBO);
+ GL.BufferData(BufferTarget.ArrayBuffer, planeVertices.Length * sizeof(float), planeVertices, BufferUsageHint.StaticDraw);
+ GL.EnableVertexAttribArray(0);
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
+ GL.EnableVertexAttribArray(1);
+ GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), (3 * sizeof(float)));
+ GL.EnableVertexAttribArray(2);
+ GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), (6 * sizeof(float)));
+ GL.BindVertexArray(0);
+
+ // load textures
+ // -------------
+ woodTexture = Texture.LoadFromFile("Resources/wood.png");
+ woodTexture.Use(TextureUnit.Texture0);
+
+ // configure depth map FBO
+ // -----------------------
+ depthMapFBO = GL.GenFramebuffer();
+ // create depth texture
+ depthMap = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2D, depthMap);
+ GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent, SHADOW_WIDTH, SHADOW_HEIGHT, 0, PixelFormat.DepthComponent, PixelType.Float, IntPtr.Zero);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder);
+ var borderColor = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, borderColor);
+
+ // attach depth texture as FBO's depth buffer
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, depthMapFBO);
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, TextureTarget.Texture2D, depthMap, 0);
+ GL.DrawBuffer(DrawBufferMode.None);
+ GL.ReadBuffer(ReadBufferMode.None);
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+
+
+ // shader configuration
+ // --------------------
+ shader.Use();
+ shader.SetInt("diffuseTexture", 0);
+ shader.SetInt("shadowMap", 1);
+ debugDepthQuad.Use();
+ debugDepthQuad.SetInt("depthMap", 0);
+
+ }
+ protected override void OnLoad()
+ {
+ base.OnLoad();
+
+ InitGL();
+
+ _camera = new Camera(new Vector3(0.0f, 0.0f, 3.0f), Size.X / (float)Size.Y);
+ //WindowState = WindowState.Maximized;
+ }
+
+ protected override void OnRenderFrame(FrameEventArgs e)
+ {
+ base.OnRenderFrame(e);
+
+ // per-frame time logic
+ // --------------------
+ //deltaTime = currentFrame - lastFrame;
+ //lastFrame = currentFrame;
+
+
+ // change light position over time
+ //lightPos.x = sin(glfwGetTime()) * 3.0f;
+ //lightPos.z = cos(glfwGetTime()) * 2.0f;
+ //lightPos.y = 5.0 + cos(glfwGetTime()) * 1.0f;
+
+ // render
+ // ------
+ GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+ // 1. render depth of scene to texture (from light's perspective)
+ // --------------------------------------------------------------
+ Matrix4 lightProjection, lightView;
+ float near_plane = 1.0f, far_plane = 7.5f;
+ //lightProjection = glm::perspective(glm::radians(45.0f), (GLfloat)SHADOW_WIDTH / (GLfloat)SHADOW_HEIGHT, near_plane, far_plane); // note that if you use a perspective projection matrix you'll have to change the light position as the current light position isn't enough to reflect the whole scene
+ lightProjection = Matrix4.CreateOrthographicOffCenter(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
+ lightView = Matrix4.LookAt(lightPos, new Vector3(0.0f), new Vector3(0.0f, 1.0f, 0.0f));
+ //lightSpaceMatrix = lightProjection * lightView;
+ lightSpaceMatrix = lightView * lightProjection;
+ // render scene from light's point of view
+ simpleDepthShader.Use();
+ simpleDepthShader.SetMatrix4("lightSpaceMatrix", lightSpaceMatrix);
+
+ GL.Viewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, depthMapFBO);
+ GL.Clear(ClearBufferMask.DepthBufferBit);
+ //glActiveTexture(GL_TEXTURE0);
+ //glBindTexture(GL_TEXTURE_2D, woodTexture);
+ renderScene(simpleDepthShader);
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+
+ // reset viewport
+ GL.Viewport(0, 0, Size.X, Size.Y);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+ // 2. render scene as normal using the generated depth/shadow map
+ // --------------------------------------------------------------
+ shader.Use();
+ Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(_camera.Fov), Size.X / (float)Size.Y, 0.1f, 100.0f);
+ Matrix4 view = _camera.GetViewMatrix();
+ shader.SetMatrix4("projection", projection);
+ shader.SetMatrix4("view", view);
+ // set light uniforms
+ shader.SetVector3("viewPos", _camera.Position);
+ shader.SetVector3("lightPos", lightPos);
+ shader.SetMatrix4("lightSpaceMatrix", lightSpaceMatrix);
+ woodTexture.Use(TextureUnit.Texture0);
+ GL.ActiveTexture(TextureUnit.Texture1);
+ GL.BindTexture(TextureTarget.Texture2D, depthMap);
+ renderScene(shader);
+
+ // render Depth map to quad for visual debugging
+ // ---------------------------------------------
+ debugDepthQuad.Use();
+ //debugDepthQuad.SetFloat("near_plane", near_plane);
+ //debugDepthQuad.SetFloat("far_plane", far_plane);
+ GL.ActiveTexture(TextureUnit.Texture0);
+ GL.BindTexture(TextureTarget.Texture2D, depthMap);
+ //renderQuad();
+
+ // GL.fw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
+ // -------------------------------------------------------------------------------
+ SwapBuffers();
+ }
+
+
+ void renderCube()
+ {
+ // initialize (if necessary)
+ if (cubeVAO == 0)
+ {
+ float[] vertices = {
+ // back face
+ -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
+ 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
+ 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right
+ 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
+ -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
+ -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left
+ // front face
+ -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
+ 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right
+ 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
+ 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
+ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left
+ -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
+ // left face
+ -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
+ -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
+ -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
+ -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
+ -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right
+ -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
+ // right face
+ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
+ 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
+ 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right
+ 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
+ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
+ 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left
+ // bottom face
+ -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
+ 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
+ 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
+ 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
+ -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
+ -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
+ // top face
+ -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left
+ 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
+ 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right
+ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
+ -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left
+ -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left
+ };
+ cubeVAO = GL.GenVertexArray();
+ cubeVBO = GL.GenBuffer();
+ // fill buffer
+ GL.BindBuffer(BufferTarget.ArrayBuffer, cubeVBO);
+ GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
+ // link vertex attributes
+ GL.BindVertexArray(cubeVAO);
+ GL.EnableVertexAttribArray(0);
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
+ GL.EnableVertexAttribArray(1);
+ GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), (3 * sizeof(float)));
+ GL.EnableVertexAttribArray(2);
+ GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), (6 * sizeof(float)));
+ GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
+ GL.BindVertexArray(0);
+ }
+ // render Cube
+ GL.BindVertexArray(cubeVAO);
+ GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
+ GL.BindVertexArray(0);
+ }
+
+ void renderQuad()
+ {
+ if (quadVAO == 0)
+ {
+ // setup plane VAO
+ quadVAO = GL.GenVertexArray();
+ quadVBO = GL.GenBuffer();
+ GL.BindVertexArray(quadVAO);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, quadVBO);
+ GL.BufferData(BufferTarget.ArrayBuffer, quadVertices.Length * sizeof(float), quadVertices, BufferUsageHint.StaticDraw);
+ GL.EnableVertexAttribArray(0);
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
+ GL.EnableVertexAttribArray(1);
+ GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), (3 * sizeof(float)));
+ GL.EnableVertexAttribArray(2);
+ }
+ GL.BindVertexArray(quadVAO);
+ GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
+ GL.BindVertexArray(0);
+ }
+ // renders the 3D scene
+ // --------------------
+ void renderScene(Shader shader)
+ {
+ // floor
+ Matrix4 model = Matrix4.Identity;
+ shader.SetMatrix4("model", model);
+ GL.BindVertexArray(planeVAO);
+ GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
+ // cubes
+ model = Matrix4.Identity;
+ model = model * Matrix4.CreateScale(new Vector3(0.5f));
+ model = model * Matrix4.CreateTranslation(0.0f, 1.5f, 0.0f);
+ shader.SetMatrix4("model", model);
+ renderCube();
+
+ model = Matrix4.Identity;
+ model = model * Matrix4.CreateScale(new Vector3(0.5f));
+ model = model * Matrix4.CreateTranslation(2.0f, 0.0f, 1.0f);
+ shader.SetMatrix4("model", model);
+ renderCube();
+
+ model = Matrix4.Identity;
+ model = model * Matrix4.CreateFromAxisAngle(new Vector3(1f, 0, 1f), MathHelper.DegreesToRadians(60.0f));
+ model = model * Matrix4.CreateScale(new Vector3(0.25f));
+ model = model * Matrix4.CreateTranslation(-1.0f, 0.0f, 2.0f);
+ shader.SetMatrix4("model", model);
+ renderCube();
+ }
+
+ protected override void OnUpdateFrame(FrameEventArgs e)
+ {
+ base.OnUpdateFrame(e);
+
+ if (!IsFocused)
+ {
+ return;
+ }
+
+ var input = KeyboardState;
+
+ if (input.IsKeyDown(Keys.Escape))
+ {
+ Close();
+ }
+
+ const float cameraSpeed = 1.5f;
+ const float sensitivity = 0.2f;
+
+ if (input.IsKeyDown(Keys.W))
+ {
+ _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
+ }
+ if (input.IsKeyDown(Keys.S))
+ {
+ _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
+ }
+ if (input.IsKeyDown(Keys.A))
+ {
+ _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
+ }
+ if (input.IsKeyDown(Keys.D))
+ {
+ _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
+ }
+ if (input.IsKeyDown(Keys.Space))
+ {
+ _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
+ }
+ if (input.IsKeyDown(Keys.LeftShift))
+ {
+ _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
+ }
+
+ var mouse = MouseState;
+
+ if (_firstMove)
+ {
+ _lastPos = new Vector2(mouse.X, mouse.Y);
+ _firstMove = false;
+ }
+ else
+ {
+ var deltaX = mouse.X - _lastPos.X;
+ var deltaY = mouse.Y - _lastPos.Y;
+ _lastPos = new Vector2(mouse.X, mouse.Y);
+
+ _camera.Yaw += deltaX * sensitivity;
+ _camera.Pitch -= deltaY * sensitivity;
+ }
+ }
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ base.OnMouseWheel(e);
+
+
+
+ if (prevMouseWheel - e.OffsetY < 0)
+ _camera.Fov --;
+ else if (prevMouseWheel - e.OffsetY > 0)
+ _camera.Fov ++;
+
+ prevMouseWheel = e.OffsetY;
+ }
+
+ protected override void OnResize(ResizeEventArgs e)
+ {
+ base.OnResize(e);
+
+ GL.Viewport(0, 0, Size.X, Size.Y);
+ if (_camera != null)
+ _camera.AspectRatio = Size.X / (float)Size.Y;
+ }
+ }
+}
+
diff --git a/Chapter6/1-PBR-Lighting/1-PBR-Lighting.csproj b/Chapter6/1-PBR-Lighting/1-PBR-Lighting.csproj
new file mode 100644
index 0000000..bafb585
--- /dev/null
+++ b/Chapter6/1-PBR-Lighting/1-PBR-Lighting.csproj
@@ -0,0 +1,24 @@
+
+
+ WinExe
+ LearnOpenTK
+ LearnOpenTK
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Chapter6/1-PBR-Lighting/OpenTK.dll.config b/Chapter6/1-PBR-Lighting/OpenTK.dll.config
new file mode 100644
index 0000000..7098d39
--- /dev/null
+++ b/Chapter6/1-PBR-Lighting/OpenTK.dll.config
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter6/1-PBR-Lighting/Program.cs b/Chapter6/1-PBR-Lighting/Program.cs
new file mode 100644
index 0000000..d1c61fe
--- /dev/null
+++ b/Chapter6/1-PBR-Lighting/Program.cs
@@ -0,0 +1,25 @@
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.Desktop;
+
+namespace LearnOpenTK
+{
+ public static class Program
+ {
+ private static void Main()
+ {
+ var nativeWindowSettings = new NativeWindowSettings()
+ {
+ Size = new Vector2i(800, 600),
+ Title = "LearnOpenTK - Basic lighting",
+ // This is needed to run on macos
+ Flags = ContextFlags.ForwardCompatible,
+ };
+
+ using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
+ {
+ window.Run();
+ }
+ }
+ }
+}
diff --git a/Chapter6/1-PBR-Lighting/Shaders/pbr.fs b/Chapter6/1-PBR-Lighting/Shaders/pbr.fs
new file mode 100644
index 0000000..5333e96
--- /dev/null
+++ b/Chapter6/1-PBR-Lighting/Shaders/pbr.fs
@@ -0,0 +1,121 @@
+#version 330 core
+out vec4 FragColor;
+in vec2 TexCoords;
+in vec3 WorldPos;
+in vec3 Normal;
+
+// material parameters
+uniform vec3 albedo;
+uniform float metallic;
+uniform float roughness;
+uniform float ao;
+
+// lights
+uniform vec3 lightPositions[4];
+uniform vec3 lightColors[4];
+
+uniform vec3 camPos;
+
+const float PI = 3.14159265359;
+// ----------------------------------------------------------------------------
+float DistributionGGX(vec3 N, vec3 H, float roughness)
+{
+ float a = roughness*roughness;
+ float a2 = a*a;
+ float NdotH = max(dot(N, H), 0.0);
+ float NdotH2 = NdotH*NdotH;
+
+ float nom = a2;
+ float denom = (NdotH2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+
+ return nom / denom;
+}
+// ----------------------------------------------------------------------------
+float GeometrySchlickGGX(float NdotV, float roughness)
+{
+ float r = (roughness + 1.0);
+ float k = (r*r) / 8.0;
+
+ float nom = NdotV;
+ float denom = NdotV * (1.0 - k) + k;
+
+ return nom / denom;
+}
+// ----------------------------------------------------------------------------
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
+{
+ float NdotV = max(dot(N, V), 0.0);
+ float NdotL = max(dot(N, L), 0.0);
+ float ggx2 = GeometrySchlickGGX(NdotV, roughness);
+ float ggx1 = GeometrySchlickGGX(NdotL, roughness);
+
+ return ggx1 * ggx2;
+}
+// ----------------------------------------------------------------------------
+vec3 fresnelSchlick(float cosTheta, vec3 F0)
+{
+ return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
+}
+// ----------------------------------------------------------------------------
+void main()
+{
+ vec3 N = normalize(Normal);
+ vec3 V = normalize(camPos - WorldPos);
+
+ // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0
+ // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)
+ vec3 F0 = vec3(0.04);
+ F0 = mix(F0, albedo, metallic);
+
+ // reflectance equation
+ vec3 Lo = vec3(0.0);
+ for(int i = 0; i < 4; ++i)
+ {
+ // calculate per-light radiance
+ vec3 L = normalize(lightPositions[i] - WorldPos);
+ vec3 H = normalize(V + L);
+ float distance = length(lightPositions[i] - WorldPos);
+ float attenuation = 1.0 / (distance * distance);
+ vec3 radiance = lightColors[i] * attenuation;
+
+ // Cook-Torrance BRDF
+ float NDF = DistributionGGX(N, H, roughness);
+ float G = GeometrySmith(N, V, L, roughness);
+ vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
+
+ vec3 numerator = NDF * G * F;
+ float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zero
+ vec3 specular = numerator / denominator;
+
+ // kS is equal to Fresnel
+ vec3 kS = F;
+ // for energy conservation, the diffuse and specular light can't
+ // be above 1.0 (unless the surface emits light); to preserve this
+ // relationship the diffuse component (kD) should equal 1.0 - kS.
+ vec3 kD = vec3(1.0) - kS;
+ // multiply kD by the inverse metalness such that only non-metals
+ // have diffuse lighting, or a linear blend if partly metal (pure metals
+ // have no diffuse light).
+ kD *= 1.0 - metallic;
+
+ // scale light by NdotL
+ float NdotL = max(dot(N, L), 0.0);
+
+ // add to outgoing radiance Lo
+ Lo += (kD * albedo / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again
+ }
+
+ // ambient lighting (note that the next IBL tutorial will replace
+ // this ambient lighting with environment lighting).
+ vec3 ambient = vec3(0.03) * albedo * ao;
+
+ vec3 color = ambient + Lo;
+
+ // HDR tonemapping
+ color = color / (color + vec3(1.0));
+ // gamma correct
+ color = pow(color, vec3(1.0/2.2));
+
+ FragColor = vec4(color, 1.0);
+}
diff --git a/Chapter6/1-PBR-Lighting/Shaders/pbr.vs b/Chapter6/1-PBR-Lighting/Shaders/pbr.vs
new file mode 100644
index 0000000..60f7ff7
--- /dev/null
+++ b/Chapter6/1-PBR-Lighting/Shaders/pbr.vs
@@ -0,0 +1,22 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec3 aNormal;
+layout (location = 2) in vec2 aTexCoords;
+
+out vec2 TexCoords;
+out vec3 WorldPos;
+out vec3 Normal;
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform mat4 model;
+uniform mat3 normalMatrix;
+
+void main()
+{
+ TexCoords = aTexCoords;
+ WorldPos = vec3(vec4(aPos, 1.0) * model);
+ Normal = aNormal * transpose(inverse(mat3(model)));
+
+ gl_Position = vec4(WorldPos, 1.0) * view * projection;
+}
\ No newline at end of file
diff --git a/Chapter6/1-PBR-Lighting/Window.cs b/Chapter6/1-PBR-Lighting/Window.cs
new file mode 100644
index 0000000..7e1c5e9
--- /dev/null
+++ b/Chapter6/1-PBR-Lighting/Window.cs
@@ -0,0 +1,438 @@
+using LearnOpenTK.Common;
+using OpenTK.Graphics.OpenGL4;
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.GraphicsLibraryFramework;
+using OpenTK.Windowing.Desktop;
+using System.IO;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace LearnOpenTK
+{
+
+
+ // In this tutorial we set up some basic lighting and look at how the phong model works
+ // For more insight into how it all works look at the web version. If you are just here for the source,
+ // most of the changes are in the shaders, specifically most of the changes are in the fragment shader as this is
+ // where the lighting calculations happens.
+ public class Window : GameWindow
+ {
+ // settings
+ const uint SCR_WIDTH = 1280;
+ const uint SCR_HEIGHT = 720;
+
+ // camera
+ private Camera _camera;
+
+ float lastX = 800.0f / 2.0f;
+ float lastY = 600.0f / 2.0f;
+ bool firstMouse = true;
+
+ // timing
+ float deltaTime = 0.0f;
+ float lastFrame = 0.0f;
+ private Stopwatch _timer;
+
+ int sphereVAO = 0;
+ int indexCount;
+ int nrRows = 7;
+ int nrColumns = 7;
+ float spacing = 2.5f;
+ const int X_SEGMENTS = 64;
+ const int Y_SEGMENTS = 64;
+
+ Vector3 albedoVec = new Vector3(0.5f, 0.0f, 0.0f);
+
+ Matrix4 lightSpaceMatrix;
+ float prevMouseWheel;
+
+ // renderQuad() renders a 1x1 XY quad in NDC
+ // -----------------------------------------
+ int quadVAO = 0;
+ int quadVBO;
+
+ // lighting info
+ // -------------
+ // lights
+ // ------
+ Vector3[] lightPositions = {
+ new Vector3(-10.0f, 10.0f, 10.0f),
+ new Vector3( 10.0f, 10.0f, 10.0f),
+ new Vector3(-10.0f, -10.0f, 10.0f),
+ new Vector3( 10.0f, -10.0f, 10.0f),
+ };
+ Vector3[] lightColors = {
+ new Vector3(300.0f, 300.0f, 300.0f),
+ new Vector3(300.0f, 300.0f, 300.0f),
+ new Vector3(300.0f, 300.0f, 300.0f),
+ new Vector3(300.0f, 300.0f, 300.0f)
+ };
+
+
+
+
+
+ private readonly Vector3 lightPos = new Vector3(-2.0f, 4.0f, -1.0f);
+
+ private int _vertexBufferObject;
+
+ private int _vaoModel;
+
+ private int _vaoLamp;
+
+ private Shader shader, simpleDepthShader, debugDepthQuad;
+ private int planeVAO;
+ private int planeVBO;
+
+ private bool _firstMove = true;
+
+ private Vector2 _lastPos;
+
+ public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
+ : base(gameWindowSettings, nativeWindowSettings)
+ {
+ }
+
+ protected void InitGL()
+ {
+ // configure GL.obal openGL. state
+ // -----------------------------
+ GL.Enable(EnableCap.DepthTest);
+
+ // initialize static shader uniforms before rendering
+ // --------------------------------------------------
+ Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(_camera.Fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
+
+ // build and compile shaders
+ // -------------------------
+ shader = new Shader("Shaders/pbr.vs", "Shaders/pbr.fs");
+ shader.Use();
+ shader.SetMatrix4("projection", projection);
+ }
+ protected override void OnLoad()
+ {
+ base.OnLoad();
+
+
+ // We start the stopwatch here as this method is only called once.
+ _timer = new Stopwatch();
+ _timer.Start();
+
+ _camera = new Camera(new Vector3(0.0f, 0.0f, 3.0f), (float)Size.X / (float)Size.Y);
+ InitGL();
+ }
+
+ protected override void OnRenderFrame(FrameEventArgs e)
+ {
+ base.OnRenderFrame(e);
+
+ shader.Use();
+ shader.SetVector3("albedo", albedoVec);
+ shader.SetFloat("ao", 1.0f);
+
+ // per-frame time logic
+ // --------------------
+ //float currentFrame = static_cast(GL.fwGetTime());
+ //deltaTime = currentFrame - lastFrame;
+ //lastFrame = currentFrame;
+
+ // render
+ // ------
+ GL.Viewport(0, 0, Size.X, Size.Y);
+ GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+
+ shader.Use();
+ Matrix4 view = _camera.GetViewMatrix();
+ shader.SetMatrix4("view", view);
+ shader.SetVector3("camPos", _camera.Position);
+
+ // render rows*column number of spheres with varying metallic/roughness values scaled by rows and columns respectively
+ Matrix4 model = Matrix4.Identity;
+ for (int row = 0; row < nrRows; ++row)
+ {
+ shader.SetFloat("metallic", (float)row / (float)nrRows);
+ for (int col = 0; col < nrColumns; ++col)
+ {
+ // we clamp the roughness to 0.05 - 1.0 as perfectly smooth surfaces (roughness of 0.0) tend to look a bit off
+ // on direct lighting.
+ shader.SetFloat("roughness", Math.Clamp((float)col / (float)nrColumns, 0.05f, 1.0f));
+
+ model = Matrix4.Identity;
+ model = model * Matrix4.CreateTranslation(new Vector3(
+ (col - (nrColumns / 2)) * spacing,
+ (row - (nrRows / 2)) * spacing,
+ 0.0f
+ ));
+
+ shader.SetMatrix4("model", model);
+ //shader.SetMatrix3("normalMatrix", Matrix3.Transpose(Matrix3.Invert(new Matrix3(model))));
+ renderSphere();
+ }
+ }
+
+ // render light source (simply re-render sphere at light positions)
+ // this looks a bit off as we use the same shader, but it'll make their positions obvious and
+ // keeps the codeprint small.
+ for (uint i = 0; i < lightPositions.Length; ++i)
+ {
+ Vector3 newPos = lightPositions[i] + new Vector3((float)Math.Sin(GLFW.GetTime() * 5.0) * 5.0f, 0.0f, 0.0f);
+ newPos = lightPositions[i];
+ shader.SetVector3("lightPositions[" + i + "]", newPos);
+ shader.SetVector3("lightColors[" + i + "]", lightColors[i]);
+
+
+ model = Matrix4.Identity;
+ model = model * Matrix4.CreateScale(new Vector3(0.5f));
+ model = model * Matrix4.CreateTranslation(newPos);
+
+ shader.SetMatrix4("model", model);
+ //shader.SetMatrix3("normalMatrix", Matrix3.Transpose(Matrix3.Invert(new Matrix3(model))));
+ renderSphere();
+ }
+
+ // GL.fw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
+ // -------------------------------------------------------------------------------
+ SwapBuffers();
+ }
+
+ protected override void OnUpdateFrame(FrameEventArgs e)
+ {
+ base.OnUpdateFrame(e);
+
+ if (!IsFocused)
+ {
+ return;
+ }
+
+ var input = KeyboardState;
+
+ if (input.IsKeyDown(Keys.Escape))
+ {
+ Close();
+ }
+
+ const float cameraSpeed = 1.5f;
+ const float sensitivity = 0.2f;
+
+ if (input.IsKeyDown(Keys.W))
+ {
+ _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
+ }
+ if (input.IsKeyDown(Keys.S))
+ {
+ _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
+ }
+ if (input.IsKeyDown(Keys.A))
+ {
+ _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
+ }
+ if (input.IsKeyDown(Keys.D))
+ {
+ _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
+ }
+ if (input.IsKeyDown(Keys.Space))
+ {
+ _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
+ }
+ if (input.IsKeyDown(Keys.LeftShift))
+ {
+ _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
+ }
+
+ var mouse = MouseState;
+
+ if (_firstMove)
+ {
+ _lastPos = new Vector2(mouse.X, mouse.Y);
+ _firstMove = false;
+ }
+ else
+ {
+ var deltaX = mouse.X - _lastPos.X;
+ var deltaY = mouse.Y - _lastPos.Y;
+ _lastPos = new Vector2(mouse.X, mouse.Y);
+
+ _camera.Yaw += deltaX * sensitivity;
+ _camera.Pitch -= deltaY * sensitivity;
+ }
+ }
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ base.OnMouseWheel(e);
+
+ if (prevMouseWheel - e.OffsetY < 0)
+ _camera.Fov --;
+ else if (prevMouseWheel - e.OffsetY > 0)
+ _camera.Fov ++;
+
+ prevMouseWheel = e.OffsetY;
+ }
+
+ protected override void OnResize(ResizeEventArgs e)
+ {
+ base.OnResize(e);
+
+ GL.Viewport(0, 0, Size.X, Size.Y);
+ if (_camera != null)
+ _camera.AspectRatio = Size.X / (float)Size.Y;
+ }
+
+ // renders (and builds at first invocation) a sphere
+ // -------------------------------------------------
+ void renderSphere()
+ {
+ if (sphereVAO == 0)
+ {
+ sphereVAO = GL.GenVertexArray();
+
+ int vbo, ebo;
+ vbo = GL.GenBuffer();
+ ebo = GL.GenBuffer();
+
+ List positions = new();
+ List uv = new();
+ List normals = new();
+ List< uint> indices = new();
+
+ for (uint x = 0; x <= X_SEGMENTS; ++x)
+ {
+ for (uint y = 0; y <= Y_SEGMENTS; ++y)
+ {
+ float xSegment = (float)x / (float)X_SEGMENTS;
+ float ySegment = (float)y / (float)Y_SEGMENTS;
+ float xPos = (float)(Math.Cos(xSegment * 2.0f * Math.PI) * Math.Sin(ySegment *Math.PI));
+ float yPos = (float)Math.Cos(ySegment *Math.PI);
+ float zPos = (float)(Math.Sin(xSegment * 2.0f *Math.PI) * Math.Sin(ySegment *Math.PI));
+
+ positions.Add(new Vector3(xPos, yPos, zPos));
+ uv.Add(new Vector2(xSegment, ySegment));
+ normals.Add(new Vector3(xPos, yPos, zPos));
+ }
+ }
+
+ bool oddRow = false;
+ for (uint y = 0; y < Y_SEGMENTS; ++y)
+ {
+ if (!oddRow) // even rows: y == 0, y == 2; and so on
+ {
+ for (int x = 0; x <= X_SEGMENTS; ++x)
+ {
+ indices.Add((uint)(y * (X_SEGMENTS + 1) + x));
+ indices.Add((uint)((y + 1) * (X_SEGMENTS + 1) + x));
+ }
+ }
+ else
+ {
+ for (int x = X_SEGMENTS; x >= 0; --x)
+ {
+ indices.Add((uint)((y + 1) * (X_SEGMENTS + 1) + x));
+ indices.Add((uint)(y * (X_SEGMENTS + 1) + x));
+ }
+ }
+ oddRow = !oddRow;
+ }
+ indexCount = indices.Count;
+
+ List data = new();
+ for (int i = 0; i < positions.Count; ++i)
+ {
+ data.Add(positions[i].X);
+ data.Add(positions[i].Y);
+ data.Add(positions[i].Z);
+ if (normals.Count > 0)
+ {
+ data.Add(normals[i].X);
+ data.Add(normals[i].Y);
+ data.Add(normals[i].Z);
+ }
+ if (uv.Count > 0)
+ {
+ data.Add(uv[i].X);
+ data.Add(uv[i].Y);
+ }
+ }
+ GL.BindVertexArray(sphereVAO);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
+ GL.BufferData(BufferTarget.ArrayBuffer, data.Count * sizeof(float), data.ToArray(), BufferUsageHint.StaticDraw);
+ GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
+ GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Count * sizeof(uint), indices.ToArray(), BufferUsageHint.StaticDraw);
+ int stride = (3 + 2 + 3) * sizeof(float);
+ GL.EnableVertexAttribArray(0);
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
+ GL.EnableVertexAttribArray(1);
+ GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
+ GL.EnableVertexAttribArray(2);
+ GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, 6 * sizeof(float));
+ }
+
+ GL.BindVertexArray(sphereVAO);
+ GL.DrawElements(PrimitiveType.TriangleStrip, indexCount, DrawElementsType.UnsignedInt, 0);
+ }
+ }
+}
+/*
+
+
+int main()
+{
+ // GL.fw: initialize and configure
+ // ------------------------------
+ GL.fwInit();
+ GL.fwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ GL.fwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+ GL.fwWindowHint(GLFW_SAMPLES, 4);
+ GL.fwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+
+ GL.fwSetFramebufferSizeCallback(window, framebuffer_size_callback);
+ GL.fwSetCursorPosCallback(window, mouse_callback);
+ GL.fwSetScrollCallback(window, scroll_callback);
+
+ // tell GLFW to capture our mouse
+ GL.fwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
+
+ // build and compile shaders
+ // -------------------------
+ Shader shader("1.1.pbr.vs", "1.1.pbr.fs");
+
+
+
+ // initialize static shader uniforms before rendering
+ // --------------------------------------------------
+ Matrix4 projection = GL.m::perspective(GL.m::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
+ shader.use();
+
+ // render loop
+ // -----------
+ while (!GL.fwWindowShouldClose(window))
+ {
+
+ }
+
+ // GL.fw: terminate, clearing all previously allocated GLFW resources.
+ // ------------------------------------------------------------------
+ GL.fwTerminate();
+ return 0;
+}
+
+
+// GL.fw: whenever the window size changed (by OS or user resize) this callback function executes
+// ---------------------------------------------------------------------------------------------
+void framebuffer_size_callback(GLFWwindow* window, int width, int height)
+{
+ // make sure the viewport matches the new window dimensions; note that width and
+ // height will be significantly larger than specified on retina displays.
+ GL.Viewport(0, 0, width, height);
+}
+
+
+
+
+
+
+*/
\ No newline at end of file
diff --git a/Common/Shader.cs b/Common/Shader.cs
index b1e6b8b..3e271d4 100644
--- a/Common/Shader.cs
+++ b/Common/Shader.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
+using OpenTK.Windowing.GraphicsLibraryFramework;
namespace LearnOpenTK.Common
{
@@ -69,6 +70,7 @@ public Shader(string vertPath, string fragPath)
// First, we have to get the number of active uniforms in the shader.
GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms);
+
// Next, allocate the dictionary to hold the locations.
_uniformLocations = new Dictionary();
@@ -76,13 +78,29 @@ public Shader(string vertPath, string fragPath)
for (var i = 0; i < numberOfUniforms; i++)
{
// get the name of this uniform,
- var key = GL.GetActiveUniform(Handle, i, out _, out _);
-
- // get the location,
- var location = GL.GetUniformLocation(Handle, key);
-
- // and then add it to the dictionary.
- _uniformLocations.Add(key, location);
+ var key = GL.GetActiveUniform(Handle, i, out int size, out ActiveUniformType type);
+
+ if (key.EndsWith("[0]"))
+ {
+ var prefix = key.Substring(0, key.IndexOf('['));
+ for (int k = 0; k < size; k++)
+ {
+ var newKey = prefix + "[" + k + "]";
+ // get the location,
+ var location = GL.GetUniformLocation(Handle, newKey);
+
+ // and then add it to the dictionary.
+ _uniformLocations.Add(newKey, location);
+ }
+ }
+ else
+ {
+ // get the location,
+ var location = GL.GetUniformLocation(Handle, key);
+
+ // and then add it to the dictionary.
+ _uniformLocations.Add(key, location);
+ }
}
}
@@ -175,6 +193,22 @@ public void SetMatrix4(string name, Matrix4 data)
GL.UniformMatrix4(_uniformLocations[name], true, ref data);
}
+ ///
+ /// Set a uniform Matrix4 on this shader
+ ///
+ /// The name of the uniform
+ /// The data to set
+ ///
+ ///
+ /// The matrix is transposed before being sent to the shader.
+ ///
+ ///
+ public void SetMatrix3(string name, Matrix3 data)
+ {
+ GL.UseProgram(Handle);
+ GL.UniformMatrix3(_uniformLocations[name], true, ref data);
+ }
+
///
/// Set a uniform Vector3 on this shader.
///
@@ -183,6 +217,7 @@ public void SetMatrix4(string name, Matrix4 data)
public void SetVector3(string name, Vector3 data)
{
GL.UseProgram(Handle);
+ //var location = GL.GetUniformLocation(Handle, name);
GL.Uniform3(_uniformLocations[name], data);
}
}
diff --git a/LearnOpenTK.sln b/LearnOpenTK.sln
index c22f114..d0c0b43 100644
--- a/LearnOpenTK.sln
+++ b/LearnOpenTK.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30309.148
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34511.84
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1-CreatingAWindow", "Chapter1\1-CreatingAWindow\1-CreatingAWindow.csproj", "{39A0FE24-A920-4C13-9BB9-18483FECD55D}"
EndProject
@@ -41,11 +41,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "5-LightCasters-PointLights"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "5-LightCasters-Spotlight", "Chapter2\5-LightCasters-Spotlight\5-LightCasters-Spotlight.csproj", "{2DD36D08-FBC6-40E1-8EB9-3C94ABE6985E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4-Shaders-InsAndOuts", "Chapter1\4-Shaders-InsAndOuts\4-Shaders-InsAndOuts.csproj", "{1234CBB6-22C1-42D0-828B-1D1E7085EF33}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "4-Shaders-InsAndOuts", "Chapter1\4-Shaders-InsAndOuts\4-Shaders-InsAndOuts.csproj", "{1234CBB6-22C1-42D0-828B-1D1E7085EF33}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4-Shaders-Uniforms", "Chapter1\4-Shaders-Uniforms\4-Shaders-Uniforms.csproj", "{E8DA696D-F948-4967-BF75-1570A2957C3B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "4-Shaders-Uniforms", "Chapter1\4-Shaders-Uniforms\4-Shaders-Uniforms.csproj", "{E8DA696D-F948-4967-BF75-1570A2957C3B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4-Shaders-MoreAttributes", "Chapter1\4-Shaders-MoreAttributes\4-Shaders-MoreAttributes.csproj", "{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "4-Shaders-MoreAttributes", "Chapter1\4-Shaders-MoreAttributes\4-Shaders-MoreAttributes.csproj", "{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "3-1-AdvancedLighting-ShadowMapping", "Chapter5\3.1.3-ShadowMapping\3-1-AdvancedLighting-ShadowMapping.csproj", "{951CDA27-F60E-46E4-AF74-9ED130B0B1DC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chapter5", "Chapter5", "{BBA7AD1A-E744-4520-814B-2EEEC4CA6491}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1-PBR-Lighting", "Chapter6\1-PBR-Lighting\1-PBR-Lighting.csproj", "{633E14EA-5A93-4FD0-B2BF-A820A852CE6D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chapter6", "Chapter6", "{EF8CEE04-BE7B-4F02-9266-8ABD9B9ACD76}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chapter4", "Chapter4", "{6C07F1A6-9ACB-46CA-83F1-4E988ECC161B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "4-1-FrameBuffers", "Chapter4\4.1-Framebuffers\4-1-FrameBuffers.csproj", "{51C574E2-211C-4B94-9085-1857BEC02F7D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -133,6 +145,18 @@ Global
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}.Release|Any CPU.Build.0 = Release|Any CPU
+ {951CDA27-F60E-46E4-AF74-9ED130B0B1DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {951CDA27-F60E-46E4-AF74-9ED130B0B1DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {951CDA27-F60E-46E4-AF74-9ED130B0B1DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {951CDA27-F60E-46E4-AF74-9ED130B0B1DC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {633E14EA-5A93-4FD0-B2BF-A820A852CE6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {633E14EA-5A93-4FD0-B2BF-A820A852CE6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {633E14EA-5A93-4FD0-B2BF-A820A852CE6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {633E14EA-5A93-4FD0-B2BF-A820A852CE6D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {51C574E2-211C-4B94-9085-1857BEC02F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {51C574E2-211C-4B94-9085-1857BEC02F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {51C574E2-211C-4B94-9085-1857BEC02F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {51C574E2-211C-4B94-9085-1857BEC02F7D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -157,9 +181,9 @@ Global
{1234CBB6-22C1-42D0-828B-1D1E7085EF33} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{E8DA696D-F948-4967-BF75-1570A2957C3B} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {2A8747DC-91DB-4ECB-AB4A-5504F9D715D9}
+ {951CDA27-F60E-46E4-AF74-9ED130B0B1DC} = {BBA7AD1A-E744-4520-814B-2EEEC4CA6491}
+ {633E14EA-5A93-4FD0-B2BF-A820A852CE6D} = {EF8CEE04-BE7B-4F02-9266-8ABD9B9ACD76}
+ {51C574E2-211C-4B94-9085-1857BEC02F7D} = {6C07F1A6-9ACB-46CA-83F1-4E988ECC161B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2A8747DC-91DB-4ECB-AB4A-5504F9D715D9}