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}