diff --git a/Chapter1/1-CreatingAWindow/1-CreatingAWindow.csproj b/Chapter1/1-CreatingAWindow/1-CreatingAWindow.csproj index 36f3f3c..ba75629 100644 --- a/Chapter1/1-CreatingAWindow/1-CreatingAWindow.csproj +++ b/Chapter1/1-CreatingAWindow/1-CreatingAWindow.csproj @@ -3,10 +3,11 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 - + + diff --git a/Chapter1/1-CreatingAWindow/Program.cs b/Chapter1/1-CreatingAWindow/Program.cs index adc2bd3..87306af 100644 --- a/Chapter1/1-CreatingAWindow/Program.cs +++ b/Chapter1/1-CreatingAWindow/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Creating a Window", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/2-HelloTriangle/2-HelloTriangle.csproj b/Chapter1/2-HelloTriangle/2-HelloTriangle.csproj index e968f4c..56a56ae 100644 --- a/Chapter1/2-HelloTriangle/2-HelloTriangle.csproj +++ b/Chapter1/2-HelloTriangle/2-HelloTriangle.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,19 +11,7 @@ - - - - - - - - - - PreserveNewest - - - PreserveNewest - + + diff --git a/Chapter1/2-HelloTriangle/Program.cs b/Chapter1/2-HelloTriangle/Program.cs index c3e9037..db9ff3a 100644 --- a/Chapter1/2-HelloTriangle/Program.cs +++ b/Chapter1/2-HelloTriangle/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Creating a Window", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/2-HelloTriangle/Shaders/shader.frag b/Chapter1/2-HelloTriangle/Shaders/shader.frag index db749de..6d69b56 100644 --- a/Chapter1/2-HelloTriangle/Shaders/shader.frag +++ b/Chapter1/2-HelloTriangle/Shaders/shader.frag @@ -4,5 +4,6 @@ out vec4 outputColor; void main() { + // Set the pixel color to RGB (1, 1, 0), a yellow color. outputColor = vec4(1.0, 1.0, 0.0, 1.0); } \ No newline at end of file diff --git a/Chapter1/2-HelloTriangle/Shaders/shader.vert b/Chapter1/2-HelloTriangle/Shaders/shader.vert index e9e0e7b..6ebfb4e 100644 --- a/Chapter1/2-HelloTriangle/Shaders/shader.vert +++ b/Chapter1/2-HelloTriangle/Shaders/shader.vert @@ -18,14 +18,14 @@ // shader(vertex) -// This defines our input variable, aPosition. +// This defines our input variable, aPos. // It starts with the line "layout(location = 0)". This defines where this input variable will be located, which is needed for GL.VertexAttribPointer. -// However, you can omit it, and replace this with just "in vec3 aPosition". If you do that, you'll have to replace the 0 in GL.VertexAttribPointer with +// However, you can omit it, and replace this with just "in vec3 aPos". If you do that, you'll have to replace the 0 in GL.VertexAttribPointer with // a call to GL.GetAttribLocation(shaderHandle, attributeName) // Next, the keyword "in" defines this as an input variable. We'll have an example of the "out" keyword in the next tutorial. // Then, the keyword "vec3" means this is a vector with 3 floats inside. -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; // Like C, we have an entrypoint function. In this case, it takes void and returns void, and must be named main. @@ -37,5 +37,5 @@ layout(location = 0) in vec3 aPosition; void main(void) { - gl_Position = vec4(aPosition, 1.0); + gl_Position = vec4(aPos, 1.0); } \ No newline at end of file diff --git a/Chapter1/2-HelloTriangle/Window.cs b/Chapter1/2-HelloTriangle/Window.cs index 133aa2d..a5312b5 100644 --- a/Chapter1/2-HelloTriangle/Window.cs +++ b/Chapter1/2-HelloTriangle/Window.cs @@ -1,8 +1,9 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using System; +using System.IO; namespace LearnOpenTK { @@ -34,7 +35,7 @@ public class Window : GameWindow // This class is a wrapper around a shader, which helps us manage it. // The shader class's code is in the Common project. // What shaders are and what they're used for will be explained later in this tutorial. - private Shader _shader; + private int _shader; public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -112,13 +113,13 @@ protected override void OnLoad() // Modern OpenGL makes this pipeline very free, giving us a lot of freedom on how vertices are turned to pixels. // The drawback is that we actually need two more programs for this! These are called "shaders". // Shaders are tiny programs that live on the GPU. OpenGL uses them to handle the vertex-to-pixel pipeline. - // Check out the Shader class in Common to see how we create our shaders, as well as a more in-depth explanation of how shaders work. + // Check out the CompileProgram function to see how we create our shaders, as well as a more in-depth explanation of how shaders work. // shader.vert and shader.frag contain the actual shader code. - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); // Now, enable the shader. // Just like the VBO, this is global, so every function that uses a shader will modify this one until a new one is bound instead. - _shader.Use(); + GL.UseProgram(_shader); // Setup is now complete! Now we move to the OnRenderFrame function to finally draw the triangle. } @@ -140,7 +141,7 @@ protected override void OnRenderFrame(FrameEventArgs e) // and then calling an OpenGL function to render. // Bind the shader - _shader.Use(); + GL.UseProgram(_shader); // Bind the VAO GL.BindVertexArray(_vertexArrayObject); @@ -176,13 +177,13 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); // When the window gets resized, we have to call GL.Viewport to resize OpenGL's viewport to match the new size. // If we don't, the NDC will no longer be correct. - GL.Viewport(0, 0, Size.X, Size.Y); + GL.Viewport(0, 0, e.Width, e.Height); } // Now, for cleanup. @@ -197,7 +198,7 @@ protected override void OnResize(ResizeEventArgs e) // longer used for whatever reason (e.g. a new scene is loaded that doesn't use a texture). // This would free up video ram (VRAM) that can be used for new textures. // - // The coming chapters will not have this code. + // The coming chapters will not have this cleanup code. protected override void OnUnload() { // Unbind all the resources by binding the targets to 0/null. @@ -209,9 +210,70 @@ protected override void OnUnload() GL.DeleteBuffer(_vertexBufferObject); GL.DeleteVertexArray(_vertexArrayObject); - GL.DeleteProgram(_shader.Handle); + GL.DeleteProgram(_shader); base.OnUnload(); } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + // Compile vertex shader. + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + + // We do the same for the fragment shader. + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + // These two shaders must then be merged into a shader program, which can then be used by OpenGL. + // To do this, create a program... + int handle = GL.CreateProgram(); + + // Attach both shaders... + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + // And then link them together. + GL.LinkProgram(handle); + + // Check for linking errors + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + // We can use `GL.GetProgramInfoLog(program)` to get information about the error. + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + // When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program. + // Detach them, and then delete them. + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + // GL.CreateShader will create an empty shader. The ShaderType enum denotes which type of shader will be created. + int shader = GL.CreateShader(type); + + // Upload the GLSL source code. + GL.ShaderSource(shader, source); + + // Try to compile the shader + GL.CompileShader(shader); + + // Check for compilation errors + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + // We can use `GL.GetShaderInfoLog(shader)` to get information about the error. + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } } } diff --git a/Chapter1/3-ElementBufferObjects/3-ElementBufferObjects.csproj b/Chapter1/3-ElementBufferObjects/3-ElementBufferObjects.csproj index 25afd09..56a56ae 100644 --- a/Chapter1/3-ElementBufferObjects/3-ElementBufferObjects.csproj +++ b/Chapter1/3-ElementBufferObjects/3-ElementBufferObjects.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,10 +11,7 @@ - - - - - + + diff --git a/Chapter1/3-ElementBufferObjects/Program.cs b/Chapter1/3-ElementBufferObjects/Program.cs index d5d1fe0..3e9c242 100644 --- a/Chapter1/3-ElementBufferObjects/Program.cs +++ b/Chapter1/3-ElementBufferObjects/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Element Buffer Objects", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/3-ElementBufferObjects/Shaders/shader.vert b/Chapter1/3-ElementBufferObjects/Shaders/shader.vert index 42a0e96..0a3cec8 100644 --- a/Chapter1/3-ElementBufferObjects/Shaders/shader.vert +++ b/Chapter1/3-ElementBufferObjects/Shaders/shader.vert @@ -1,8 +1,8 @@ #version 330 core -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; void main(void) { - gl_Position = vec4(aPosition, 1.0); + gl_Position = vec4(aPos, 1.0); } \ No newline at end of file diff --git a/Chapter1/3-ElementBufferObjects/Window.cs b/Chapter1/3-ElementBufferObjects/Window.cs index 012f8d8..d98b245 100644 --- a/Chapter1/3-ElementBufferObjects/Window.cs +++ b/Chapter1/3-ElementBufferObjects/Window.cs @@ -1,8 +1,9 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using System; +using System.IO; namespace LearnOpenTK { @@ -38,7 +39,7 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; // Add a handle for the EBO private int _elementBufferObject; @@ -76,8 +77,8 @@ protected override void OnLoad() GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); // The EBO has now been properly setup. Go to the Render function to see how we draw our rectangle now! - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); } protected override void OnRenderFrame(FrameEventArgs e) @@ -86,7 +87,7 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.Clear(ClearBufferMask.ColorBufferBit); - _shader.Use(); + GL.UseProgram(_shader); // Because ElementArrayObject is a property of the currently bound VAO, // the buffer you will find in the ElementArrayBuffer will change with the currently bound VAO. @@ -115,10 +116,56 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) + { + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) { - base.OnResize(e); - GL.Viewport(0, 0, Size.X, Size.Y); + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; } } } \ No newline at end of file diff --git a/Chapter1/4-Shaders-InsAndOuts/4-Shaders-InsAndOuts.csproj b/Chapter1/4-Shaders-InsAndOuts/4-Shaders-InsAndOuts.csproj index 25afd09..56a56ae 100644 --- a/Chapter1/4-Shaders-InsAndOuts/4-Shaders-InsAndOuts.csproj +++ b/Chapter1/4-Shaders-InsAndOuts/4-Shaders-InsAndOuts.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,10 +11,7 @@ - - - - - + + diff --git a/Chapter1/4-Shaders-InsAndOuts/Program.cs b/Chapter1/4-Shaders-InsAndOuts/Program.cs index 33574f3..7e6569b 100644 --- a/Chapter1/4-Shaders-InsAndOuts/Program.cs +++ b/Chapter1/4-Shaders-InsAndOuts/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Shaders In and Outs!", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/4-Shaders-InsAndOuts/Shaders/shader.vert b/Chapter1/4-Shaders-InsAndOuts/Shaders/shader.vert index cb39b32..c87b6e7 100644 --- a/Chapter1/4-Shaders-InsAndOuts/Shaders/shader.vert +++ b/Chapter1/4-Shaders-InsAndOuts/Shaders/shader.vert @@ -1,7 +1,7 @@ #version 330 core // the position variable has attribute position 0 -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; // This variable uses the keyword out in order to pass on the value to the // next shader down in the chain, in this case the frag shader @@ -10,7 +10,7 @@ out vec4 vertexColor; void main(void) { // see how we directly give a vec3 to vec4's constructor - gl_Position = vec4(aPosition, 1.0); + gl_Position = vec4(aPos, 1.0); // Here we assign the variable a dark red color to the out variable vertexColor = vec4(0.5, 0.0, 0.0, 1.0); diff --git a/Chapter1/4-Shaders-InsAndOuts/Window.cs b/Chapter1/4-Shaders-InsAndOuts/Window.cs index dd2f4f0..e066ea0 100644 --- a/Chapter1/4-Shaders-InsAndOuts/Window.cs +++ b/Chapter1/4-Shaders-InsAndOuts/Window.cs @@ -1,10 +1,10 @@ -using System; -using OpenTK.Graphics.OpenGL4; -using LearnOpenTK.Common; +using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.Common; using OpenTK.Windowing.GraphicsLibraryFramework; +using System; using System.Diagnostics; +using System.IO; namespace LearnOpenTK @@ -25,7 +25,7 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -56,8 +56,8 @@ protected override void OnLoad() GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount); Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}"); - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); } protected override void OnRenderFrame(FrameEventArgs e) @@ -66,7 +66,7 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.Clear(ClearBufferMask.ColorBufferBit); - _shader.Use(); + GL.UseProgram(_shader); GL.BindVertexArray(_vertexArrayObject); @@ -87,11 +87,56 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); - GL.Viewport(0, 0, Size.X, Size.Y); + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; } } } \ No newline at end of file diff --git a/Chapter1/4-Shaders-MoreAttributes/4-Shaders-MoreAttributes.csproj b/Chapter1/4-Shaders-MoreAttributes/4-Shaders-MoreAttributes.csproj index 25afd09..56a56ae 100644 --- a/Chapter1/4-Shaders-MoreAttributes/4-Shaders-MoreAttributes.csproj +++ b/Chapter1/4-Shaders-MoreAttributes/4-Shaders-MoreAttributes.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,10 +11,7 @@ - - - - - + + diff --git a/Chapter1/4-Shaders-MoreAttributes/Program.cs b/Chapter1/4-Shaders-MoreAttributes/Program.cs index d5150d5..37d395e 100644 --- a/Chapter1/4-Shaders-MoreAttributes/Program.cs +++ b/Chapter1/4-Shaders-MoreAttributes/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Shaders More Attributes!", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/4-Shaders-MoreAttributes/Shaders/shader.vert b/Chapter1/4-Shaders-MoreAttributes/Shaders/shader.vert index 364b96f..ada856b 100644 --- a/Chapter1/4-Shaders-MoreAttributes/Shaders/shader.vert +++ b/Chapter1/4-Shaders-MoreAttributes/Shaders/shader.vert @@ -1,7 +1,7 @@ #version 330 core // the position variable has attribute position 0 -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; // This is where the color values we assigned in the main program goes to layout(location = 1) in vec3 aColor; @@ -11,7 +11,7 @@ out vec3 ourColor; // output a color to the fragment shader void main(void) { // see how we directly give a vec3 to vec4's constructor - gl_Position = vec4(aPosition, 1.0); + gl_Position = vec4(aPos, 1.0); // We use the outColor variable to pass on the color information to the frag shader ourColor = aColor; diff --git a/Chapter1/4-Shaders-MoreAttributes/Window.cs b/Chapter1/4-Shaders-MoreAttributes/Window.cs index 53d8985..a7c81cc 100644 --- a/Chapter1/4-Shaders-MoreAttributes/Window.cs +++ b/Chapter1/4-Shaders-MoreAttributes/Window.cs @@ -1,10 +1,10 @@ -using System; -using OpenTK.Graphics.OpenGL4; -using LearnOpenTK.Common; +using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.Common; using OpenTK.Windowing.GraphicsLibraryFramework; +using System; using System.Diagnostics; +using System.IO; namespace LearnOpenTK { @@ -28,7 +28,7 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -67,8 +67,8 @@ protected override void OnLoad() GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount); Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}"); - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); } protected override void OnRenderFrame(FrameEventArgs e) @@ -77,7 +77,7 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.Clear(ClearBufferMask.ColorBufferBit); - _shader.Use(); + GL.UseProgram(_shader); GL.BindVertexArray(_vertexArrayObject); @@ -98,11 +98,56 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); - GL.Viewport(0, 0, Size.X, Size.Y); + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; } } } \ No newline at end of file diff --git a/Chapter1/4-Shaders-Uniforms/4-Shaders-Uniforms.csproj b/Chapter1/4-Shaders-Uniforms/4-Shaders-Uniforms.csproj index 25afd09..56a56ae 100644 --- a/Chapter1/4-Shaders-Uniforms/4-Shaders-Uniforms.csproj +++ b/Chapter1/4-Shaders-Uniforms/4-Shaders-Uniforms.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,10 +11,7 @@ - - - - - + + diff --git a/Chapter1/4-Shaders-Uniforms/Program.cs b/Chapter1/4-Shaders-Uniforms/Program.cs index 43c71c3..582f9c5 100644 --- a/Chapter1/4-Shaders-Uniforms/Program.cs +++ b/Chapter1/4-Shaders-Uniforms/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Shaders Uniforms!", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/4-Shaders-Uniforms/Shaders/shader.vert b/Chapter1/4-Shaders-Uniforms/Shaders/shader.vert index bb42695..a86fec7 100644 --- a/Chapter1/4-Shaders-Uniforms/Shaders/shader.vert +++ b/Chapter1/4-Shaders-Uniforms/Shaders/shader.vert @@ -1,9 +1,9 @@ #version 330 core -layout(location = 0) in vec3 aPosition; // the position variable has attribute position 0 +layout(location = 0) in vec3 aPos; // the position variable has attribute position 0 void main(void) { // see how we directly give a vec3 to vec4's constructor - gl_Position = vec4(aPosition, 1.0); + gl_Position = vec4(aPos, 1.0); } \ No newline at end of file diff --git a/Chapter1/4-Shaders-Uniforms/Window.cs b/Chapter1/4-Shaders-Uniforms/Window.cs index ece406d..c74bd02 100644 --- a/Chapter1/4-Shaders-Uniforms/Window.cs +++ b/Chapter1/4-Shaders-Uniforms/Window.cs @@ -1,10 +1,11 @@ -using System; -using System.Diagnostics; -using OpenTK.Graphics.OpenGL4; -using LearnOpenTK.Common; -using OpenTK.Windowing.Desktop; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; +using System; +using System.Diagnostics; +using System.IO; namespace LearnOpenTK { @@ -29,7 +30,7 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -56,8 +57,8 @@ protected override void OnLoad() GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount); Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}"); - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); // We start the stopwatch here as this method is only called once. _timer = new Stopwatch(); @@ -70,7 +71,7 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.Clear(ClearBufferMask.ColorBufferBit); - _shader.Use(); + GL.UseProgram(_shader); // Here, we get the total seconds that have elapsed since the last time this method has reset // and we assign it to the timeValue variable so it can be used for the pulsating color. @@ -83,7 +84,7 @@ protected override void OnRenderFrame(FrameEventArgs e) // This gets the uniform variable location from the frag shader so that we can // assign the new green value to it. - int vertexColorLocation = GL.GetUniformLocation(_shader.Handle, "ourColor"); + int vertexColorLocation = GL.GetUniformLocation(_shader, "ourColor"); // Here we're assigning the ourColor variable in the frag shader // via the OpenGL Uniform method which takes in the value as the individual vec values (which total 4 in this instance). @@ -112,11 +113,56 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) + { + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) { - base.OnResize(e); + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } - GL.Viewport(0, 0, Size.X, Size.Y); + return shader; } } } \ No newline at end of file diff --git a/Chapter1/5-Textures/5-Textures.csproj b/Chapter1/5-Textures/5-Textures.csproj index b5e0922..c8b95b6 100644 --- a/Chapter1/5-Textures/5-Textures.csproj +++ b/Chapter1/5-Textures/5-Textures.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + diff --git a/Chapter1/5-Textures/Program.cs b/Chapter1/5-Textures/Program.cs index 905d56a..1406055 100644 --- a/Chapter1/5-Textures/Program.cs +++ b/Chapter1/5-Textures/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Textures", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/5-Textures/Shaders/shader.vert b/Chapter1/5-Textures/Shaders/shader.vert index 494f81d..85c3668 100644 --- a/Chapter1/5-Textures/Shaders/shader.vert +++ b/Chapter1/5-Textures/Shaders/shader.vert @@ -1,6 +1,6 @@ #version 330 core -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; // We add another input variable for the texture coordinates. @@ -18,5 +18,5 @@ void main(void) texCoord = aTexCoord; - gl_Position = vec4(aPosition, 1.0); + gl_Position = vec4(aPos, 1.0); } \ No newline at end of file diff --git a/Chapter1/5-Textures/Window.cs b/Chapter1/5-Textures/Window.cs index e8739a3..2548dbf 100644 --- a/Chapter1/5-Textures/Window.cs +++ b/Chapter1/5-Textures/Window.cs @@ -1,8 +1,10 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -32,10 +34,10 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; // For documentation on this, check Texture.cs. - private Texture _texture; + private int _texture; public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -59,26 +61,34 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); - // The shaders have been modified to include the texture coordinates, check them out after finishing the OnLoad function. - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); - // Because there's now 5 floats between the start of the first vertex and the start of the second, // we modify the stride from 3 * sizeof(float) to 5 * sizeof(float). // This will now pass the new vertex array to the buffer. - var vertexLocation = _shader.GetAttribLocation("aPosition"); + var vertexLocation = 0; // The location of aPos GL.EnableVertexAttribArray(vertexLocation); GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); // Next, we also setup texture coordinates. It works in much the same way. // We add an offset of 3, since the texture coordinates comes after the position data. // We also change the amount of data to 2 because there's only 2 floats for texture coordinates. - var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); + var texCoordLocation = 1; // The location of aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); - _texture = Texture.LoadFromFile("Resources/container.png"); - _texture.Use(TextureUnit.Texture0); + // The shaders have been modified to include the texture coordinates, check them out after finishing the OnLoad function. + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); + + // Next, we must setup the samplers in the shaders to use the right texture units. + // The int we send to the uniform indicates which texture unit the sampler should use. + // This is more important when using multiple textures as in the next chapter. + GL.Uniform1(GL.GetUniformLocation(_shader, "texture0"), 0); + + // Now we need to actually load the texture data itself. + // See the LoadTextureFromFile function for how to do this. + _texture = LoadTextureFromFile("Resources/container.png"); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); } protected override void OnRenderFrame(FrameEventArgs e) @@ -89,8 +99,10 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vertexArrayObject); - _texture.Use(TextureUnit.Texture0); - _shader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); + + GL.UseProgram(_shader); GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); @@ -109,11 +121,117 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) + { + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) { - base.OnResize(e); + // Generate handle + int handle = GL.GenTexture(); + + // Bind the handle + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + // For this example, we're going to use the StbImageSharp library to load textures. + + // OpenGL has it's texture origin in the lower left corner instead of the top left corner, + // so we tell StbImageSharp to flip the image when loading. + StbImage.stbi_set_flip_vertically_on_load(1); + + // Here we open a stream to the file and pass it to StbImageSharp to load. + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + + // Now that our pixels are prepared, it's time to generate a texture. We do this with GL.TexImage2D. + // Arguments: + // The type of texture we're generating. There are various different types of textures, but the only one we need right now is Texture2D. + // Level of detail. We can use this to start from a smaller mipmap (if we want), but we don't need to do that, so leave it at 0. + // Target format of the pixels. This is the format OpenGL will store our image with. + // Width of the image + // Height of the image. + // Border of the image. This must always be 0; it's a legacy parameter that Khronos never got rid of. + // The format of the pixels, explained above. Since we loaded the pixels as RGBA earlier, we need to use PixelFormat.Rgba. + // Data type of the pixels. + // And finally, the actual pixels. + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } - GL.Viewport(0, 0, Size.X, Size.Y); + // Now that our texture is loaded, we can set a few settings to affect how the image appears on rendering. + + // First, we set the min and mag filter. These are used for when the texture is scaled down and up, respectively. + // Here, we use Linear for both. This means that OpenGL will try to blend pixels, meaning that textures scaled too far will look blurred. + // You could also use (amongst other options) Nearest, which just grabs the nearest pixel, which makes the texture look pixelated if scaled too far. + // NOTE: The default settings for both of these are LinearMipmap. If you leave these as default but don't generate mipmaps, + // your image will fail to render at all (usually resulting in pure black instead). + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + // Now, set the wrapping mode. S is for the X axis, and T is for the Y axis. + // We set this to Repeat so that textures will repeat when wrapped. Not demonstrated here since the texture coordinates exactly match + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + // Next, generate mipmaps. + // Mipmaps are smaller copies of the texture, scaled down. Each mipmap level is half the size of the previous one + // Generated mipmaps go all the way down to just one pixel. + // OpenGL will automatically switch between mipmaps when an object gets sufficiently far away. + // This prevents moiré effects, as well as saving on texture bandwidth. + // Here you can see and read about the morié effect https://en.wikipedia.org/wiki/Moir%C3%A9_pattern + // Here is an example of mips in action https://en.wikipedia.org/wiki/File:Mipmap_Aliasing_Comparison.png + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + return handle; } } } \ No newline at end of file diff --git a/Chapter1/6-MultipleTextures/6-MultipleTextures.csproj b/Chapter1/6-MultipleTextures/6-MultipleTextures.csproj index b5e0922..c8b95b6 100644 --- a/Chapter1/6-MultipleTextures/6-MultipleTextures.csproj +++ b/Chapter1/6-MultipleTextures/6-MultipleTextures.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + diff --git a/Chapter1/6-MultipleTextures/Program.cs b/Chapter1/6-MultipleTextures/Program.cs index 3f29f8a..99cff2b 100644 --- a/Chapter1/6-MultipleTextures/Program.cs +++ b/Chapter1/6-MultipleTextures/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Multiple Textures", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/6-MultipleTextures/Shaders/shader.vert b/Chapter1/6-MultipleTextures/Shaders/shader.vert index 4fb2871..4934eaa 100644 --- a/Chapter1/6-MultipleTextures/Shaders/shader.vert +++ b/Chapter1/6-MultipleTextures/Shaders/shader.vert @@ -1,6 +1,6 @@ #version 330 core -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; layout(location = 1) in vec2 aTexCoord; @@ -10,5 +10,5 @@ void main(void) { texCoord = aTexCoord; - gl_Position = vec4(aPosition, 1.0); + gl_Position = vec4(aPos, 1.0); } \ No newline at end of file diff --git a/Chapter1/6-MultipleTextures/Window.cs b/Chapter1/6-MultipleTextures/Window.cs index 6b19d83..4e902ee 100644 --- a/Chapter1/6-MultipleTextures/Window.cs +++ b/Chapter1/6-MultipleTextures/Window.cs @@ -1,8 +1,10 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -29,11 +31,11 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; - private Texture _texture; + private int _texture; - private Texture _texture2; + private int _texture2; public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -58,31 +60,36 @@ protected override void OnLoad() GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); // shader.frag has been modified yet again, take a look at it as well. - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); - var vertexLocation = _shader.GetAttribLocation("aPosition"); + var vertexLocation = 0; // The location of aPos GL.EnableVertexAttribArray(vertexLocation); GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); - var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); + var texCoordLocation = 1; // The location of aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); - _texture = Texture.LoadFromFile("Resources/container.png"); + _texture = LoadTextureFromFile("Resources/container.png"); // Texture units are explained in Texture.cs, at the Use function. // First texture goes in texture unit 0. - _texture.Use(TextureUnit.Texture0); + + // First we use GL.ActiveTexture to tell OpenGL which texture unit we would like following commands to use. + GL.ActiveTexture(TextureUnit.Texture0); + // Then we bind our first texture to the 0th texture unit as a 2D texture. + GL.BindTexture(TextureTarget.Texture2D, _texture); // This is helpful because System.Drawing reads the pixels differently than OpenGL expects. - _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); + _texture2 = LoadTextureFromFile("Resources/awesomeface.png"); // Then, the second goes in texture unit 1. - _texture2.Use(TextureUnit.Texture1); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); - // Next, we must setup the samplers in the shaders to use the right textures. + // Next, we must setup the samplers in the shaders to use the right texture units. // The int we send to the uniform indicates which texture unit the sampler should use. - _shader.SetInt("texture0", 0); - _shader.SetInt("texture1", 1); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture0"), 0); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture1"), 1); } protected override void OnRenderFrame(FrameEventArgs e) @@ -93,9 +100,12 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vertexArrayObject); - _texture.Use(TextureUnit.Texture0); - _texture2.Use(TextureUnit.Texture1); - _shader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); + + GL.UseProgram(_shader); GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); @@ -114,11 +124,82 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) + { + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) { - base.OnResize(e); + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - GL.Viewport(0, 0, Size.X, Size.Y); + return handle; } } } \ No newline at end of file diff --git a/Chapter1/7-Transformations/7-Transformations.csproj b/Chapter1/7-Transformations/7-Transformations.csproj index b5e0922..c8b95b6 100644 --- a/Chapter1/7-Transformations/7-Transformations.csproj +++ b/Chapter1/7-Transformations/7-Transformations.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + diff --git a/Chapter1/7-Transformations/Program.cs b/Chapter1/7-Transformations/Program.cs index 4008987..e5aac3c 100644 --- a/Chapter1/7-Transformations/Program.cs +++ b/Chapter1/7-Transformations/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Transformations", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/7-Transformations/Shaders/shader.vert b/Chapter1/7-Transformations/Shaders/shader.vert index bb064b4..77ba190 100644 --- a/Chapter1/7-Transformations/Shaders/shader.vert +++ b/Chapter1/7-Transformations/Shaders/shader.vert @@ -1,6 +1,6 @@ #version 330 core -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; layout(location = 1) in vec2 aTexCoord; @@ -14,5 +14,5 @@ void main(void) texCoord = aTexCoord; // Then all you have to do is multiply the vertices by the transformation matrix, and you'll see your transformation in the scene! - gl_Position = vec4(aPosition, 1.0) * transform; + gl_Position = vec4(aPos, 1.0) * transform; } \ No newline at end of file diff --git a/Chapter1/7-Transformations/Window.cs b/Chapter1/7-Transformations/Window.cs index 2507132..d017c87 100644 --- a/Chapter1/7-Transformations/Window.cs +++ b/Chapter1/7-Transformations/Window.cs @@ -1,9 +1,11 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -39,11 +41,11 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; - private Texture _texture; + private int _texture; - private Texture _texture2; + private int _texture2; public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) @@ -68,25 +70,27 @@ protected override void OnLoad() GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); // shader.vert has been modified, take a look at it as well. - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); - var vertexLocation = _shader.GetAttribLocation("aPosition"); + var vertexLocation = 0; // The location of aPos GL.EnableVertexAttribArray(vertexLocation); GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); - var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); + var texCoordLocation = 1; // The location of aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); - _texture = Texture.LoadFromFile("Resources/container.png"); - _texture.Use(TextureUnit.Texture0); + _texture = LoadTextureFromFile("Resources/container.png"); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); - _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); - _texture2.Use(TextureUnit.Texture1); + _texture2 = LoadTextureFromFile("Resources/awesomeface.png"); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); - _shader.SetInt("texture0", 0); - _shader.SetInt("texture1", 1); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture0"), 0); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture1"), 1); } protected override void OnRenderFrame(FrameEventArgs e) @@ -123,13 +127,16 @@ protected override void OnRenderFrame(FrameEventArgs e) // The next tutorial will be about how to set one up so we can use more human-readable numbers. transform = transform * Matrix4.CreateTranslation(0.1f, 0.1f, 0.0f); - _texture.Use(TextureUnit.Texture0); - _texture2.Use(TextureUnit.Texture1); - _shader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); + + GL.UseProgram(_shader); // Now that the matrix is finished, pass it to the vertex shader. // Go over to shader.vert to see how we finally apply this to the vertices. - _shader.SetMatrix4("transform", transform); + GL.UniformMatrix4(GL.GetUniformLocation(_shader, "transform"), true, ref transform); // And that's it for now! In the next tutorial, we'll see how to setup a full coordinates system. @@ -150,11 +157,82 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) + { + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) { - base.OnResize(e); + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - GL.Viewport(0, 0, Size.X, Size.Y); + return handle; } } } \ No newline at end of file diff --git a/Chapter1/8-CoordinatesSystems/8-CoordinatesSystems.csproj b/Chapter1/8-CoordinatesSystems/8-CoordinatesSystems.csproj index b5e0922..c8b95b6 100644 --- a/Chapter1/8-CoordinatesSystems/8-CoordinatesSystems.csproj +++ b/Chapter1/8-CoordinatesSystems/8-CoordinatesSystems.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + diff --git a/Chapter1/8-CoordinatesSystems/Program.cs b/Chapter1/8-CoordinatesSystems/Program.cs index d26f898..67618ff 100644 --- a/Chapter1/8-CoordinatesSystems/Program.cs +++ b/Chapter1/8-CoordinatesSystems/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Coordinates Systems", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/8-CoordinatesSystems/Shaders/shader.vert b/Chapter1/8-CoordinatesSystems/Shaders/shader.vert index 6457b69..a1dd2e5 100644 --- a/Chapter1/8-CoordinatesSystems/Shaders/shader.vert +++ b/Chapter1/8-CoordinatesSystems/Shaders/shader.vert @@ -1,6 +1,6 @@ #version 330 core -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; layout(location = 1) in vec2 aTexCoord; @@ -14,5 +14,5 @@ void main(void) { texCoord = aTexCoord; - gl_Position = vec4(aPosition, 1.0) * model * view * projection; + gl_Position = vec4(aPos, 1.0) * model * view * projection; } diff --git a/Chapter1/8-CoordinatesSystems/Window.cs b/Chapter1/8-CoordinatesSystems/Window.cs index 3282234..853c397 100644 --- a/Chapter1/8-CoordinatesSystems/Window.cs +++ b/Chapter1/8-CoordinatesSystems/Window.cs @@ -1,9 +1,11 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -33,11 +35,11 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; - private Texture _texture; + private int _texture; - private Texture _texture2; + private int _texture2; // We create a double to hold how long has passed since the program was opened. private double _time; @@ -78,25 +80,27 @@ protected override void OnLoad() GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); // shader.vert has been modified. Take a look at it after the explanation in OnRenderFrame. - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); - var vertexLocation = _shader.GetAttribLocation("aPosition"); + var vertexLocation = 0; // The location of aPos GL.EnableVertexAttribArray(vertexLocation); GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); - var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); + var texCoordLocation = 1; // The location of aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); - _texture = Texture.LoadFromFile("Resources/container.png"); - _texture.Use(TextureUnit.Texture0); + _texture = LoadTextureFromFile("Resources/container.png"); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); - _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); - _texture2.Use(TextureUnit.Texture1); + _texture2 = LoadTextureFromFile("Resources/awesomeface.png"); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); - _shader.SetInt("texture0", 0); - _shader.SetInt("texture1", 1); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture0"), 0); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture1"), 1); // For the view, we don't do too much here. Next tutorial will be all about a Camera class that will make it much easier to manipulate the view. // For now, we move it backwards three units on the Z axis. @@ -124,12 +128,15 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vertexArrayObject); - _texture.Use(TextureUnit.Texture0); - _texture2.Use(TextureUnit.Texture1); - _shader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); + + GL.UseProgram(_shader); // Finally, we have the model matrix. This determines the position of the model. - var model = Matrix4.Identity * Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time)); + var model = Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time)); // Then, we pass all of these matrices to the vertex shader. // You could also multiply them here and then pass, which is faster, but having the separate matrices available is used for some advanced effects. @@ -140,9 +147,9 @@ protected override void OnRenderFrame(FrameEventArgs e) // If you pass the individual matrices to the shader and multiply there, you have to do in the order "model * view * projection". // You can think like this: first apply the modelToWorld (aka model) matrix, then apply the worldToView (aka view) matrix, // and finally apply the viewToProjectedSpace (aka projection) matrix. - _shader.SetMatrix4("model", model); - _shader.SetMatrix4("view", _view); - _shader.SetMatrix4("projection", _projection); + GL.UniformMatrix4(GL.GetUniformLocation(_shader, "model"), true, ref model); + GL.UniformMatrix4(GL.GetUniformLocation(_shader, "view"), true, ref _view); + GL.UniformMatrix4(GL.GetUniformLocation(_shader, "projection"), true, ref _projection); GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); @@ -161,11 +168,82 @@ protected override void OnUpdateFrame(FrameEventArgs e) } } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) + { + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); + } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) { - base.OnResize(e); + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - GL.Viewport(0, 0, Size.X, Size.Y); + return handle; } } } \ No newline at end of file diff --git a/Chapter1/9-Camera/9-Camera.csproj b/Chapter1/9-Camera/9-Camera.csproj index b5e0922..c8b95b6 100644 --- a/Chapter1/9-Camera/9-Camera.csproj +++ b/Chapter1/9-Camera/9-Camera.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + diff --git a/Common/Camera.cs b/Chapter1/9-Camera/Camera.cs similarity index 99% rename from Common/Camera.cs rename to Chapter1/9-Camera/Camera.cs index b705c6b..15b64b8 100644 --- a/Common/Camera.cs +++ b/Chapter1/9-Camera/Camera.cs @@ -1,7 +1,7 @@ using OpenTK.Mathematics; using System; -namespace LearnOpenTK.Common +namespace LearnOpenTK { // This is the camera class as it could be set up after the tutorials on the website. // It is important to note there are a few ways you could have set up this camera. diff --git a/Chapter1/9-Camera/Program.cs b/Chapter1/9-Camera/Program.cs index 907d3d3..4526663 100644 --- a/Chapter1/9-Camera/Program.cs +++ b/Chapter1/9-Camera/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Camera", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter1/9-Camera/Shaders/shader.vert b/Chapter1/9-Camera/Shaders/shader.vert index 6457b69..a1dd2e5 100644 --- a/Chapter1/9-Camera/Shaders/shader.vert +++ b/Chapter1/9-Camera/Shaders/shader.vert @@ -1,6 +1,6 @@ #version 330 core -layout(location = 0) in vec3 aPosition; +layout(location = 0) in vec3 aPos; layout(location = 1) in vec2 aTexCoord; @@ -14,5 +14,5 @@ void main(void) { texCoord = aTexCoord; - gl_Position = vec4(aPosition, 1.0) * model * view * projection; + gl_Position = vec4(aPos, 1.0) * model * view * projection; } diff --git a/Chapter1/9-Camera/Window.cs b/Chapter1/9-Camera/Window.cs index f9ccc1f..c8fdded 100644 --- a/Chapter1/9-Camera/Window.cs +++ b/Chapter1/9-Camera/Window.cs @@ -1,10 +1,11 @@ -using System; -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -40,11 +41,11 @@ public class Window : GameWindow private int _vertexArrayObject; - private Shader _shader; + private int _shader; - private Texture _texture; + private int _texture; - private Texture _texture2; + private int _texture2; // The view and projection matrices have been removed as we don't need them here anymore. // They can now be found in the new camera class. @@ -84,25 +85,27 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); - _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - _shader.Use(); + _shader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + GL.UseProgram(_shader); - var vertexLocation = _shader.GetAttribLocation("aPosition"); + var vertexLocation = 0; // The location of aPos GL.EnableVertexAttribArray(vertexLocation); GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); - var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); + var texCoordLocation = 1; // The location of aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); - _texture = Texture.LoadFromFile("Resources/container.png"); - _texture.Use(TextureUnit.Texture0); + _texture = LoadTextureFromFile("Resources/container.png"); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); - _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); - _texture2.Use(TextureUnit.Texture1); + _texture2 = LoadTextureFromFile("Resources/awesomeface.png"); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); - _shader.SetInt("texture0", 0); - _shader.SetInt("texture1", 1); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture0"), 0); + GL.Uniform1(GL.GetUniformLocation(_shader, "texture1"), 1); // We initialize the camera so that it is 3 units back from where the rectangle is. // We also give it the proper aspect ratio. @@ -122,14 +125,20 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vertexArrayObject); - _texture.Use(TextureUnit.Texture0); - _texture2.Use(TextureUnit.Texture1); - _shader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _texture); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _texture2); + + GL.UseProgram(_shader); - var model = Matrix4.Identity * Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time)); - _shader.SetMatrix4("model", model); - _shader.SetMatrix4("view", _camera.GetViewMatrix()); - _shader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); + + var model = Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time)); + GL.UniformMatrix4(GL.GetUniformLocation(_shader, "model"), true, ref model); + GL.UniformMatrix4(GL.GetUniformLocation(_shader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_shader, "projection"), true, ref projection); GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); @@ -211,13 +220,85 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); // We need to update the aspect ratio once the window has been resized. _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + return handle; + } } } diff --git a/Chapter2/1-Colors/1-Colors.csproj b/Chapter2/1-Colors/1-Colors.csproj index ec7f0ed..dd06013 100644 --- a/Chapter2/1-Colors/1-Colors.csproj +++ b/Chapter2/1-Colors/1-Colors.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,10 +11,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/1-Colors/Camera.cs b/Chapter2/1-Colors/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/1-Colors/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/1-Colors/OpenTK.dll.config b/Chapter2/1-Colors/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/1-Colors/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/1-Colors/Program.cs b/Chapter2/1-Colors/Program.cs index e14478c..1cf8360 100644 --- a/Chapter2/1-Colors/Program.cs +++ b/Chapter2/1-Colors/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Colors", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/1-Colors/Window.cs b/Chapter2/1-Colors/Window.cs index 69715a4..8b44af1 100644 --- a/Chapter2/1-Colors/Window.cs +++ b/Chapter2/1-Colors/Window.cs @@ -1,9 +1,11 @@ -using LearnOpenTK.Common; +using OpenTK.Graphics.GL; using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using System; +using System.IO; namespace LearnOpenTK { @@ -77,9 +79,9 @@ public class Window : GameWindow // We also need two shaders, one for the lamp and one for our lighting material. // The lighting shader is where most of this chapter will take place as this is where a lot of the lighting "magic" happens. - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; private Camera _camera; @@ -107,15 +109,16 @@ protected override void OnLoad() // Load the two different shaders, they use the same vertex shader program. However they have two different fragment shaders. // This is because the lamp only uses a basic shader to turn it white, it wouldn't make sense to have the lamp lit in other colors. // The lighting shaders uses the lighting.frag shader which is what a large part of this chapter will be about - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); { // Initialize the vao for the model _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); - var vertexLocation = _lightingShader.GetAttribLocation("aPos"); + // Specified in the shader.vert file, aPos has location 0. + var vertexLocation = 0; GL.EnableVertexAttribArray(vertexLocation); GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); } @@ -125,8 +128,8 @@ protected override void OnLoad() _vaoLamp = GL.GenVertexArray(); GL.BindVertexArray(_vaoLamp); - // Set the vertex attributes (only position data for our lamp) - var vertexLocation = _lampShader.GetAttribLocation("aPos"); + // Specified in the shader.vert file, aPos has location 0. + var vertexLocation = 0; GL.EnableVertexAttribArray(vertexLocation); GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); } @@ -145,29 +148,34 @@ protected override void OnRenderFrame(FrameEventArgs e) // Draw the model/cube with the lighting shader GL.BindVertexArray(_vaoModel); - _lightingShader.Use(); + GL.UseProgram(_lightingShader); + + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); // Matrix4.Identity is used as the matrix, since we just want to draw it at 0, 0, 0 - _lightingShader.SetMatrix4("model", Matrix4.Identity); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + Matrix4 model = Matrix4.Identity; - _lightingShader.SetVector3("objectColor", new Vector3(1.0f, 0.5f, 0.31f)); - _lightingShader.SetVector3("lightColor", new Vector3(1.0f, 1.0f, 1.0f)); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); + + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "objectColor"), new Vector3(1.0f, 0.5f, 0.31f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "lightColor"), new Vector3(1.0f, 1.0f, 1.0f)); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); // Draw the lamp, this is mostly the same as for the model cube GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); // We scale the lamp cube down a bit to make it less dominant - lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos); + // We scale the lamp cube down a bit to make it less dominant + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_lightPos); - _lampShader.SetMatrix4("model", lampMatrix); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -243,12 +251,58 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); - GL.Viewport(0, 0, Size.X, Size.Y); + GL.Viewport(0, 0, e.Width, e.Height); + _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } } } diff --git a/Chapter2/2-BasicLighting/2-BasicLighting.csproj b/Chapter2/2-BasicLighting/2-BasicLighting.csproj index ec7f0ed..dd06013 100644 --- a/Chapter2/2-BasicLighting/2-BasicLighting.csproj +++ b/Chapter2/2-BasicLighting/2-BasicLighting.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,10 +11,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/2-BasicLighting/Camera.cs b/Chapter2/2-BasicLighting/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/2-BasicLighting/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/2-BasicLighting/OpenTK.dll.config b/Chapter2/2-BasicLighting/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/2-BasicLighting/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/2-BasicLighting/Program.cs b/Chapter2/2-BasicLighting/Program.cs index d1c61fe..d54fdfc 100644 --- a/Chapter2/2-BasicLighting/Program.cs +++ b/Chapter2/2-BasicLighting/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Basic lighting", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/2-BasicLighting/Window.cs b/Chapter2/2-BasicLighting/Window.cs index d80d979..24f5883 100644 --- a/Chapter2/2-BasicLighting/Window.cs +++ b/Chapter2/2-BasicLighting/Window.cs @@ -1,9 +1,11 @@ -using LearnOpenTK.Common; +using OpenTK.Graphics.GL; using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using System; +using System.IO; namespace LearnOpenTK { @@ -69,9 +71,9 @@ public class Window : GameWindow private int _vaoLamp; - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; private Camera _camera; @@ -96,20 +98,21 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); { _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); - var positionLocation = _lightingShader.GetAttribLocation("aPos"); + // Specified in the shader.vert file, aPos has location 0. + var positionLocation = 0; GL.EnableVertexAttribArray(positionLocation); // Remember to change the stride as we now have 6 floats per vertex GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); - // We now need to define the layout of the normal so the shader can use it - var normalLocation = _lightingShader.GetAttribLocation("aNormal"); + // Specified in the shader.vert file, aNormal has location 1. + var normalLocation = 1; GL.EnableVertexAttribArray(normalLocation); GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float)); } @@ -118,7 +121,8 @@ protected override void OnLoad() _vaoLamp = GL.GenVertexArray(); GL.BindVertexArray(_vaoLamp); - var positionLocation = _lampShader.GetAttribLocation("aPos"); + // Specified in the shader.vert file, aPos has location 0. + var positionLocation = 0; GL.EnableVertexAttribArray(positionLocation); // Also change the stride here as we now have 6 floats per vertex. Now we don't define the normal for the lamp VAO // this is because it isn't used, it might seem like a waste to use the same VBO if they dont have the same data @@ -140,29 +144,33 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoModel); - _lightingShader.Use(); + GL.UseProgram(_lightingShader); + + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); + + Matrix4 model = Matrix4.Identity; - _lightingShader.SetMatrix4("model", Matrix4.Identity); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); - _lightingShader.SetVector3("objectColor", new Vector3(1.0f, 0.5f, 0.31f)); - _lightingShader.SetVector3("lightColor", new Vector3(1.0f, 1.0f, 1.0f)); - _lightingShader.SetVector3("lightPos", _lightPos); - _lightingShader.SetVector3("viewPos", _camera.Position); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "objectColor"), new Vector3(1.0f, 0.5f, 0.31f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "lightColor"), new Vector3(1.0f, 1.0f, 1.0f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "lightPos"), _lightPos); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "viewPos"), _camera.Position); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); - lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos); + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_lightPos); - _lampShader.SetMatrix4("model", lampMatrix); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -238,12 +246,58 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } } } diff --git a/Chapter2/3-Materials/3-Materials.csproj b/Chapter2/3-Materials/3-Materials.csproj index ec7f0ed..dd06013 100644 --- a/Chapter2/3-Materials/3-Materials.csproj +++ b/Chapter2/3-Materials/3-Materials.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -11,10 +11,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/3-Materials/Camera.cs b/Chapter2/3-Materials/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/3-Materials/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/3-Materials/OpenTK.dll.config b/Chapter2/3-Materials/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/3-Materials/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/3-Materials/Program.cs b/Chapter2/3-Materials/Program.cs index 3208c78..e4d91de 100644 --- a/Chapter2/3-Materials/Program.cs +++ b/Chapter2/3-Materials/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Materials", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/3-Materials/Window.cs b/Chapter2/3-Materials/Window.cs index 987174f..d5dc10b 100644 --- a/Chapter2/3-Materials/Window.cs +++ b/Chapter2/3-Materials/Window.cs @@ -1,11 +1,11 @@ -using System; -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using System; using System.Diagnostics; +using System.IO; namespace LearnOpenTK { @@ -69,9 +69,9 @@ public class Window : GameWindow private int _vaoLamp; - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; private Camera _camera; @@ -96,18 +96,18 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); { _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); - var positionLocation = _lightingShader.GetAttribLocation("aPos"); + var positionLocation = 0; // The location of aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); - var normalLocation = _lightingShader.GetAttribLocation("aNormal"); + var normalLocation = 1; // The location of aNormal GL.EnableVertexAttribArray(normalLocation); GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float)); } @@ -116,7 +116,7 @@ protected override void OnLoad() _vaoLamp = GL.GenVertexArray(); GL.BindVertexArray(_vaoLamp); - var positionLocation = _lampShader.GetAttribLocation("aPos"); + var positionLocation = 0; // The location of aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); } @@ -134,20 +134,24 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoModel); - _lightingShader.Use(); + GL.UseProgram(_lightingShader); - _lightingShader.SetMatrix4("model", Matrix4.Identity); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); + Matrix4 model = Matrix4.Identity; - _lightingShader.SetVector3("viewPos", _camera.Position); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); + + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "viewPos"), _camera.Position); // Here we set the material values of the cube, the material struct is just a container so to access // the underlying values we simply type "material.value" to get the location of the uniform - _lightingShader.SetVector3("material.ambient", new Vector3(1.0f, 0.5f, 0.31f)); - _lightingShader.SetVector3("material.diffuse", new Vector3(1.0f, 0.5f, 0.31f)); - _lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f)); - _lightingShader.SetFloat("material.shininess", 32.0f); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "material.ambient"), new Vector3(1.0f, 0.5f, 0.31f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "material.diffuse"), new Vector3(1.0f, 0.5f, 0.31f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "material.specular"), new Vector3(1.0f, 0.5f, 0.5f)); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.shininess"), 32.0f); // This is where we change the lights color over time using the sin function Vector3 lightColor; @@ -160,24 +164,22 @@ protected override void OnRenderFrame(FrameEventArgs e) Vector3 ambientColor = lightColor * new Vector3(0.2f); Vector3 diffuseColor = lightColor * new Vector3(0.5f); - _lightingShader.SetVector3("light.position", _lightPos); - _lightingShader.SetVector3("light.ambient", ambientColor); - _lightingShader.SetVector3("light.diffuse", diffuseColor); - _lightingShader.SetVector3("light.specular", new Vector3(1.0f, 1.0f, 1.0f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.position"), _lightPos); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.ambient"), ambientColor); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.diffuse"), diffuseColor); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.specular"), new Vector3(1.0f, 1.0f, 1.0f)); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - Matrix4 lampMatrix = Matrix4.Identity; - lampMatrix *= Matrix4.CreateScale(0.2f); - lampMatrix *= Matrix4.CreateTranslation(_lightPos); + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_lightPos); - _lampShader.SetMatrix4("model", lampMatrix); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -253,12 +255,58 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } } } diff --git a/Chapter2/4-LightingMaps/4-LightingMaps.csproj b/Chapter2/4-LightingMaps/4-LightingMaps.csproj index 9263ce0..081d661 100644 --- a/Chapter2/4-LightingMaps/4-LightingMaps.csproj +++ b/Chapter2/4-LightingMaps/4-LightingMaps.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -17,10 +17,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/4-LightingMaps/Camera.cs b/Chapter2/4-LightingMaps/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/4-LightingMaps/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/4-LightingMaps/OpenTK.dll.config b/Chapter2/4-LightingMaps/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/4-LightingMaps/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/4-LightingMaps/Program.cs b/Chapter2/4-LightingMaps/Program.cs index 92bf3c8..d67858a 100644 --- a/Chapter2/4-LightingMaps/Program.cs +++ b/Chapter2/4-LightingMaps/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Lighting maps", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/4-LightingMaps/Shaders/lighting.frag b/Chapter2/4-LightingMaps/Shaders/lighting.frag index c7c21f0..e3f5be0 100644 --- a/Chapter2/4-LightingMaps/Shaders/lighting.frag +++ b/Chapter2/4-LightingMaps/Shaders/lighting.frag @@ -28,26 +28,26 @@ in vec3 FragPos; // Now we need the texture coordinates, however we only need one set even though we have 2 textures, // as every fragment should have the same texture position no matter what texture we are using. -in vec2 TexCoords; +in vec2 TexCoord; void main() { // Each of the 3 different components now use a texture for the material values instead of the object wide color they had before. // Note: The ambient and the diffuse share the same texture. // ambient - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoord)); // Diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(norm, lightDir), 0.0); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoord)); // Specular vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoord)); vec3 result = ambient + diffuse + specular; FragColor = vec4(result, 1.0); diff --git a/Chapter2/4-LightingMaps/Shaders/shader.vert b/Chapter2/4-LightingMaps/Shaders/shader.vert index af12da3..4fa340c 100644 --- a/Chapter2/4-LightingMaps/Shaders/shader.vert +++ b/Chapter2/4-LightingMaps/Shaders/shader.vert @@ -1,7 +1,7 @@ #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; +layout (location = 2) in vec2 aTexCoord; uniform mat4 model; uniform mat4 view; @@ -9,12 +9,12 @@ uniform mat4 projection; out vec3 Normal; out vec3 FragPos; -out vec2 TexCoords; +out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0) * model * view * projection; FragPos = vec3(vec4(aPos, 1.0) * model); Normal = aNormal * mat3(transpose(inverse(model))); - TexCoords = aTexCoords; + TexCoord = aTexCoord; } \ No newline at end of file diff --git a/Chapter2/4-LightingMaps/Window.cs b/Chapter2/4-LightingMaps/Window.cs index b3479b6..2f8d452 100644 --- a/Chapter2/4-LightingMaps/Window.cs +++ b/Chapter2/4-LightingMaps/Window.cs @@ -1,9 +1,11 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -67,16 +69,16 @@ public class Window : GameWindow private int _vaoLamp; - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; // The texture containing information for the diffuse map, this would more commonly // just be called the color/texture of the object. - private Texture _diffuseMap; + private int _diffuseMap; // The specular map is a black/white representation of how specular each part of the texture is. - private Texture _specularMap; + private int _specularMap; private Camera _camera; @@ -101,25 +103,25 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); { _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); // All of the vertex attributes have been updated to now have a stride of 8 float sizes. - var positionLocation = _lightingShader.GetAttribLocation("aPos"); + var positionLocation = 0; // The location of aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); - var normalLocation = _lightingShader.GetAttribLocation("aNormal"); + var normalLocation = 1; // The location of aNormal GL.EnableVertexAttribArray(normalLocation); GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float)); // The texture coords have now been added too, remember we only have 2 coordinates as the texture is 2d, // so the size parameter should only be 2 for the texture coordinates. - var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords"); + var texCoordLocation = 2; // The location of aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float)); } @@ -130,15 +132,15 @@ protected override void OnLoad() // The lamp shader should have its stride updated aswell, however we dont actually // use the texture coords for the lamp, so we dont need to add any extra attributes. - var positionLocation = _lampShader.GetAttribLocation("aPos"); + var positionLocation = 0; // The location of aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); } // Our two textures are loaded in from memory, you should head over and // check them out and compare them to the results. - _diffuseMap = Texture.LoadFromFile("Resources/container2.png"); - _specularMap = Texture.LoadFromFile("Resources/container2_specular.png"); + _diffuseMap = LoadTextureFromFile("Resources/container2.png"); + _specularMap = LoadTextureFromFile("Resources/container2_specular.png"); _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); @@ -155,40 +157,45 @@ protected override void OnRenderFrame(FrameEventArgs e) // The two textures need to be used, in this case we use the diffuse map as our 0th texture // and the specular map as our 1st texture. - _diffuseMap.Use(TextureUnit.Texture0); - _specularMap.Use(TextureUnit.Texture1); - _lightingShader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _diffuseMap); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _specularMap); - _lightingShader.SetMatrix4("model", Matrix4.Identity); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UseProgram(_lightingShader); - _lightingShader.SetVector3("viewPos", _camera.Position); + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); - // Here we specify to the shaders what textures they should refer to when we want to get the positions. - _lightingShader.SetInt("material.diffuse", 0); - _lightingShader.SetInt("material.specular", 1); - _lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f)); - _lightingShader.SetFloat("material.shininess", 32.0f); + Matrix4 model = Matrix4.Identity; - _lightingShader.SetVector3("light.position", _lightPos); - _lightingShader.SetVector3("light.ambient", new Vector3(0.2f)); - _lightingShader.SetVector3("light.diffuse", new Vector3(0.5f)); - _lightingShader.SetVector3("light.specular", new Vector3(1.0f)); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "viewPos"), _camera.Position); + + // Here we specify to the shaders what textures they should refer to when we want to get the positions. + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.diffuse"), 0); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.specular"), 1); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.shininess"), 32.0f); + + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.position"), _lightPos); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.ambient"), new Vector3(0.2f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.diffuse"), new Vector3(0.5f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.specular"), new Vector3(1.0f)); + GL.DrawArrays(PrimitiveType.Triangles, 0, 36); GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - Matrix4 lampMatrix = Matrix4.Identity; - lampMatrix *= Matrix4.CreateScale(0.2f); - lampMatrix *= Matrix4.CreateTranslation(_lightPos); + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_lightPos); - _lampShader.SetMatrix4("model", lampMatrix); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -264,12 +271,84 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + return handle; + } } } diff --git a/Chapter2/5-LightCasters-DirectionalLights/5-LightCasters-DirectionalLights.csproj b/Chapter2/5-LightCasters-DirectionalLights/5-LightCasters-DirectionalLights.csproj index 182a118..7705f8b 100644 --- a/Chapter2/5-LightCasters-DirectionalLights/5-LightCasters-DirectionalLights.csproj +++ b/Chapter2/5-LightCasters-DirectionalLights/5-LightCasters-DirectionalLights.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/5-LightCasters-DirectionalLights/Camera.cs b/Chapter2/5-LightCasters-DirectionalLights/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/5-LightCasters-DirectionalLights/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/5-LightCasters-DirectionalLights/OpenTK.dll.config b/Chapter2/5-LightCasters-DirectionalLights/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/5-LightCasters-DirectionalLights/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/5-LightCasters-DirectionalLights/Program.cs b/Chapter2/5-LightCasters-DirectionalLights/Program.cs index 15c17bd..7762351 100644 --- a/Chapter2/5-LightCasters-DirectionalLights/Program.cs +++ b/Chapter2/5-LightCasters-DirectionalLights/Program.cs @@ -9,7 +9,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new OpenTK.Mathematics.Vector2i(800, 600), + ClientSize = new OpenTK.Mathematics.Vector2i(800, 600), Title = "LearnOpenTK - Light caster - directional", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/5-LightCasters-DirectionalLights/Shaders/lighting.frag b/Chapter2/5-LightCasters-DirectionalLights/Shaders/lighting.frag index d2cb8bf..2480cab 100644 --- a/Chapter2/5-LightCasters-DirectionalLights/Shaders/lighting.frag +++ b/Chapter2/5-LightCasters-DirectionalLights/Shaders/lighting.frag @@ -21,25 +21,25 @@ uniform vec3 viewPos; out vec4 FragColor; in vec3 Normal; -in vec2 TexCoords; +in vec2 TexCoord; void main() { // ambient - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoord)); // diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(-light.direction);//We still normalize the light direction since we techically dont know, //wether it was normalized for us or not. float diff = max(dot(norm, lightDir), 0.0); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoord)); // specular vec3 viewDir = normalize(viewPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoord)); vec3 result = ambient + diffuse + specular; FragColor = vec4(result, 1.0); diff --git a/Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.vert b/Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.vert index 5604e7b..9a0c35f 100644 --- a/Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.vert +++ b/Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.vert @@ -1,18 +1,18 @@ #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; +layout (location = 2) in vec2 aTexCoord; uniform mat4 model; uniform mat4 view; uniform mat4 projection; out vec3 Normal; -out vec2 TexCoords; +out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0) * model * view * projection; Normal = aNormal * mat3(transpose(inverse(model))); - TexCoords = aTexCoords; + TexCoord = aTexCoord; } \ No newline at end of file diff --git a/Chapter2/5-LightCasters-DirectionalLights/Window.cs b/Chapter2/5-LightCasters-DirectionalLights/Window.cs index 9a75fc0..7a71878 100644 --- a/Chapter2/5-LightCasters-DirectionalLights/Window.cs +++ b/Chapter2/5-LightCasters-DirectionalLights/Window.cs @@ -1,9 +1,11 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -82,13 +84,13 @@ public class Window : GameWindow private int _vaoLamp; - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; - private Texture _diffuseMap; + private int _diffuseMap; - private Texture _specularMap; + private int _specularMap; private Camera _camera; @@ -113,22 +115,22 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + { _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); - var positionLocation = _lightingShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); - var normalLocation = _lightingShader.GetAttribLocation("aNormal"); + var normalLocation = 1; // aNormal GL.EnableVertexAttribArray(normalLocation); GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float)); - var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords"); + var texCoordLocation = 2; // aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float)); } @@ -139,13 +141,13 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); - var positionLocation = _lampShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); } - _diffuseMap = Texture.LoadFromFile("Resources/container2.png"); - _specularMap = Texture.LoadFromFile("Resources/container2_specular.png"); + _diffuseMap = LoadTextureFromFile("Resources/container2.png"); + _specularMap = LoadTextureFromFile("Resources/container2_specular.png"); _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); @@ -160,25 +162,31 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoModel); - _diffuseMap.Use(TextureUnit.Texture0); - _specularMap.Use(TextureUnit.Texture1); - _lightingShader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _diffuseMap); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _specularMap); + + GL.UseProgram(_lightingShader); + + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); - _lightingShader.SetVector3("viewPos", _camera.Position); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "viewPos"), _camera.Position); - _lightingShader.SetInt("material.diffuse", 0); - _lightingShader.SetInt("material.specular", 1); - _lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f)); - _lightingShader.SetFloat("material.shininess", 32.0f); + // Here we specify to the shaders what textures they should refer to when we want to get the positions. + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.diffuse"), 0); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.specular"), 1); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.shininess"), 32.0f); // Directional light needs a direction, in this example we just use (-0.2, -1.0, -0.3f) as the lights direction - _lightingShader.SetVector3("light.direction", new Vector3(-0.2f, -1.0f, -0.3f)); - _lightingShader.SetVector3("light.ambient", new Vector3(0.2f)); - _lightingShader.SetVector3("light.diffuse", new Vector3(0.5f)); - _lightingShader.SetVector3("light.specular", new Vector3(1.0f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.direction"), new Vector3(-0.2f, -1.0f, -0.3f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.ambient"), new Vector3(0.2f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.diffuse"), new Vector3(0.5f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.specular"), new Vector3(1.0f)); // We want to draw all the cubes at their respective positions for (int i = 0; i < _cubePositions.Length; i++) @@ -188,8 +196,8 @@ protected override void OnRenderFrame(FrameEventArgs e) // We then calculate the angle and rotate the model around an axis float angle = 20.0f * i; model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle); - // Remember to set the model at last so it can be used by opentk - _lightingShader.SetMatrix4("model", model); + // Remember to set the model at last so it can be used by opengl + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); // At last we draw all our cubes GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -197,14 +205,13 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); - lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos); + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_lightPos); - _lampShader.SetMatrix4("model", lampMatrix); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -280,12 +287,84 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + return handle; + } } } diff --git a/Chapter2/5-LightCasters-PointLights/5-LightCasters-PointLights.csproj b/Chapter2/5-LightCasters-PointLights/5-LightCasters-PointLights.csproj index 182a118..7705f8b 100644 --- a/Chapter2/5-LightCasters-PointLights/5-LightCasters-PointLights.csproj +++ b/Chapter2/5-LightCasters-PointLights/5-LightCasters-PointLights.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/5-LightCasters-PointLights/Camera.cs b/Chapter2/5-LightCasters-PointLights/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/5-LightCasters-PointLights/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/5-LightCasters-PointLights/OpenTK.dll.config b/Chapter2/5-LightCasters-PointLights/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/5-LightCasters-PointLights/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/5-LightCasters-PointLights/Program.cs b/Chapter2/5-LightCasters-PointLights/Program.cs index c7f68ef..211bcab 100644 --- a/Chapter2/5-LightCasters-PointLights/Program.cs +++ b/Chapter2/5-LightCasters-PointLights/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Light casters - point lights", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/5-LightCasters-PointLights/Shaders/lighting.frag b/Chapter2/5-LightCasters-PointLights/Shaders/lighting.frag index a32b305..e7975a2 100644 --- a/Chapter2/5-LightCasters-PointLights/Shaders/lighting.frag +++ b/Chapter2/5-LightCasters-PointLights/Shaders/lighting.frag @@ -28,25 +28,25 @@ out vec4 FragColor; in vec3 Normal; in vec3 FragPos; -in vec2 TexCoords; +in vec2 TexCoord; void main() { //ambient - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoord)); //diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(norm, lightDir), 0.0); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoord)); //specular vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoord)); //attenuation //The attenuation is the term we use when talking about how dim the light gets over distance diff --git a/Chapter2/5-LightCasters-PointLights/Shaders/shader.vert b/Chapter2/5-LightCasters-PointLights/Shaders/shader.vert index af12da3..4fa340c 100644 --- a/Chapter2/5-LightCasters-PointLights/Shaders/shader.vert +++ b/Chapter2/5-LightCasters-PointLights/Shaders/shader.vert @@ -1,7 +1,7 @@ #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; +layout (location = 2) in vec2 aTexCoord; uniform mat4 model; uniform mat4 view; @@ -9,12 +9,12 @@ uniform mat4 projection; out vec3 Normal; out vec3 FragPos; -out vec2 TexCoords; +out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0) * model * view * projection; FragPos = vec3(vec4(aPos, 1.0) * model); Normal = aNormal * mat3(transpose(inverse(model))); - TexCoords = aTexCoords; + TexCoord = aTexCoord; } \ No newline at end of file diff --git a/Chapter2/5-LightCasters-PointLights/Window.cs b/Chapter2/5-LightCasters-PointLights/Window.cs index a40b7ea..295d5ad 100644 --- a/Chapter2/5-LightCasters-PointLights/Window.cs +++ b/Chapter2/5-LightCasters-PointLights/Window.cs @@ -1,9 +1,11 @@ -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -83,13 +85,13 @@ public class Window : GameWindow private int _vaoLamp; - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; - private Texture _diffuseMap; + private int _diffuseMap; - private Texture _specularMap; + private int _specularMap; private Camera _camera; @@ -114,22 +116,22 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + { _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); - var positionLocation = _lightingShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); - var normalLocation = _lightingShader.GetAttribLocation("aNormal"); + var normalLocation = 1; // aNormal GL.EnableVertexAttribArray(normalLocation); GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float)); - var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords"); + var texCoordLocation = 2; // aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float)); } @@ -138,13 +140,13 @@ protected override void OnLoad() _vaoLamp = GL.GenVertexArray(); GL.BindVertexArray(_vaoLamp); - var positionLocation = _lampShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); } - _diffuseMap = Texture.LoadFromFile("Resources/container2.png"); - _specularMap = Texture.LoadFromFile("Resources/container2_specular.png"); + _diffuseMap = LoadTextureFromFile("Resources/container2.png"); + _specularMap = LoadTextureFromFile("Resources/container2_specular.png"); _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); @@ -159,27 +161,34 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoModel); - _diffuseMap.Use(TextureUnit.Texture0); - _specularMap.Use(TextureUnit.Texture1); - _lightingShader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _diffuseMap); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _specularMap); + + GL.UseProgram(_lightingShader); + + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); - _lightingShader.SetVector3("viewPos", _camera.Position); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "viewPos"), _camera.Position); - _lightingShader.SetInt("material.diffuse", 0); - _lightingShader.SetInt("material.specular", 1); - _lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f)); - _lightingShader.SetFloat("material.shininess", 32.0f); + // Here we specify to the shaders what textures they should refer to when we want to get the positions. + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.diffuse"), 0); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.specular"), 1); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.shininess"), 32.0f); - _lightingShader.SetVector3("light.position", _lightPos); - _lightingShader.SetFloat("light.constant", 1.0f); - _lightingShader.SetFloat("light.linear", 0.09f); - _lightingShader.SetFloat("light.quadratic", 0.032f); - _lightingShader.SetVector3("light.ambient", new Vector3(0.2f)); - _lightingShader.SetVector3("light.diffuse", new Vector3(0.5f)); - _lightingShader.SetVector3("light.specular", new Vector3(1.0f)); + // Directional light needs a direction, in this example we just use (-0.2, -1.0, -0.3f) as the lights direction + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.position"), _lightPos); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.constant"), 1.0f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.linear"), 0.09f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.quadratic"), 0.032f); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.ambient"), new Vector3(0.2f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.diffuse"), new Vector3(0.5f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.specular"), new Vector3(1.0f)); // We want to draw all the cubes at their respective positions for (int i = 0; i < _cubePositions.Length; i++) @@ -191,7 +200,7 @@ protected override void OnRenderFrame(FrameEventArgs e) float angle = 20.0f * i; model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle); // Remember to set the model at last so it can be used by opentk - _lightingShader.SetMatrix4("model", model); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); // At last we draw all our cubes GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -199,14 +208,13 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); - lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos); + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_lightPos); - _lampShader.SetMatrix4("model", lampMatrix); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -282,12 +290,84 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + return handle; + } } } diff --git a/Chapter2/5-LightCasters-Spotlight/5-LightCasters-Spotlight.csproj b/Chapter2/5-LightCasters-Spotlight/5-LightCasters-Spotlight.csproj index 182a118..7705f8b 100644 --- a/Chapter2/5-LightCasters-Spotlight/5-LightCasters-Spotlight.csproj +++ b/Chapter2/5-LightCasters-Spotlight/5-LightCasters-Spotlight.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/5-LightCasters-Spotlight/Camera.cs b/Chapter2/5-LightCasters-Spotlight/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/5-LightCasters-Spotlight/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/5-LightCasters-Spotlight/OpenTK.dll.config b/Chapter2/5-LightCasters-Spotlight/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/5-LightCasters-Spotlight/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/5-LightCasters-Spotlight/Program.cs b/Chapter2/5-LightCasters-Spotlight/Program.cs index d8ba86c..21cfdfc 100644 --- a/Chapter2/5-LightCasters-Spotlight/Program.cs +++ b/Chapter2/5-LightCasters-Spotlight/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Light casters - spotlight", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/5-LightCasters-Spotlight/Shaders/lighting.frag b/Chapter2/5-LightCasters-Spotlight/Shaders/lighting.frag index 78abfaf..a421a32 100644 --- a/Chapter2/5-LightCasters-Spotlight/Shaders/lighting.frag +++ b/Chapter2/5-LightCasters-Spotlight/Shaders/lighting.frag @@ -29,24 +29,24 @@ out vec4 FragColor; in vec3 Normal; in vec3 FragPos; -in vec2 TexCoords; +in vec2 TexCoord; void main() { //ambient - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoord)); //diffuse vec3 norm = normalize(Normal); vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(norm, lightDir), 0.0); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoord)); //specular vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoord)); //attenuation float distance = length(light.position - FragPos); diff --git a/Chapter2/5-LightCasters-Spotlight/Shaders/shader.vert b/Chapter2/5-LightCasters-Spotlight/Shaders/shader.vert index af12da3..4fa340c 100644 --- a/Chapter2/5-LightCasters-Spotlight/Shaders/shader.vert +++ b/Chapter2/5-LightCasters-Spotlight/Shaders/shader.vert @@ -1,7 +1,7 @@ #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; +layout (location = 2) in vec2 aTexCoord; uniform mat4 model; uniform mat4 view; @@ -9,12 +9,12 @@ uniform mat4 projection; out vec3 Normal; out vec3 FragPos; -out vec2 TexCoords; +out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0) * model * view * projection; FragPos = vec3(vec4(aPos, 1.0) * model); Normal = aNormal * mat3(transpose(inverse(model))); - TexCoords = aTexCoords; + TexCoord = aTexCoord; } \ No newline at end of file diff --git a/Chapter2/5-LightCasters-Spotlight/Window.cs b/Chapter2/5-LightCasters-Spotlight/Window.cs index 792373c..9782fb9 100644 --- a/Chapter2/5-LightCasters-Spotlight/Window.cs +++ b/Chapter2/5-LightCasters-Spotlight/Window.cs @@ -1,10 +1,11 @@ -using System; -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -85,13 +86,13 @@ public class Window : GameWindow private int _vaoLamp; - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; - private Texture _diffuseMap; + private int _diffuseMap; - private Texture _specularMap; + private int _specularMap; private Camera _camera; @@ -116,22 +117,22 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + { _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); - var positionLocation = _lightingShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); - var normalLocation = _lightingShader.GetAttribLocation("aNormal"); + var normalLocation = 1; // aNormal GL.EnableVertexAttribArray(normalLocation); GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float)); - var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords"); + var texCoordLocation = 2; // aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float)); } @@ -140,13 +141,13 @@ protected override void OnLoad() _vaoLamp = GL.GenVertexArray(); GL.BindVertexArray(_vaoLamp); - var positionLocation = _lampShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); } - _diffuseMap = Texture.LoadFromFile("Resources/container2.png"); - _specularMap = Texture.LoadFromFile("Resources/container2_specular.png"); + _diffuseMap = LoadTextureFromFile("Resources/container2.png"); + _specularMap = LoadTextureFromFile("Resources/container2_specular.png"); _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); @@ -161,30 +162,37 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoModel); - _diffuseMap.Use(TextureUnit.Texture0); - _specularMap.Use(TextureUnit.Texture1); - _lightingShader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _diffuseMap); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _specularMap); + + GL.UseProgram(_lightingShader); + + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); - _lightingShader.SetVector3("viewPos", _camera.Position); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "viewPos"), _camera.Position); - _lightingShader.SetInt("material.diffuse", 0); - _lightingShader.SetInt("material.specular", 1); - _lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f)); - _lightingShader.SetFloat("material.shininess", 32.0f); + // Here we specify to the shaders what textures they should refer to when we want to get the positions. + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.diffuse"), 0); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.specular"), 1); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.shininess"), 32.0f); - _lightingShader.SetVector3("light.position", _camera.Position); - _lightingShader.SetVector3("light.direction", _camera.Front); - _lightingShader.SetFloat("light.cutOff", MathF.Cos(MathHelper.DegreesToRadians(12.5f))); - _lightingShader.SetFloat("light.outerCutOff", MathF.Cos(MathHelper.DegreesToRadians(17.5f))); - _lightingShader.SetFloat("light.constant", 1.0f); - _lightingShader.SetFloat("light.linear", 0.09f); - _lightingShader.SetFloat("light.quadratic", 0.032f); - _lightingShader.SetVector3("light.ambient", new Vector3(0.2f)); - _lightingShader.SetVector3("light.diffuse", new Vector3(0.5f)); - _lightingShader.SetVector3("light.specular", new Vector3(1.0f)); + // Directional light needs a direction, in this example we just use (-0.2, -1.0, -0.3f) as the lights direction + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.position"), _camera.Position); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.direction"), _camera.Front); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.cutOff"), MathF.Cos(MathHelper.DegreesToRadians(12.5f))); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.outerCutOff"), MathF.Cos(MathHelper.DegreesToRadians(17.5f))); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.constant"), 1.0f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.linear"), 0.09f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "light.quadratic"), 0.032f); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.ambient"), new Vector3(0.2f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.diffuse"), new Vector3(0.5f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "light.specular"), new Vector3(1.0f)); // We want to draw all the cubes at their respective positions for (int i = 0; i < _cubePositions.Length; i++) @@ -195,7 +203,7 @@ protected override void OnRenderFrame(FrameEventArgs e) float angle = 20.0f * i; model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle); // Remember to set the model at last so it can be used by opentk - _lightingShader.SetMatrix4("model", model); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); // At last we draw all our cubes GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -203,14 +211,13 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); - lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos); + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_lightPos); - _lampShader.SetMatrix4("model", lampMatrix); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); @@ -286,12 +293,84 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + return handle; + } } } diff --git a/Chapter2/6-MultipleLights/6-MultipleLights.csproj b/Chapter2/6-MultipleLights/6-MultipleLights.csproj index 182a118..7705f8b 100644 --- a/Chapter2/6-MultipleLights/6-MultipleLights.csproj +++ b/Chapter2/6-MultipleLights/6-MultipleLights.csproj @@ -3,7 +3,7 @@ WinExe LearnOpenTK LearnOpenTK - netcoreapp3.1 + net8.0 @@ -12,10 +12,7 @@ - - - - - + + \ No newline at end of file diff --git a/Chapter2/6-MultipleLights/Camera.cs b/Chapter2/6-MultipleLights/Camera.cs new file mode 100644 index 0000000..15b64b8 --- /dev/null +++ b/Chapter2/6-MultipleLights/Camera.cs @@ -0,0 +1,119 @@ +using OpenTK.Mathematics; +using System; + +namespace LearnOpenTK +{ + // This is the camera class as it could be set up after the tutorials on the website. + // It is important to note there are a few ways you could have set up this camera. + // For example, you could have also managed the player input inside the camera class, + // and a lot of the properties could have been made into functions. + + // TL;DR: This is just one of many ways in which we could have set up the camera. + // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code. + public class Camera + { + // Those vectors are directions pointing outwards from the camera to define how it rotated. + private Vector3 _front = -Vector3.UnitZ; + + private Vector3 _up = Vector3.UnitY; + + private Vector3 _right = Vector3.UnitX; + + // Rotation around the X axis (radians) + private float _pitch; + + // Rotation around the Y axis (radians) + private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. + + // The field of view of the camera (radians) + private float _fov = MathHelper.PiOver2; + + public Camera(Vector3 position, float aspectRatio) + { + Position = position; + AspectRatio = aspectRatio; + } + + // The position of the camera + public Vector3 Position { get; set; } + + // This is simply the aspect ratio of the viewport, used for the projection matrix. + public float AspectRatio { private get; set; } + + public Vector3 Front => _front; + + public Vector3 Up => _up; + + public Vector3 Right => _right; + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Pitch + { + get => MathHelper.RadiansToDegrees(_pitch); + set + { + // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch + // of weird "bugs" when you are using euler angles for rotation. + // If you want to read more about this you can try researching a topic called gimbal lock + var angle = MathHelper.Clamp(value, -89f, 89f); + _pitch = MathHelper.DegreesToRadians(angle); + UpdateVectors(); + } + } + + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Yaw + { + get => MathHelper.RadiansToDegrees(_yaw); + set + { + _yaw = MathHelper.DegreesToRadians(value); + UpdateVectors(); + } + } + + // The field of view (FOV) is the vertical angle of the camera view. + // This has been discussed more in depth in a previous tutorial, + // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. + // We convert from degrees to radians as soon as the property is set to improve performance. + public float Fov + { + get => MathHelper.RadiansToDegrees(_fov); + set + { + var angle = MathHelper.Clamp(value, 1f, 90f); + _fov = MathHelper.DegreesToRadians(angle); + } + } + + // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials + public Matrix4 GetViewMatrix() + { + return Matrix4.LookAt(Position, Position + _front, _up); + } + + // Get the projection matrix using the same method we have used up until this point + public Matrix4 GetProjectionMatrix() + { + return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); + } + + // This function is going to update the direction vertices using some of the math learned in the web tutorials. + private void UpdateVectors() + { + // First, the front matrix is calculated using some basic trigonometry. + _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); + _front.Y = MathF.Sin(_pitch); + _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); + + // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. + _front = Vector3.Normalize(_front); + + // Calculate both the right and the up vector using cross product. + // Note that we are calculating the right from the global up; this behaviour might + // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. + _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); + _up = Vector3.Normalize(Vector3.Cross(_right, _front)); + } + } +} \ No newline at end of file diff --git a/Chapter2/6-MultipleLights/OpenTK.dll.config b/Chapter2/6-MultipleLights/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/Chapter2/6-MultipleLights/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Chapter2/6-MultipleLights/Program.cs b/Chapter2/6-MultipleLights/Program.cs index 6c488e7..691277b 100644 --- a/Chapter2/6-MultipleLights/Program.cs +++ b/Chapter2/6-MultipleLights/Program.cs @@ -10,7 +10,7 @@ private static void Main() { var nativeWindowSettings = new NativeWindowSettings() { - Size = new Vector2i(800, 600), + ClientSize = new Vector2i(800, 600), Title = "LearnOpenTK - Multiple lights", // This is needed to run on macos Flags = ContextFlags.ForwardCompatible, diff --git a/Chapter2/6-MultipleLights/Shaders/lighting.frag b/Chapter2/6-MultipleLights/Shaders/lighting.frag index b68142b..5746618 100644 --- a/Chapter2/6-MultipleLights/Shaders/lighting.frag +++ b/Chapter2/6-MultipleLights/Shaders/lighting.frag @@ -54,7 +54,7 @@ out vec4 FragColor; in vec3 Normal; in vec3 FragPos; -in vec2 TexCoords; +in vec2 TexCoord; //Here we have some function prototypes, these are the signatures the gpu will use to know how the //parameters of each light calculation is layed out. @@ -89,9 +89,9 @@ vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); //combine results - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoord)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoord)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoord)); return (ambient + diffuse + specular); } @@ -108,9 +108,9 @@ vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); //combine results - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoord)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoord)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoord)); ambient *= attenuation; diffuse *= attenuation; specular *= attenuation; @@ -138,9 +138,9 @@ vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); //combine results - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoord)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoord)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoord)); ambient *= attenuation; diffuse *= attenuation * intensity; specular *= attenuation * intensity; diff --git a/Chapter2/6-MultipleLights/Shaders/shader.vert b/Chapter2/6-MultipleLights/Shaders/shader.vert index af12da3..4fa340c 100644 --- a/Chapter2/6-MultipleLights/Shaders/shader.vert +++ b/Chapter2/6-MultipleLights/Shaders/shader.vert @@ -1,7 +1,7 @@ #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; +layout (location = 2) in vec2 aTexCoord; uniform mat4 model; uniform mat4 view; @@ -9,12 +9,12 @@ uniform mat4 projection; out vec3 Normal; out vec3 FragPos; -out vec2 TexCoords; +out vec2 TexCoord; void main() { gl_Position = vec4(aPos, 1.0) * model * view * projection; FragPos = vec3(vec4(aPos, 1.0) * model); Normal = aNormal * mat3(transpose(inverse(model))); - TexCoords = aTexCoords; + TexCoord = aTexCoord; } \ No newline at end of file diff --git a/Chapter2/6-MultipleLights/Window.cs b/Chapter2/6-MultipleLights/Window.cs index 4b64d2c..03cc0dd 100644 --- a/Chapter2/6-MultipleLights/Window.cs +++ b/Chapter2/6-MultipleLights/Window.cs @@ -1,10 +1,11 @@ -using System; -using LearnOpenTK.Common; -using OpenTK.Graphics.OpenGL4; +using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; -using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using StbImageSharp; +using System; +using System.IO; namespace LearnOpenTK { @@ -87,13 +88,13 @@ public class Window : GameWindow private int _vaoLamp; - private Shader _lampShader; + private int _lampShader; - private Shader _lightingShader; + private int _lightingShader; - private Texture _diffuseMap; + private int _diffuseMap; - private Texture _specularMap; + private int _specularMap; private Camera _camera; @@ -118,22 +119,22 @@ protected override void OnLoad() GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); - _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); - _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); - + _lightingShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/lighting.frag")); + _lampShader = CompileProgram(File.ReadAllText("Shaders/shader.vert"), File.ReadAllText("Shaders/shader.frag")); + { _vaoModel = GL.GenVertexArray(); GL.BindVertexArray(_vaoModel); - var positionLocation = _lightingShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); - var normalLocation = _lightingShader.GetAttribLocation("aNormal"); + var normalLocation = 1; // aNormal GL.EnableVertexAttribArray(normalLocation); GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float)); - var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords"); + var texCoordLocation = 2; // aTexCoord GL.EnableVertexAttribArray(texCoordLocation); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float)); } @@ -142,13 +143,13 @@ protected override void OnLoad() _vaoLamp = GL.GenVertexArray(); GL.BindVertexArray(_vaoLamp); - var positionLocation = _lampShader.GetAttribLocation("aPos"); + var positionLocation = 0; // aPos GL.EnableVertexAttribArray(positionLocation); GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); } - _diffuseMap = Texture.LoadFromFile("Resources/container2.png"); - _specularMap = Texture.LoadFromFile("Resources/container2_specular.png"); + _diffuseMap = LoadTextureFromFile("Resources/container2.png"); + _specularMap = LoadTextureFromFile("Resources/container2_specular.png"); _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); @@ -163,19 +164,23 @@ protected override void OnRenderFrame(FrameEventArgs e) GL.BindVertexArray(_vaoModel); - _diffuseMap.Use(TextureUnit.Texture0); - _specularMap.Use(TextureUnit.Texture1); - _lightingShader.Use(); + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, _diffuseMap); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, _specularMap); + GL.UseProgram(_lightingShader); - _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + Matrix4 projection = _camera.GetProjectionMatrix(); + Matrix4 view = _camera.GetViewMatrix(); - _lightingShader.SetVector3("viewPos", _camera.Position); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "projection"), true, ref projection); - _lightingShader.SetInt("material.diffuse", 0); - _lightingShader.SetInt("material.specular", 1); - _lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f)); - _lightingShader.SetFloat("material.shininess", 32.0f); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "viewPos"), _camera.Position); + + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.diffuse"), 0); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.specular"), 1); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "material.shininess"), 32.0f); /* Here we set all the uniforms for the 5/6 types of lights we have. We have to set them manually and index @@ -184,58 +189,57 @@ the proper PointLight struct in the array to set each uniform variable. This can by using 'Uniform buffer objects', but that is something we'll discuss in the 'Advanced GLSL' tutorial. */ // Directional light - _lightingShader.SetVector3("dirLight.direction", new Vector3(-0.2f, -1.0f, -0.3f)); - _lightingShader.SetVector3("dirLight.ambient", new Vector3(0.05f, 0.05f, 0.05f)); - _lightingShader.SetVector3("dirLight.diffuse", new Vector3(0.4f, 0.4f, 0.4f)); - _lightingShader.SetVector3("dirLight.specular", new Vector3(0.5f, 0.5f, 0.5f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "dirLight.direction"), new Vector3(-0.2f, -1.0f, -0.3f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "dirLight.ambient"), new Vector3(0.05f, 0.05f, 0.05f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "dirLight.diffuse"), new Vector3(0.4f, 0.4f, 0.4f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "dirLight.specular"), new Vector3(0.5f, 0.5f, 0.5f)); // Point lights for (int i = 0; i < _pointLightPositions.Length; i++) { - _lightingShader.SetVector3($"pointLights[{i}].position", _pointLightPositions[i]); - _lightingShader.SetVector3($"pointLights[{i}].ambient", new Vector3(0.05f, 0.05f, 0.05f)); - _lightingShader.SetVector3($"pointLights[{i}].diffuse", new Vector3(0.8f, 0.8f, 0.8f)); - _lightingShader.SetVector3($"pointLights[{i}].specular", new Vector3(1.0f, 1.0f, 1.0f)); - _lightingShader.SetFloat($"pointLights[{i}].constant", 1.0f); - _lightingShader.SetFloat($"pointLights[{i}].linear", 0.09f); - _lightingShader.SetFloat($"pointLights[{i}].quadratic", 0.032f); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, $"pointLights[{i}].position"), _pointLightPositions[i]); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, $"pointLights[{i}].ambient"), new Vector3(0.05f, 0.05f, 0.05f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, $"pointLights[{i}].diffuse"), new Vector3(0.8f, 0.8f, 0.8f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, $"pointLights[{i}].specular"), new Vector3(1.0f, 1.0f, 1.0f)); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, $"pointLights[{i}].constant"), 1.0f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, $"pointLights[{i}].linear"), 0.09f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, $"pointLights[{i}].quadratic"), 0.032f); } // Spot light - _lightingShader.SetVector3("spotLight.position", _camera.Position); - _lightingShader.SetVector3("spotLight.direction", _camera.Front); - _lightingShader.SetVector3("spotLight.ambient", new Vector3(0.0f, 0.0f, 0.0f)); - _lightingShader.SetVector3("spotLight.diffuse", new Vector3(1.0f, 1.0f, 1.0f)); - _lightingShader.SetVector3("spotLight.specular", new Vector3(1.0f, 1.0f, 1.0f)); - _lightingShader.SetFloat("spotLight.constant", 1.0f); - _lightingShader.SetFloat("spotLight.linear", 0.09f); - _lightingShader.SetFloat("spotLight.quadratic", 0.032f); - _lightingShader.SetFloat("spotLight.cutOff", MathF.Cos(MathHelper.DegreesToRadians(12.5f))); - _lightingShader.SetFloat("spotLight.outerCutOff", MathF.Cos(MathHelper.DegreesToRadians(17.5f))); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "spotLight.position"), _camera.Position); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "spotLight.direction"), _camera.Front); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "spotLight.ambient"), new Vector3(0.0f, 0.0f, 0.0f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "spotLight.diffuse"), new Vector3(1.0f, 1.0f, 1.0f)); + GL.Uniform3(GL.GetUniformLocation(_lightingShader, "spotLight.specular"), new Vector3(1.0f, 1.0f, 1.0f)); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "spotLight.constant"), 1.0f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "spotLight.linear"), 0.09f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "spotLight.quadratic"), 0.032f); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "spotLight.cutOff"), MathF.Cos(MathHelper.DegreesToRadians(12.5f))); + GL.Uniform1(GL.GetUniformLocation(_lightingShader, "spotLight.outerCutOff"), MathF.Cos(MathHelper.DegreesToRadians(17.5f))); for (int i = 0; i < _cubePositions.Length; i++) { Matrix4 model = Matrix4.CreateTranslation(_cubePositions[i]); float angle = 20.0f * i; model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle); - _lightingShader.SetMatrix4("model", model); + GL.UniformMatrix4(GL.GetUniformLocation(_lightingShader, "model"), true, ref model); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); } GL.BindVertexArray(_vaoLamp); - _lampShader.Use(); + GL.UseProgram(_lampShader); - _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); - _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "view"), true, ref view); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "projection"), true, ref projection); // We use a loop to draw all the lights at the proper position for (int i = 0; i < _pointLightPositions.Length; i++) { - Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); - lampMatrix = lampMatrix * Matrix4.CreateTranslation(_pointLightPositions[i]); + Matrix4 lampMatrix = Matrix4.CreateScale(0.2f) * Matrix4.CreateTranslation(_pointLightPositions[i]); - _lampShader.SetMatrix4("model", lampMatrix); + GL.UniformMatrix4(GL.GetUniformLocation(_lampShader, "model"), true, ref lampMatrix); GL.DrawArrays(PrimitiveType.Triangles, 0, 36); } @@ -312,12 +316,84 @@ protected override void OnMouseWheel(MouseWheelEventArgs e) _camera.Fov -= e.OffsetY; } - protected override void OnResize(ResizeEventArgs e) + protected override void OnFramebufferResize(FramebufferResizeEventArgs e) { - base.OnResize(e); + base.OnFramebufferResize(e); + + GL.Viewport(0, 0, e.Width, e.Height); - GL.Viewport(0, 0, Size.X, Size.Y); _camera.AspectRatio = Size.X / (float)Size.Y; } + + private static int CompileProgram(string vertexSource, string fragmentSource) + { + int vertexShader = CompileShader(ShaderType.VertexShader, vertexSource); + int fragmentShader = CompileShader(ShaderType.FragmentShader, fragmentSource); + + int handle = GL.CreateProgram(); + + GL.AttachShader(handle, vertexShader); + GL.AttachShader(handle, fragmentShader); + + GL.LinkProgram(handle); + + GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out var code); + if (code != (int)All.True) + { + string infoLog = GL.GetProgramInfoLog(handle); + throw new Exception($"Error occurred whilst linking Program({handle}):\n{infoLog}"); + } + + GL.DetachShader(handle, vertexShader); + GL.DetachShader(handle, fragmentShader); + GL.DeleteShader(fragmentShader); + GL.DeleteShader(vertexShader); + + return handle; + } + + private static int CompileShader(ShaderType type, string source) + { + int shader = GL.CreateShader(type); + + GL.ShaderSource(shader, source); + + GL.CompileShader(shader); + + GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); + if (code != (int)All.True) + { + var infoLog = GL.GetShaderInfoLog(shader); + throw new Exception($"Error occurred whilst compiling Shader({shader}):\n{infoLog}"); + } + + return shader; + } + + public static int LoadTextureFromFile(string path) + { + int handle = GL.GenTexture(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, handle); + + StbImage.stbi_set_flip_vertically_on_load(1); + + using (Stream stream = File.OpenRead(path)) + { + ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); + } + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + + return handle; + } } } diff --git a/Common/Common.csproj b/Common/Common.csproj deleted file mode 100644 index 14df6c8..0000000 --- a/Common/Common.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - LearnOpenTK.Common - LearnOpenTK.Common - netcoreapp3.1 - - - - - - - diff --git a/Common/Shader.cs b/Common/Shader.cs deleted file mode 100644 index b1e6b8b..0000000 --- a/Common/Shader.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Collections.Generic; -using OpenTK.Graphics.OpenGL4; -using OpenTK.Mathematics; - -namespace LearnOpenTK.Common -{ - // A simple class meant to help create shaders. - public class Shader - { - public readonly int Handle; - - private readonly Dictionary _uniformLocations; - - // This is how you create a simple shader. - // Shaders are written in GLSL, which is a language very similar to C in its semantics. - // The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on. - // A commented example of GLSL can be found in shader.vert. - public Shader(string vertPath, string fragPath) - { - // There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders. - // The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader. - // The vertex shader won't be too important here, but they'll be more important later. - // The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel. - // The fragment shader is what we'll be using the most here. - - // Load vertex shader and compile - var shaderSource = File.ReadAllText(vertPath); - - // GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created. - var vertexShader = GL.CreateShader(ShaderType.VertexShader); - - // Now, bind the GLSL source code - GL.ShaderSource(vertexShader, shaderSource); - - // And then compile - CompileShader(vertexShader); - - // We do the same for the fragment shader. - shaderSource = File.ReadAllText(fragPath); - var fragmentShader = GL.CreateShader(ShaderType.FragmentShader); - GL.ShaderSource(fragmentShader, shaderSource); - CompileShader(fragmentShader); - - // These two shaders must then be merged into a shader program, which can then be used by OpenGL. - // To do this, create a program... - Handle = GL.CreateProgram(); - - // Attach both shaders... - GL.AttachShader(Handle, vertexShader); - GL.AttachShader(Handle, fragmentShader); - - // And then link them together. - LinkProgram(Handle); - - // When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program. - // Detach them, and then delete them. - GL.DetachShader(Handle, vertexShader); - GL.DetachShader(Handle, fragmentShader); - GL.DeleteShader(fragmentShader); - GL.DeleteShader(vertexShader); - - // The shader is now ready to go, but first, we're going to cache all the shader uniform locations. - // Querying this from the shader is very slow, so we do it once on initialization and reuse those values - // later. - - // 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(); - - // Loop over all the uniforms, - 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); - } - } - - private static void CompileShader(int shader) - { - // Try to compile the shader - GL.CompileShader(shader); - - // Check for compilation errors - GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); - if (code != (int)All.True) - { - // We can use `GL.GetShaderInfoLog(shader)` to get information about the error. - var infoLog = GL.GetShaderInfoLog(shader); - throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}"); - } - } - - private static void LinkProgram(int program) - { - // We link the program - GL.LinkProgram(program); - - // Check for linking errors - GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code); - if (code != (int)All.True) - { - // We can use `GL.GetProgramInfoLog(program)` to get information about the error. - throw new Exception($"Error occurred whilst linking Program({program})"); - } - } - - // A wrapper function that enables the shader program. - public void Use() - { - GL.UseProgram(Handle); - } - - // The shader sources provided with this project use hardcoded layout(location)-s. If you want to do it dynamically, - // you can omit the layout(location=X) lines in the vertex shader, and use this in VertexAttribPointer instead of the hardcoded values. - public int GetAttribLocation(string attribName) - { - return GL.GetAttribLocation(Handle, attribName); - } - - // Uniform setters - // Uniforms are variables that can be set by user code, instead of reading them from the VBO. - // You use VBOs for vertex-related data, and uniforms for almost everything else. - - // Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method: - // 1. Bind the program you want to set the uniform on - // 2. Get a handle to the location of the uniform with GL.GetUniformLocation. - // 3. Use the appropriate GL.Uniform* function to set the uniform. - - /// - /// Set a uniform int on this shader. - /// - /// The name of the uniform - /// The data to set - public void SetInt(string name, int data) - { - GL.UseProgram(Handle); - GL.Uniform1(_uniformLocations[name], data); - } - - /// - /// Set a uniform float on this shader. - /// - /// The name of the uniform - /// The data to set - public void SetFloat(string name, float data) - { - GL.UseProgram(Handle); - GL.Uniform1(_uniformLocations[name], 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 SetMatrix4(string name, Matrix4 data) - { - GL.UseProgram(Handle); - GL.UniformMatrix4(_uniformLocations[name], true, ref data); - } - - /// - /// Set a uniform Vector3 on this shader. - /// - /// The name of the uniform - /// The data to set - public void SetVector3(string name, Vector3 data) - { - GL.UseProgram(Handle); - GL.Uniform3(_uniformLocations[name], data); - } - } -} diff --git a/Common/Texture.cs b/Common/Texture.cs deleted file mode 100755 index b9bde62..0000000 --- a/Common/Texture.cs +++ /dev/null @@ -1,91 +0,0 @@ -using OpenTK.Graphics.OpenGL4; -using System.Drawing; -using System.Drawing.Imaging; -using PixelFormat = OpenTK.Graphics.OpenGL4.PixelFormat; -using StbImageSharp; -using System.IO; - -namespace LearnOpenTK.Common -{ - // A helper class, much like Shader, meant to simplify loading textures. - public class Texture - { - public readonly int Handle; - - public static Texture LoadFromFile(string path) - { - // Generate handle - int handle = GL.GenTexture(); - - // Bind the handle - GL.ActiveTexture(TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, handle); - - // For this example, we're going to use .NET's built-in System.Drawing library to load textures. - - // OpenGL has it's texture origin in the lower left corner instead of the top left corner, - // so we tell StbImageSharp to flip the image when loading. - StbImage.stbi_set_flip_vertically_on_load(1); - - // Here we open a stream to the file and pass it to StbImageSharp to load. - using (Stream stream = File.OpenRead(path)) - { - ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); - - // Now that our pixels are prepared, it's time to generate a texture. We do this with GL.TexImage2D. - // Arguments: - // The type of texture we're generating. There are various different types of textures, but the only one we need right now is Texture2D. - // Level of detail. We can use this to start from a smaller mipmap (if we want), but we don't need to do that, so leave it at 0. - // Target format of the pixels. This is the format OpenGL will store our image with. - // Width of the image - // Height of the image. - // Border of the image. This must always be 0; it's a legacy parameter that Khronos never got rid of. - // The format of the pixels, explained above. Since we loaded the pixels as RGBA earlier, we need to use PixelFormat.Rgba. - // Data type of the pixels. - // And finally, the actual pixels. - GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); - } - - // Now that our texture is loaded, we can set a few settings to affect how the image appears on rendering. - - // First, we set the min and mag filter. These are used for when the texture is scaled down and up, respectively. - // Here, we use Linear for both. This means that OpenGL will try to blend pixels, meaning that textures scaled too far will look blurred. - // You could also use (amongst other options) Nearest, which just grabs the nearest pixel, which makes the texture look pixelated if scaled too far. - // NOTE: The default settings for both of these are LinearMipmap. If you leave these as default but don't generate mipmaps, - // your image will fail to render at all (usually resulting in pure black instead). - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); - - // Now, set the wrapping mode. S is for the X axis, and T is for the Y axis. - // We set this to Repeat so that textures will repeat when wrapped. Not demonstrated here since the texture coordinates exactly match - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); - - // Next, generate mipmaps. - // Mipmaps are smaller copies of the texture, scaled down. Each mipmap level is half the size of the previous one - // Generated mipmaps go all the way down to just one pixel. - // OpenGL will automatically switch between mipmaps when an object gets sufficiently far away. - // This prevents moiré effects, as well as saving on texture bandwidth. - // Here you can see and read about the morié effect https://en.wikipedia.org/wiki/Moir%C3%A9_pattern - // Here is an example of mips in action https://en.wikipedia.org/wiki/File:Mipmap_Aliasing_Comparison.png - GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - - return new Texture(handle); - } - - public Texture(int glHandle) - { - Handle = glHandle; - } - - // Activate texture - // Multiple textures can be bound, if your shader needs more than just one. - // If you want to do that, use GL.ActiveTexture to set which slot GL.BindTexture binds to. - // The OpenGL standard requires that there be at least 16, but there can be more depending on your graphics card. - public void Use(TextureUnit unit) - { - GL.ActiveTexture(unit); - GL.BindTexture(TextureTarget.Texture2D, Handle); - } - } -} diff --git a/LearnOpenTK.sln b/LearnOpenTK.sln index c22f114..ba4771b 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.14.36408.4 d17.14 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 @@ -19,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "8-CoordinatesSystems", "Cha EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "9-Camera", "Chapter1\9-Camera\9-Camera.csproj", "{F2E87EBD-BA5C-4A22-820F-99B492B39BCB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chapter1", "Chapter1", "{F7676E7B-4F14-48D9-8985-3FA73BF633BD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chapter2", "Chapter2", "{F66727D5-726B-41DF-8318-6E12733B278F}" @@ -85,10 +83,6 @@ Global {F2E87EBD-BA5C-4A22-820F-99B492B39BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2E87EBD-BA5C-4A22-820F-99B492B39BCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {F2E87EBD-BA5C-4A22-820F-99B492B39BCB}.Release|Any CPU.Build.0 = Release|Any CPU - {2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Release|Any CPU.Build.0 = Release|Any CPU {F363CEB6-1E2C-4114-AA8C-85452472F82B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F363CEB6-1E2C-4114-AA8C-85452472F82B}.Debug|Any CPU.Build.0 = Debug|Any CPU {F363CEB6-1E2C-4114-AA8C-85452472F82B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -161,7 +155,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2A8747DC-91DB-4ECB-AB4A-5504F9D715D9} EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {2A8747DC-91DB-4ECB-AB4A-5504F9D715D9} - EndGlobalSection EndGlobal