├── .gitattributes ├── .gitignore ├── Chapter1 ├── 1-CreatingAWindow │ ├── 1-CreatingAWindow.csproj │ ├── Program.cs │ └── Window.cs ├── 2-HelloTriangle │ ├── 2-HelloTriangle.csproj │ ├── Program.cs │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 3-ElementBufferObjects │ ├── 3-ElementBufferObjects.csproj │ ├── Program.cs │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 4-Shaders-InsAndOuts │ ├── 4-Shaders-InsAndOuts.csproj │ ├── Program.cs │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 4-Shaders-MoreAttributes │ ├── 4-Shaders-MoreAttributes.csproj │ ├── Program.cs │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 4-Shaders-Uniforms │ ├── 4-Shaders-Uniforms.csproj │ ├── Program.cs │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 5-Textures │ ├── 5-Textures.csproj │ ├── Program.cs │ ├── Resources │ │ └── container.png │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 6-MultipleTextures │ ├── 6-MultipleTextures.csproj │ ├── Program.cs │ ├── Resources │ │ ├── awesomeface.png │ │ └── container.png │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 7-Transformations │ ├── 7-Transformations.csproj │ ├── Program.cs │ ├── Resources │ │ ├── awesomeface.png │ │ └── container.png │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 8-CoordinatesSystems │ ├── 8-CoordinatesSystems.csproj │ ├── Program.cs │ ├── Resources │ │ ├── awesomeface.png │ │ └── container.png │ ├── Shaders │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs └── 9-Camera │ ├── 9-Camera.csproj │ ├── Program.cs │ ├── Resources │ ├── awesomeface.png │ └── container.png │ ├── Shaders │ ├── shader.frag │ └── shader.vert │ └── Window.cs ├── Chapter2 ├── 1-Colors │ ├── 1-Colors.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Shaders │ │ ├── lighting.frag │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 2-BasicLighting │ ├── 2-BasicLighting.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Shaders │ │ ├── lighting.frag │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 3-Materials │ ├── 3-Materials.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Shaders │ │ ├── lighting.frag │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 4-LightingMaps │ ├── 4-LightingMaps.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Resources │ │ ├── container2.png │ │ └── container2_specular.png │ ├── Shaders │ │ ├── lighting.frag │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 5-LightCasters-DirectionalLights │ ├── 5-LightCasters-DirectionalLights.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Resources │ │ ├── container2.png │ │ └── container2_specular.png │ ├── Shaders │ │ ├── lighting.frag │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 5-LightCasters-PointLights │ ├── 5-LightCasters-PointLights.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Resources │ │ ├── container2.png │ │ └── container2_specular.png │ ├── Shaders │ │ ├── lighting.frag │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs ├── 5-LightCasters-Spotlight │ ├── 5-LightCasters-Spotlight.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Resources │ │ ├── container2.png │ │ └── container2_specular.png │ ├── Shaders │ │ ├── lighting.frag │ │ ├── shader.frag │ │ └── shader.vert │ └── Window.cs └── 6-MultipleLights │ ├── 6-MultipleLights.csproj │ ├── OpenTK.dll.config │ ├── Program.cs │ ├── Resources │ ├── container2.png │ └── container2_specular.png │ ├── Shaders │ ├── lighting.frag │ ├── shader.frag │ └── shader.vert │ └── Window.cs ├── Common ├── Camera.cs ├── Common.csproj ├── Shader.cs └── Texture.cs ├── LICENSE ├── LearnOpenTK.sln └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Chapter1/1-CreatingAWindow/1-CreatingAWindow.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter1/1-CreatingAWindow/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Creating a Window", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | // To create a new window, create a class that extends GameWindow, then call Run() on it. 20 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 21 | { 22 | window.Run(); 23 | } 24 | 25 | // And that's it! That's all it takes to create a window with OpenTK. 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter1/1-CreatingAWindow/Window.cs: -------------------------------------------------------------------------------- 1 |  2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.GraphicsLibraryFramework; 4 | using OpenTK.Windowing.Desktop; 5 | 6 | namespace LearnOpenTK 7 | { 8 | // This is where all OpenGL code will be written. 9 | // OpenToolkit allows for several functions to be overriden to extend functionality; this is how we'll be writing code. 10 | public class Window : GameWindow 11 | { 12 | // A simple constructor to let us set properties like window size, title, FPS, etc. on the window. 13 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 14 | : base(gameWindowSettings, nativeWindowSettings) 15 | { 16 | } 17 | 18 | // This function runs on every update frame. 19 | protected override void OnUpdateFrame(FrameEventArgs e) 20 | { 21 | // Check if the Escape button is currently being pressed. 22 | if (KeyboardState.IsKeyDown(Keys.Escape)) 23 | { 24 | // If it is, close the window. 25 | Close(); 26 | } 27 | 28 | base.OnUpdateFrame(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Chapter1/2-HelloTriangle/2-HelloTriangle.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Chapter1/2-HelloTriangle/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Creating a Window", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/2-HelloTriangle/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 outputColor; 4 | 5 | void main() 6 | { 7 | outputColor = vec4(1.0, 1.0, 0.0, 1.0); 8 | } -------------------------------------------------------------------------------- /Chapter1/2-HelloTriangle/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | // For more information on how shaders work, check out the web version of this tutorial. 2 | // I'll include a simpler summary here. 3 | 4 | // First non-comment line should always be a #version statement; this just tells the GLSL compiler what version it should use. 5 | #version 330 core 6 | 7 | // GLSL's syntax is somewhat like C, but it has a few differences. 8 | 9 | // There are four different types of variables in GLSL: input, output, uniform, and internal. 10 | // - Input variables are sent from the buffer, in a way defined by GL.VertexAttribPointer. 11 | // - Output variables are sent from this shader to the next one in the chain (which will be the fragment shader most of the time). 12 | // - Uniforms will be touched on in the next tutorial. 13 | // - Internal variables are defined in the shader file and only used there. 14 | 15 | 16 | // The vertex shader is run once for every vertex. In C# pseudocode, it might look something like: 17 | // foreach(var vertex in vertices) 18 | // shader(vertex) 19 | 20 | 21 | // This defines our input variable, aPosition. 22 | // It starts with the line "layout(location = 0)". This defines where this input variable will be located, which is needed for GL.VertexAttribPointer. 23 | // 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 24 | // a call to GL.GetAttribLocation(shaderHandle, attributeName) 25 | // Next, the keyword "in" defines this as an input variable. We'll have an example of the "out" keyword in the next tutorial. 26 | // Then, the keyword "vec3" means this is a vector with 3 floats inside. 27 | 28 | layout(location = 0) in vec3 aPosition; 29 | 30 | 31 | // Like C, we have an entrypoint function. In this case, it takes void and returns void, and must be named main. 32 | // You can do all sorts of calculations here to modify your vertices, but right now, we don't need to do any of that. 33 | // gl_Position is the final vertex position; pass a vec4 to it and you're done. 34 | // Keep in mind that we only pass a vec3 to this shader; the fourth component of a vertex is known as "w". 35 | // It's only used in some more advanced OpenGL functions; it's not needed here. 36 | // So with a call to the vec4 function, we just give it a constant value of 1.0. 37 | 38 | void main(void) 39 | { 40 | gl_Position = vec4(aPosition, 1.0); 41 | } -------------------------------------------------------------------------------- /Chapter1/3-ElementBufferObjects/3-ElementBufferObjects.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter1/3-ElementBufferObjects/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Element Buffer Objects", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/3-ElementBufferObjects/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 outputColor; 4 | 5 | void main() 6 | { 7 | outputColor = vec4(1.0, 1.0, 0.0, 1.0); 8 | } -------------------------------------------------------------------------------- /Chapter1/3-ElementBufferObjects/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) in vec3 aPosition; 4 | 5 | void main(void) 6 | { 7 | gl_Position = vec4(aPosition, 1.0); 8 | } -------------------------------------------------------------------------------- /Chapter1/3-ElementBufferObjects/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Windowing.Common; 4 | using OpenTK.Windowing.GraphicsLibraryFramework; 5 | using OpenTK.Windowing.Desktop; 6 | 7 | namespace LearnOpenTK 8 | { 9 | // So you've drawn the first triangle. But what about drawing multiple? 10 | // You may consider just adding more vertices to the array, and that would technically work, but say you're drawing a rectangle. 11 | // It only needs four vertices, but since OpenGL works in triangles, you'd need to define 6. 12 | // Not a huge deal, but it quickly adds up when you get to more complex models. For example, a cube only needs 8 vertices, but 13 | // doing it that way would need 36 vertices! 14 | 15 | // OpenGL provides a way to reuse vertices, which can heavily reduce memory usage on complex objects. 16 | // This is called an Element Buffer Object. This tutorial will be all about how to set one up. 17 | public class Window : GameWindow 18 | { 19 | // We modify the vertex array to include four vertices for our rectangle. 20 | private readonly float[] _vertices = 21 | { 22 | 0.5f, 0.5f, 0.0f, // top right 23 | 0.5f, -0.5f, 0.0f, // bottom right 24 | -0.5f, -0.5f, 0.0f, // bottom left 25 | -0.5f, 0.5f, 0.0f, // top left 26 | }; 27 | 28 | // Then, we create a new array: indices. 29 | // This array controls how the EBO will use those vertices to create triangles 30 | private readonly uint[] _indices = 31 | { 32 | // Note that indices start at 0! 33 | 0, 1, 3, // The first triangle will be the top-right half of the triangle 34 | 1, 2, 3 // Then the second will be the bottom-left half of the triangle 35 | }; 36 | 37 | private int _vertexBufferObject; 38 | 39 | private int _vertexArrayObject; 40 | 41 | private Shader _shader; 42 | 43 | // Add a handle for the EBO 44 | private int _elementBufferObject; 45 | 46 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 47 | : base(gameWindowSettings, nativeWindowSettings) 48 | { 49 | } 50 | 51 | protected override void OnLoad() 52 | { 53 | base.OnLoad(); 54 | 55 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 56 | 57 | _vertexBufferObject = GL.GenBuffer(); 58 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 59 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 60 | 61 | _vertexArrayObject = GL.GenVertexArray(); 62 | GL.BindVertexArray(_vertexArrayObject); 63 | 64 | GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); 65 | GL.EnableVertexAttribArray(0); 66 | 67 | // We create/bind the Element Buffer Object EBO the same way as the VBO, except there is a major difference here which can be REALLY confusing. 68 | // The binding spot for ElementArrayBuffer is not actually a global binding spot like ArrayBuffer is. 69 | // Instead it's actually a property of the currently bound VertexArrayObject, and binding an EBO with no VAO is undefined behaviour. 70 | // This also means that if you bind another VAO, the current ElementArrayBuffer is going to change with it. 71 | // Another sneaky part is that you don't need to unbind the buffer in ElementArrayBuffer as unbinding the VAO is going to do this, 72 | // and unbinding the EBO will remove it from the VAO instead of unbinding it like you would for VBOs or VAOs. 73 | _elementBufferObject = GL.GenBuffer(); 74 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 75 | // We also upload data to the EBO the same way as we did with VBOs. 76 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 77 | // The EBO has now been properly setup. Go to the Render function to see how we draw our rectangle now! 78 | 79 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 80 | _shader.Use(); 81 | } 82 | 83 | protected override void OnRenderFrame(FrameEventArgs e) 84 | { 85 | base.OnRenderFrame(e); 86 | 87 | GL.Clear(ClearBufferMask.ColorBufferBit); 88 | 89 | _shader.Use(); 90 | 91 | // Because ElementArrayObject is a property of the currently bound VAO, 92 | // the buffer you will find in the ElementArrayBuffer will change with the currently bound VAO. 93 | GL.BindVertexArray(_vertexArrayObject); 94 | 95 | // Then replace your call to DrawTriangles with one to DrawElements 96 | // Arguments: 97 | // Primitive type to draw. Triangles in this case. 98 | // How many indices should be drawn. Six in this case. 99 | // Data type of the indices. The indices are an unsigned int, so we want that here too. 100 | // Offset in the EBO. Set this to 0 because we want to draw the whole thing. 101 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 102 | 103 | SwapBuffers(); 104 | } 105 | 106 | protected override void OnUpdateFrame(FrameEventArgs e) 107 | { 108 | base.OnUpdateFrame(e); 109 | 110 | var input = KeyboardState; 111 | 112 | if (input.IsKeyDown(Keys.Escape)) 113 | { 114 | Close(); 115 | } 116 | } 117 | 118 | protected override void OnResize(ResizeEventArgs e) 119 | { 120 | base.OnResize(e); 121 | GL.Viewport(0, 0, Size.X, Size.Y); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-InsAndOuts/4-Shaders-InsAndOuts.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter1/4-Shaders-InsAndOuts/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Shaders In and Outs!", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/4-Shaders-InsAndOuts/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 outputColor; 4 | 5 | // This is where the color variable we declared and assigned in vertex shader 6 | // Gets pass to, this is enabled by using the in keyword 7 | // Keep in mind the vec type must match in order for this to work 8 | 9 | in vec4 vertexColor; 10 | 11 | void main() 12 | { 13 | outputColor = vertexColor; 14 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-InsAndOuts/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // the position variable has attribute position 0 4 | layout(location = 0) in vec3 aPosition; 5 | 6 | // This variable uses the keyword out in order to pass on the value to the 7 | // next shader down in the chain, in this case the frag shader 8 | out vec4 vertexColor; 9 | 10 | void main(void) 11 | { 12 | // see how we directly give a vec3 to vec4's constructor 13 | gl_Position = vec4(aPosition, 1.0); 14 | 15 | // Here we assign the variable a dark red color to the out variable 16 | vertexColor = vec4(0.5, 0.0, 0.0, 1.0); 17 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-InsAndOuts/Window.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK.Graphics.OpenGL4; 3 | using LearnOpenTK.Common; 4 | using OpenTK.Windowing.Desktop; 5 | using OpenTK.Windowing.Common; 6 | using OpenTK.Windowing.GraphicsLibraryFramework; 7 | using System.Diagnostics; 8 | 9 | namespace LearnOpenTK 10 | 11 | { // Here we'll be elaborating on what shaders can do from the Hello World project we worked on before. 12 | // Specifically we'll be showing how shaders deal with input and output from the main program 13 | // and between each other. 14 | public class Window : GameWindow 15 | { 16 | 17 | private readonly float[] _vertices = 18 | { 19 | -0.5f, -0.5f, 0.0f, // Bottom-left vertex 20 | 0.5f, -0.5f, 0.0f, // Bottom-right vertex 21 | 0.0f, 0.5f, 0.0f // Top vertex 22 | }; 23 | 24 | private int _vertexBufferObject; 25 | 26 | private int _vertexArrayObject; 27 | 28 | private Shader _shader; 29 | 30 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 31 | : base(gameWindowSettings, nativeWindowSettings) 32 | { 33 | } 34 | 35 | protected override void OnLoad() 36 | { 37 | base.OnLoad(); 38 | 39 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 40 | 41 | _vertexBufferObject = GL.GenBuffer(); 42 | 43 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 44 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 45 | 46 | _vertexArrayObject = GL.GenVertexArray(); 47 | GL.BindVertexArray(_vertexArrayObject); 48 | 49 | GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); 50 | GL.EnableVertexAttribArray(0); 51 | 52 | // Vertex attributes are the data we send as input into the vertex shader from the main program. 53 | // So here we're checking to see how many vertex attributes our hardware can handle. 54 | // OpenGL at minimum supports 16 vertex attributes. This only needs to be called 55 | // when your intensive attribute work and need to know exactly how many are available to you. 56 | GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount); 57 | Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}"); 58 | 59 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 60 | _shader.Use(); 61 | } 62 | 63 | protected override void OnRenderFrame(FrameEventArgs e) 64 | { 65 | base.OnRenderFrame(e); 66 | 67 | GL.Clear(ClearBufferMask.ColorBufferBit); 68 | 69 | _shader.Use(); 70 | 71 | GL.BindVertexArray(_vertexArrayObject); 72 | 73 | GL.DrawArrays(PrimitiveType.Triangles, 0, 3); 74 | 75 | SwapBuffers(); 76 | } 77 | 78 | protected override void OnUpdateFrame(FrameEventArgs e) 79 | { 80 | base.OnUpdateFrame(e); 81 | 82 | var input = KeyboardState; 83 | 84 | if (input.IsKeyDown(Keys.Escape)) 85 | { 86 | Close(); 87 | } 88 | } 89 | 90 | protected override void OnResize(ResizeEventArgs e) 91 | { 92 | base.OnResize(e); 93 | 94 | GL.Viewport(0, 0, Size.X, Size.Y); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-MoreAttributes/4-Shaders-MoreAttributes.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter1/4-Shaders-MoreAttributes/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Shaders More Attributes!", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/4-Shaders-MoreAttributes/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 outputColor; 4 | 5 | in vec3 ourColor; 6 | 7 | void main() 8 | { 9 | outputColor = vec4(ourColor, 1.0); 10 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-MoreAttributes/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // the position variable has attribute position 0 4 | layout(location = 0) in vec3 aPosition; 5 | 6 | // This is where the color values we assigned in the main program goes to 7 | layout(location = 1) in vec3 aColor; 8 | 9 | out vec3 ourColor; // output a color to the fragment shader 10 | 11 | void main(void) 12 | { 13 | // see how we directly give a vec3 to vec4's constructor 14 | gl_Position = vec4(aPosition, 1.0); 15 | 16 | // We use the outColor variable to pass on the color information to the frag shader 17 | ourColor = aColor; 18 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-MoreAttributes/Window.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK.Graphics.OpenGL4; 3 | using LearnOpenTK.Common; 4 | using OpenTK.Windowing.Desktop; 5 | using OpenTK.Windowing.Common; 6 | using OpenTK.Windowing.GraphicsLibraryFramework; 7 | using System.Diagnostics; 8 | 9 | namespace LearnOpenTK 10 | { 11 | // In this project, we will be assigning 3 colors to the triangle, one for each vertex. 12 | // The output will be an interpolated value based on the distance from each vertex. 13 | // If you want to look more into it, the in-between step is called a Rasterizer. 14 | public class Window : GameWindow 15 | { 16 | 17 | // We're assigning three different colors at the asscoiate vertex position: 18 | // blue for the top, green for the bottom left and red for the bottom right. 19 | private readonly float[] _vertices = 20 | { 21 | // positions // colors 22 | 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right 23 | -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left 24 | 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top 25 | }; 26 | 27 | private int _vertexBufferObject; 28 | 29 | private int _vertexArrayObject; 30 | 31 | private Shader _shader; 32 | 33 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 34 | : base(gameWindowSettings, nativeWindowSettings) 35 | { 36 | } 37 | 38 | // Now, we start initializing OpenGL. 39 | protected override void OnLoad() 40 | { 41 | base.OnLoad(); 42 | 43 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 44 | 45 | _vertexBufferObject = GL.GenBuffer(); 46 | 47 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 48 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 49 | 50 | _vertexArrayObject = GL.GenVertexArray(); 51 | GL.BindVertexArray(_vertexArrayObject); 52 | 53 | // Just like before, we create a pointer for the 3 position components of our vertices. 54 | // The only difference here is that we need to account for the 3 color values in the stride variable. 55 | // Therefore, the stride contains the size of 6 floats instead of 3. 56 | GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); 57 | GL.EnableVertexAttribArray(0); 58 | 59 | // We create a new pointer for the color values. 60 | // Much like the previous pointer, we assign 6 in the stride value. 61 | // We also need to correctly set the offset to get the color values. 62 | // The color data starts after the position data, so the offset is the size of 3 floats. 63 | GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float)); 64 | // We then enable color attribute (location=1) so it is available to the shader. 65 | GL.EnableVertexAttribArray(1); 66 | 67 | GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount); 68 | Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}"); 69 | 70 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 71 | _shader.Use(); 72 | } 73 | 74 | protected override void OnRenderFrame(FrameEventArgs e) 75 | { 76 | base.OnRenderFrame(e); 77 | 78 | GL.Clear(ClearBufferMask.ColorBufferBit); 79 | 80 | _shader.Use(); 81 | 82 | GL.BindVertexArray(_vertexArrayObject); 83 | 84 | GL.DrawArrays(PrimitiveType.Triangles, 0, 3); 85 | 86 | SwapBuffers(); 87 | } 88 | 89 | protected override void OnUpdateFrame(FrameEventArgs e) 90 | { 91 | base.OnUpdateFrame(e); 92 | 93 | var input = KeyboardState; 94 | 95 | if (input.IsKeyDown(Keys.Escape)) 96 | { 97 | Close(); 98 | } 99 | } 100 | 101 | protected override void OnResize(ResizeEventArgs e) 102 | { 103 | base.OnResize(e); 104 | 105 | GL.Viewport(0, 0, Size.X, Size.Y); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-Uniforms/4-Shaders-Uniforms.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter1/4-Shaders-Uniforms/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Shaders Uniforms!", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/4-Shaders-Uniforms/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 outputColor; 4 | 5 | 6 | // The Uniform keyword allows you to access a shader variable at any stage of the shader chain 7 | // It's also accessible across all of the main program 8 | // Whatever you set this variable to it keeps it until you either reset the value or updated it 9 | 10 | uniform vec4 ourColor; 11 | 12 | void main() 13 | { 14 | outputColor = ourColor; 15 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-Uniforms/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) in vec3 aPosition; // the position variable has attribute position 0 4 | 5 | void main(void) 6 | { 7 | // see how we directly give a vec3 to vec4's constructor 8 | gl_Position = vec4(aPosition, 1.0); 9 | } -------------------------------------------------------------------------------- /Chapter1/4-Shaders-Uniforms/Window.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using OpenTK.Graphics.OpenGL4; 4 | using LearnOpenTK.Common; 5 | using OpenTK.Windowing.Desktop; 6 | using OpenTK.Windowing.Common; 7 | using OpenTK.Windowing.GraphicsLibraryFramework; 8 | 9 | namespace LearnOpenTK 10 | { 11 | // This project will explore how to use uniform variable type which allows you to assign values 12 | // to shaders at any point during the project. 13 | public class Window : GameWindow 14 | { 15 | 16 | private readonly float[] _vertices = 17 | { 18 | -0.5f, -0.5f, 0.0f, // Bottom-left vertex 19 | 0.5f, -0.5f, 0.0f, // Bottom-right vertex 20 | 0.0f, 0.5f, 0.0f // Top vertex 21 | }; 22 | 23 | // So we're going make the triangle pulsate between a color range. 24 | // In order to do that, we'll need a constantly changing value. 25 | // The stopwatch is perfect for this as it is constantly going up. 26 | private Stopwatch _timer; 27 | 28 | private int _vertexBufferObject; 29 | 30 | private int _vertexArrayObject; 31 | 32 | private Shader _shader; 33 | 34 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 35 | : base(gameWindowSettings, nativeWindowSettings) 36 | { 37 | } 38 | 39 | protected override void OnLoad() 40 | { 41 | base.OnLoad(); 42 | 43 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 44 | 45 | _vertexBufferObject = GL.GenBuffer(); 46 | 47 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 48 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 49 | 50 | _vertexArrayObject = GL.GenVertexArray(); 51 | GL.BindVertexArray(_vertexArrayObject); 52 | 53 | GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); 54 | GL.EnableVertexAttribArray(0); 55 | 56 | GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount); 57 | Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}"); 58 | 59 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 60 | _shader.Use(); 61 | 62 | // We start the stopwatch here as this method is only called once. 63 | _timer = new Stopwatch(); 64 | _timer.Start(); 65 | } 66 | 67 | protected override void OnRenderFrame(FrameEventArgs e) 68 | { 69 | base.OnRenderFrame(e); 70 | 71 | GL.Clear(ClearBufferMask.ColorBufferBit); 72 | 73 | _shader.Use(); 74 | 75 | // Here, we get the total seconds that have elapsed since the last time this method has reset 76 | // and we assign it to the timeValue variable so it can be used for the pulsating color. 77 | double timeValue = _timer.Elapsed.TotalSeconds; 78 | 79 | // We're increasing / decreasing the green value we're passing into 80 | // the shader based off of timeValue we created in the previous line, 81 | // as well as using some built in math functions to help the change be smoother. 82 | float greenValue = (float)Math.Sin(timeValue) / 2.0f + 0.5f; 83 | 84 | // This gets the uniform variable location from the frag shader so that we can 85 | // assign the new green value to it. 86 | int vertexColorLocation = GL.GetUniformLocation(_shader.Handle, "ourColor"); 87 | 88 | // Here we're assigning the ourColor variable in the frag shader 89 | // via the OpenGL Uniform method which takes in the value as the individual vec values (which total 4 in this instance). 90 | GL.Uniform4(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); 91 | 92 | // You can alternatively use this overload of the same function. 93 | // GL.Uniform4(vertexColorLocation, new OpenTK.Mathematics.Color4(0f, greenValue, 0f, 0f)); 94 | 95 | // Bind the VAO 96 | GL.BindVertexArray(_vertexArrayObject); 97 | 98 | GL.DrawArrays(PrimitiveType.Triangles, 0, 3); 99 | 100 | SwapBuffers(); 101 | } 102 | 103 | protected override void OnUpdateFrame(FrameEventArgs e) 104 | { 105 | base.OnUpdateFrame(e); 106 | 107 | var input = KeyboardState; 108 | 109 | if (input.IsKeyDown(Keys.Escape)) 110 | { 111 | Close(); 112 | } 113 | } 114 | 115 | protected override void OnResize(ResizeEventArgs e) 116 | { 117 | base.OnResize(e); 118 | 119 | GL.Viewport(0, 0, Size.X, Size.Y); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /Chapter1/5-Textures/5-Textures.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter1/5-Textures/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Textures", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/5-Textures/Resources/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/5-Textures/Resources/container.png -------------------------------------------------------------------------------- /Chapter1/5-Textures/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 outputColor; 4 | 5 | in vec2 texCoord; 6 | 7 | // A sampler2d is the representation of a texture in a shader. 8 | // Each sampler is bound to a texture unit (texture units are described in Texture.cs on the Use function). 9 | // By default, the unit is 0, so no code-related setup is actually needed. 10 | // Multiple samplers will be demonstrated in section 1.5. 11 | uniform sampler2D texture0; 12 | 13 | void main() 14 | { 15 | // To use a texture, you call the texture() function. 16 | // It takes two parameters: the sampler to use, and a vec2, used as texture coordinates. 17 | outputColor = texture(texture0, texCoord); 18 | } -------------------------------------------------------------------------------- /Chapter1/5-Textures/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) in vec3 aPosition; 4 | 5 | // We add another input variable for the texture coordinates. 6 | 7 | layout(location = 1) in vec2 aTexCoord; 8 | 9 | // ...However, they aren't needed for the vertex shader itself. 10 | // Instead, we create an output variable so we can send that data to the fragment shader. 11 | 12 | out vec2 texCoord; 13 | 14 | void main(void) 15 | { 16 | // Then, we further the input texture coordinate to the output one. 17 | // texCoord can now be used in the fragment shader. 18 | 19 | texCoord = aTexCoord; 20 | 21 | gl_Position = vec4(aPosition, 1.0); 22 | } -------------------------------------------------------------------------------- /Chapter1/5-Textures/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Windowing.Common; 4 | using OpenTK.Windowing.GraphicsLibraryFramework; 5 | using OpenTK.Windowing.Desktop; 6 | 7 | namespace LearnOpenTK 8 | { 9 | public class Window : GameWindow 10 | { 11 | // Because we're adding a texture, we modify the vertex array to include texture coordinates. 12 | // Texture coordinates range from 0.0 to 1.0, with (0.0, 0.0) representing the bottom left, and (1.0, 1.0) representing the top right. 13 | // The new layout is three floats to create a vertex, then two floats to create the coordinates. 14 | private readonly float[] _vertices = 15 | { 16 | // Position Texture coordinates 17 | 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right 18 | 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right 19 | -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left 20 | -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left 21 | }; 22 | 23 | private readonly uint[] _indices = 24 | { 25 | 0, 1, 3, 26 | 1, 2, 3 27 | }; 28 | 29 | private int _elementBufferObject; 30 | 31 | private int _vertexBufferObject; 32 | 33 | private int _vertexArrayObject; 34 | 35 | private Shader _shader; 36 | 37 | // For documentation on this, check Texture.cs. 38 | private Texture _texture; 39 | 40 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 41 | : base(gameWindowSettings, nativeWindowSettings) 42 | { 43 | } 44 | 45 | protected override void OnLoad() 46 | { 47 | base.OnLoad(); 48 | 49 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 50 | 51 | _vertexArrayObject = GL.GenVertexArray(); 52 | GL.BindVertexArray(_vertexArrayObject); 53 | 54 | _vertexBufferObject = GL.GenBuffer(); 55 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 56 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 57 | 58 | _elementBufferObject = GL.GenBuffer(); 59 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 60 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 61 | 62 | // The shaders have been modified to include the texture coordinates, check them out after finishing the OnLoad function. 63 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 64 | _shader.Use(); 65 | 66 | // Because there's now 5 floats between the start of the first vertex and the start of the second, 67 | // we modify the stride from 3 * sizeof(float) to 5 * sizeof(float). 68 | // This will now pass the new vertex array to the buffer. 69 | var vertexLocation = _shader.GetAttribLocation("aPosition"); 70 | GL.EnableVertexAttribArray(vertexLocation); 71 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); 72 | 73 | // Next, we also setup texture coordinates. It works in much the same way. 74 | // We add an offset of 3, since the texture coordinates comes after the position data. 75 | // We also change the amount of data to 2 because there's only 2 floats for texture coordinates. 76 | var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); 77 | GL.EnableVertexAttribArray(texCoordLocation); 78 | GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); 79 | 80 | _texture = Texture.LoadFromFile("Resources/container.png"); 81 | _texture.Use(TextureUnit.Texture0); 82 | } 83 | 84 | protected override void OnRenderFrame(FrameEventArgs e) 85 | { 86 | base.OnRenderFrame(e); 87 | 88 | GL.Clear(ClearBufferMask.ColorBufferBit); 89 | 90 | GL.BindVertexArray(_vertexArrayObject); 91 | 92 | _texture.Use(TextureUnit.Texture0); 93 | _shader.Use(); 94 | 95 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 96 | 97 | SwapBuffers(); 98 | } 99 | 100 | protected override void OnUpdateFrame(FrameEventArgs e) 101 | { 102 | base.OnUpdateFrame(e); 103 | 104 | var input = KeyboardState; 105 | 106 | if (input.IsKeyDown(Keys.Escape)) 107 | { 108 | Close(); 109 | } 110 | } 111 | 112 | protected override void OnResize(ResizeEventArgs e) 113 | { 114 | base.OnResize(e); 115 | 116 | GL.Viewport(0, 0, Size.X, Size.Y); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /Chapter1/6-MultipleTextures/6-MultipleTextures.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter1/6-MultipleTextures/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Multiple Textures", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/6-MultipleTextures/Resources/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/6-MultipleTextures/Resources/awesomeface.png -------------------------------------------------------------------------------- /Chapter1/6-MultipleTextures/Resources/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/6-MultipleTextures/Resources/container.png -------------------------------------------------------------------------------- /Chapter1/6-MultipleTextures/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 outputColor; 4 | 5 | in vec2 texCoord; 6 | 7 | uniform sampler2D texture0; 8 | uniform sampler2D texture1; 9 | 10 | void main() 11 | { 12 | outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2); 13 | } -------------------------------------------------------------------------------- /Chapter1/6-MultipleTextures/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) in vec3 aPosition; 4 | 5 | layout(location = 1) in vec2 aTexCoord; 6 | 7 | out vec2 texCoord; 8 | 9 | void main(void) 10 | { 11 | texCoord = aTexCoord; 12 | 13 | gl_Position = vec4(aPosition, 1.0); 14 | } -------------------------------------------------------------------------------- /Chapter1/6-MultipleTextures/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Windowing.Common; 4 | using OpenTK.Windowing.GraphicsLibraryFramework; 5 | using OpenTK.Windowing.Desktop; 6 | 7 | namespace LearnOpenTK 8 | { 9 | public class Window : GameWindow 10 | { 11 | private readonly float[] _vertices = 12 | { 13 | // Position Texture coordinates 14 | 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right 15 | 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right 16 | -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left 17 | -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left 18 | }; 19 | 20 | private readonly uint[] _indices = 21 | { 22 | 0, 1, 3, 23 | 1, 2, 3 24 | }; 25 | 26 | private int _elementBufferObject; 27 | 28 | private int _vertexBufferObject; 29 | 30 | private int _vertexArrayObject; 31 | 32 | private Shader _shader; 33 | 34 | private Texture _texture; 35 | 36 | private Texture _texture2; 37 | 38 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 39 | : base(gameWindowSettings, nativeWindowSettings) 40 | { 41 | } 42 | 43 | protected override void OnLoad() 44 | { 45 | base.OnLoad(); 46 | 47 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 48 | 49 | _vertexArrayObject = GL.GenVertexArray(); 50 | GL.BindVertexArray(_vertexArrayObject); 51 | 52 | _vertexBufferObject = GL.GenBuffer(); 53 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 54 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 55 | 56 | _elementBufferObject = GL.GenBuffer(); 57 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 58 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 59 | 60 | // shader.frag has been modified yet again, take a look at it as well. 61 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 62 | _shader.Use(); 63 | 64 | var vertexLocation = _shader.GetAttribLocation("aPosition"); 65 | GL.EnableVertexAttribArray(vertexLocation); 66 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); 67 | 68 | var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); 69 | GL.EnableVertexAttribArray(texCoordLocation); 70 | GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); 71 | 72 | _texture = Texture.LoadFromFile("Resources/container.png"); 73 | // Texture units are explained in Texture.cs, at the Use function. 74 | // First texture goes in texture unit 0. 75 | _texture.Use(TextureUnit.Texture0); 76 | 77 | // This is helpful because System.Drawing reads the pixels differently than OpenGL expects. 78 | _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); 79 | // Then, the second goes in texture unit 1. 80 | _texture2.Use(TextureUnit.Texture1); 81 | 82 | // Next, we must setup the samplers in the shaders to use the right textures. 83 | // The int we send to the uniform indicates which texture unit the sampler should use. 84 | _shader.SetInt("texture0", 0); 85 | _shader.SetInt("texture1", 1); 86 | } 87 | 88 | protected override void OnRenderFrame(FrameEventArgs e) 89 | { 90 | base.OnRenderFrame(e); 91 | 92 | GL.Clear(ClearBufferMask.ColorBufferBit); 93 | 94 | GL.BindVertexArray(_vertexArrayObject); 95 | 96 | _texture.Use(TextureUnit.Texture0); 97 | _texture2.Use(TextureUnit.Texture1); 98 | _shader.Use(); 99 | 100 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 101 | 102 | SwapBuffers(); 103 | } 104 | 105 | protected override void OnUpdateFrame(FrameEventArgs e) 106 | { 107 | base.OnUpdateFrame(e); 108 | 109 | var input = KeyboardState; 110 | 111 | if (input.IsKeyDown(Keys.Escape)) 112 | { 113 | Close(); 114 | } 115 | } 116 | 117 | protected override void OnResize(ResizeEventArgs e) 118 | { 119 | base.OnResize(e); 120 | 121 | GL.Viewport(0, 0, Size.X, Size.Y); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /Chapter1/7-Transformations/7-Transformations.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter1/7-Transformations/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Transformations", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/7-Transformations/Resources/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/7-Transformations/Resources/awesomeface.png -------------------------------------------------------------------------------- /Chapter1/7-Transformations/Resources/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/7-Transformations/Resources/container.png -------------------------------------------------------------------------------- /Chapter1/7-Transformations/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 outputColor; 4 | 5 | in vec2 texCoord; 6 | 7 | uniform sampler2D texture0; 8 | uniform sampler2D texture1; 9 | 10 | void main() 11 | { 12 | outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2); 13 | } -------------------------------------------------------------------------------- /Chapter1/7-Transformations/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) in vec3 aPosition; 4 | 5 | layout(location = 1) in vec2 aTexCoord; 6 | 7 | out vec2 texCoord; 8 | 9 | // Add a uniform for the transformation matrix. 10 | uniform mat4 transform; 11 | 12 | void main(void) 13 | { 14 | texCoord = aTexCoord; 15 | 16 | // Then all you have to do is multiply the vertices by the transformation matrix, and you'll see your transformation in the scene! 17 | gl_Position = vec4(aPosition, 1.0) * transform; 18 | } -------------------------------------------------------------------------------- /Chapter1/7-Transformations/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Mathematics; 4 | using OpenTK.Windowing.Common; 5 | using OpenTK.Windowing.GraphicsLibraryFramework; 6 | using OpenTK.Windowing.Desktop; 7 | 8 | namespace LearnOpenTK 9 | { 10 | // So you can setup OpenGL, you can draw basic shapes without wasting vertices, and you can texture them. 11 | // There's one big thing left, though: moving the shapes. 12 | // To do this, we use linear algebra to move the vertices in the vertex shader. 13 | 14 | // Just as a disclaimer: this tutorial will NOT explain linear algebra or matrices; those topics are wayyyyy too complex to do with comments. 15 | // If you want a more detailed understanding of what's going on here, look at the web version of this tutorial instead. 16 | // A deep understanding of linear algebra won't be necessary for this tutorial as OpenTK includes built-in matrix types that abstract over the actual math. 17 | 18 | // Head down to RenderFrame to see how we can apply transformations to our shape. 19 | public class Window : GameWindow 20 | { 21 | private readonly float[] _vertices = 22 | { 23 | // Position Texture coordinates 24 | 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right 25 | 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right 26 | -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left 27 | -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left 28 | }; 29 | 30 | private readonly uint[] _indices = 31 | { 32 | 0, 1, 3, 33 | 1, 2, 3 34 | }; 35 | 36 | private int _elementBufferObject; 37 | 38 | private int _vertexBufferObject; 39 | 40 | private int _vertexArrayObject; 41 | 42 | private Shader _shader; 43 | 44 | private Texture _texture; 45 | 46 | private Texture _texture2; 47 | 48 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 49 | : base(gameWindowSettings, nativeWindowSettings) 50 | { 51 | } 52 | 53 | protected override void OnLoad() 54 | { 55 | base.OnLoad(); 56 | 57 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 58 | 59 | _vertexArrayObject = GL.GenVertexArray(); 60 | GL.BindVertexArray(_vertexArrayObject); 61 | 62 | _vertexBufferObject = GL.GenBuffer(); 63 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 64 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 65 | 66 | _elementBufferObject = GL.GenBuffer(); 67 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 68 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 69 | 70 | // shader.vert has been modified, take a look at it as well. 71 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 72 | _shader.Use(); 73 | 74 | var vertexLocation = _shader.GetAttribLocation("aPosition"); 75 | GL.EnableVertexAttribArray(vertexLocation); 76 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); 77 | 78 | var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); 79 | GL.EnableVertexAttribArray(texCoordLocation); 80 | GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); 81 | 82 | _texture = Texture.LoadFromFile("Resources/container.png"); 83 | _texture.Use(TextureUnit.Texture0); 84 | 85 | _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); 86 | _texture2.Use(TextureUnit.Texture1); 87 | 88 | _shader.SetInt("texture0", 0); 89 | _shader.SetInt("texture1", 1); 90 | } 91 | 92 | protected override void OnRenderFrame(FrameEventArgs e) 93 | { 94 | base.OnRenderFrame(e); 95 | 96 | GL.Clear(ClearBufferMask.ColorBufferBit); 97 | 98 | GL.BindVertexArray(_vertexArrayObject); 99 | 100 | // Note: The matrices we'll use for transformations are all 4x4. 101 | 102 | // We start with an identity matrix. This is just a simple matrix that doesn't move the vertices at all. 103 | var transform = Matrix4.Identity; 104 | 105 | // The next few steps just show how to use OpenTK's matrix functions, and aren't necessary for the transform matrix to actually work. 106 | // If you want, you can just pass the identity matrix to the shader, though it won't affect the vertices at all. 107 | 108 | // A fact to note about matrices is that the order of multiplications matter. "matrixA * matrixB" and "matrixB * matrixA" mean different things. 109 | // A VERY important thing to know is that OpenTK matrices are so called row-major. We won't go into the full details here, but here is a good place to read more about it: 110 | // https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/geometry/row-major-vs-column-major-vector 111 | // What it means for us is that we can think of matrix multiplication as going left to right. 112 | // So "rotate * translate" means rotate (around the origin) first and then translate, as opposed to "translate * rotate" which means translate and then rotate (around the origin). 113 | 114 | // To combine two matrices, you multiply them. Here, we combine the transform matrix with another one created by OpenTK to rotate it by 20 degrees. 115 | // Note that all Matrix4.CreateRotation functions take radians, not degrees. Use MathHelper.DegreesToRadians() to convert to radians, if you want to use degrees. 116 | transform = transform * Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(20f)); 117 | 118 | // Next, we scale the matrix. This will make the rectangle slightly larger. 119 | transform = transform * Matrix4.CreateScale(1.1f); 120 | 121 | // Then, we translate the matrix, which will move it slightly towards the top-right. 122 | // Note that we aren't using a full coordinate system yet, so the translation is in normalized device coordinates. 123 | // The next tutorial will be about how to set one up so we can use more human-readable numbers. 124 | transform = transform * Matrix4.CreateTranslation(0.1f, 0.1f, 0.0f); 125 | 126 | _texture.Use(TextureUnit.Texture0); 127 | _texture2.Use(TextureUnit.Texture1); 128 | _shader.Use(); 129 | 130 | // Now that the matrix is finished, pass it to the vertex shader. 131 | // Go over to shader.vert to see how we finally apply this to the vertices. 132 | _shader.SetMatrix4("transform", transform); 133 | 134 | // And that's it for now! In the next tutorial, we'll see how to setup a full coordinates system. 135 | 136 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 137 | 138 | SwapBuffers(); 139 | } 140 | 141 | protected override void OnUpdateFrame(FrameEventArgs e) 142 | { 143 | base.OnUpdateFrame(e); 144 | 145 | var input = KeyboardState; 146 | 147 | if (input.IsKeyDown(Keys.Escape)) 148 | { 149 | Close(); 150 | } 151 | } 152 | 153 | protected override void OnResize(ResizeEventArgs e) 154 | { 155 | base.OnResize(e); 156 | 157 | GL.Viewport(0, 0, Size.X, Size.Y); 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /Chapter1/8-CoordinatesSystems/8-CoordinatesSystems.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter1/8-CoordinatesSystems/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Coordinates Systems", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/8-CoordinatesSystems/Resources/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/8-CoordinatesSystems/Resources/awesomeface.png -------------------------------------------------------------------------------- /Chapter1/8-CoordinatesSystems/Resources/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/8-CoordinatesSystems/Resources/container.png -------------------------------------------------------------------------------- /Chapter1/8-CoordinatesSystems/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 outputColor; 4 | 5 | in vec2 texCoord; 6 | 7 | uniform sampler2D texture0; 8 | uniform sampler2D texture1; 9 | 10 | void main() 11 | { 12 | outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2); 13 | } -------------------------------------------------------------------------------- /Chapter1/8-CoordinatesSystems/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) in vec3 aPosition; 4 | 5 | layout(location = 1) in vec2 aTexCoord; 6 | 7 | out vec2 texCoord; 8 | 9 | uniform mat4 model; 10 | uniform mat4 view; 11 | uniform mat4 projection; 12 | 13 | void main(void) 14 | { 15 | texCoord = aTexCoord; 16 | 17 | gl_Position = vec4(aPosition, 1.0) * model * view * projection; 18 | } 19 | -------------------------------------------------------------------------------- /Chapter1/8-CoordinatesSystems/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Mathematics; 4 | using OpenTK.Windowing.Common; 5 | using OpenTK.Windowing.GraphicsLibraryFramework; 6 | using OpenTK.Windowing.Desktop; 7 | 8 | namespace LearnOpenTK 9 | { 10 | // We can now move around objects. However, how can we move our "camera", or modify our perspective? 11 | // In this tutorial, I'll show you how to setup a full projection/view/model (PVM) matrix. 12 | // In addition, we'll make the rectangle rotate over time. 13 | public class Window : GameWindow 14 | { 15 | private readonly float[] _vertices = 16 | { 17 | // Position Texture coordinates 18 | 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right 19 | 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right 20 | -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left 21 | -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left 22 | }; 23 | 24 | private readonly uint[] _indices = 25 | { 26 | 0, 1, 3, 27 | 1, 2, 3 28 | }; 29 | 30 | private int _elementBufferObject; 31 | 32 | private int _vertexBufferObject; 33 | 34 | private int _vertexArrayObject; 35 | 36 | private Shader _shader; 37 | 38 | private Texture _texture; 39 | 40 | private Texture _texture2; 41 | 42 | // We create a double to hold how long has passed since the program was opened. 43 | private double _time; 44 | 45 | // Then, we create two matrices to hold our view and projection. They're initialized at the bottom of OnLoad. 46 | // The view matrix is what you might consider the "camera". It represents the current viewport in the window. 47 | private Matrix4 _view; 48 | 49 | // This represents how the vertices will be projected. It's hard to explain through comments, 50 | // so check out the web version for a good demonstration of what this does. 51 | private Matrix4 _projection; 52 | 53 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 54 | : base(gameWindowSettings, nativeWindowSettings) 55 | { 56 | } 57 | 58 | protected override void OnLoad() 59 | { 60 | base.OnLoad(); 61 | 62 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 63 | 64 | // We enable depth testing here. If you try to draw something more complex than one plane without this, 65 | // you'll notice that polygons further in the background will occasionally be drawn over the top of the ones in the foreground. 66 | // Obviously, we don't want this, so we enable depth testing. We also clear the depth buffer in GL.Clear over in OnRenderFrame. 67 | GL.Enable(EnableCap.DepthTest); 68 | 69 | _vertexArrayObject = GL.GenVertexArray(); 70 | GL.BindVertexArray(_vertexArrayObject); 71 | 72 | _vertexBufferObject = GL.GenBuffer(); 73 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 74 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 75 | 76 | _elementBufferObject = GL.GenBuffer(); 77 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 78 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 79 | 80 | // shader.vert has been modified. Take a look at it after the explanation in OnRenderFrame. 81 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 82 | _shader.Use(); 83 | 84 | var vertexLocation = _shader.GetAttribLocation("aPosition"); 85 | GL.EnableVertexAttribArray(vertexLocation); 86 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); 87 | 88 | var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); 89 | GL.EnableVertexAttribArray(texCoordLocation); 90 | GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); 91 | 92 | _texture = Texture.LoadFromFile("Resources/container.png"); 93 | _texture.Use(TextureUnit.Texture0); 94 | 95 | _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); 96 | _texture2.Use(TextureUnit.Texture1); 97 | 98 | _shader.SetInt("texture0", 0); 99 | _shader.SetInt("texture1", 1); 100 | 101 | // 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. 102 | // For now, we move it backwards three units on the Z axis. 103 | _view = Matrix4.CreateTranslation(0.0f, 0.0f, -3.0f); 104 | 105 | // For the matrix, we use a few parameters. 106 | // Field of view. This determines how much the viewport can see at once. 45 is considered the most "realistic" setting, but most video games nowadays use 90 107 | // Aspect ratio. This should be set to Width / Height. 108 | // Near-clipping. Any vertices closer to the camera than this value will be clipped. 109 | // Far-clipping. Any vertices farther away from the camera than this value will be clipped. 110 | _projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45f), Size.X / (float) Size.Y, 0.1f, 100.0f); 111 | 112 | // Now, head over to OnRenderFrame to see how we setup the model matrix. 113 | } 114 | 115 | protected override void OnRenderFrame(FrameEventArgs e) 116 | { 117 | base.OnRenderFrame(e); 118 | 119 | // We add the time elapsed since last frame, times 4.0 to speed up animation, to the total amount of time passed. 120 | _time += 4.0 * e.Time; 121 | 122 | // We clear the depth buffer in addition to the color buffer. 123 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 124 | 125 | GL.BindVertexArray(_vertexArrayObject); 126 | 127 | _texture.Use(TextureUnit.Texture0); 128 | _texture2.Use(TextureUnit.Texture1); 129 | _shader.Use(); 130 | 131 | // Finally, we have the model matrix. This determines the position of the model. 132 | var model = Matrix4.Identity * Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time)); 133 | 134 | // Then, we pass all of these matrices to the vertex shader. 135 | // You could also multiply them here and then pass, which is faster, but having the separate matrices available is used for some advanced effects. 136 | 137 | // IMPORTANT: OpenTK's matrix types are transposed from what OpenGL would expect - rows and columns are reversed. 138 | // They are then transposed properly when passed to the shader. 139 | // This means that we retain the same multiplication order in both OpenTK c# code and GLSL shader code. 140 | // If you pass the individual matrices to the shader and multiply there, you have to do in the order "model * view * projection". 141 | // You can think like this: first apply the modelToWorld (aka model) matrix, then apply the worldToView (aka view) matrix, 142 | // and finally apply the viewToProjectedSpace (aka projection) matrix. 143 | _shader.SetMatrix4("model", model); 144 | _shader.SetMatrix4("view", _view); 145 | _shader.SetMatrix4("projection", _projection); 146 | 147 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 148 | 149 | SwapBuffers(); 150 | } 151 | 152 | protected override void OnUpdateFrame(FrameEventArgs e) 153 | { 154 | base.OnUpdateFrame(e); 155 | 156 | var input = KeyboardState; 157 | 158 | if (input.IsKeyDown(Keys.Escape)) 159 | { 160 | Close(); 161 | } 162 | } 163 | 164 | protected override void OnResize(ResizeEventArgs e) 165 | { 166 | base.OnResize(e); 167 | 168 | GL.Viewport(0, 0, Size.X, Size.Y); 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /Chapter1/9-Camera/9-Camera.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter1/9-Camera/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Camera", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter1/9-Camera/Resources/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/9-Camera/Resources/awesomeface.png -------------------------------------------------------------------------------- /Chapter1/9-Camera/Resources/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter1/9-Camera/Resources/container.png -------------------------------------------------------------------------------- /Chapter1/9-Camera/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 outputColor; 4 | 5 | in vec2 texCoord; 6 | 7 | uniform sampler2D texture0; 8 | uniform sampler2D texture1; 9 | 10 | void main() 11 | { 12 | outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2); 13 | } -------------------------------------------------------------------------------- /Chapter1/9-Camera/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) in vec3 aPosition; 4 | 5 | layout(location = 1) in vec2 aTexCoord; 6 | 7 | out vec2 texCoord; 8 | 9 | uniform mat4 model; 10 | uniform mat4 view; 11 | uniform mat4 projection; 12 | 13 | void main(void) 14 | { 15 | texCoord = aTexCoord; 16 | 17 | gl_Position = vec4(aPosition, 1.0) * model * view * projection; 18 | } 19 | -------------------------------------------------------------------------------- /Chapter1/9-Camera/Window.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LearnOpenTK.Common; 3 | using OpenTK.Graphics.OpenGL4; 4 | using OpenTK.Mathematics; 5 | using OpenTK.Windowing.Common; 6 | using OpenTK.Windowing.GraphicsLibraryFramework; 7 | using OpenTK.Windowing.Desktop; 8 | 9 | namespace LearnOpenTK 10 | { 11 | // We now have a rotating rectangle but how can we make the view move based on the users input? 12 | // In this tutorial we will take a look at how you could implement a camera class 13 | // and start responding to user input. 14 | // You can move to the camera class to see a lot of the new code added. 15 | // Otherwise you can move to Load to see how the camera is initialized. 16 | 17 | // In reality, we can't move the camera but we actually move the rectangle. 18 | // This will explained more in depth in the web version, however it pretty much gives us the same result 19 | // as if the view itself was moved. 20 | public class Window : GameWindow 21 | { 22 | private readonly float[] _vertices = 23 | { 24 | // Position Texture coordinates 25 | 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right 26 | 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right 27 | -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left 28 | -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left 29 | }; 30 | 31 | private readonly uint[] _indices = 32 | { 33 | 0, 1, 3, 34 | 1, 2, 3 35 | }; 36 | 37 | private int _elementBufferObject; 38 | 39 | private int _vertexBufferObject; 40 | 41 | private int _vertexArrayObject; 42 | 43 | private Shader _shader; 44 | 45 | private Texture _texture; 46 | 47 | private Texture _texture2; 48 | 49 | // The view and projection matrices have been removed as we don't need them here anymore. 50 | // They can now be found in the new camera class. 51 | 52 | // We need an instance of the new camera class so it can manage the view and projection matrix code. 53 | // We also need a boolean set to true to detect whether or not the mouse has been moved for the first time. 54 | // Finally, we add the last position of the mouse so we can calculate the mouse offset easily. 55 | private Camera _camera; 56 | 57 | private bool _firstMove = true; 58 | 59 | private Vector2 _lastPos; 60 | 61 | private double _time; 62 | 63 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 64 | : base(gameWindowSettings, nativeWindowSettings) 65 | { 66 | } 67 | 68 | protected override void OnLoad() 69 | { 70 | base.OnLoad(); 71 | 72 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 73 | 74 | GL.Enable(EnableCap.DepthTest); 75 | 76 | _vertexArrayObject = GL.GenVertexArray(); 77 | GL.BindVertexArray(_vertexArrayObject); 78 | 79 | _vertexBufferObject = GL.GenBuffer(); 80 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 81 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 82 | 83 | _elementBufferObject = GL.GenBuffer(); 84 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); 85 | GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw); 86 | 87 | _shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 88 | _shader.Use(); 89 | 90 | var vertexLocation = _shader.GetAttribLocation("aPosition"); 91 | GL.EnableVertexAttribArray(vertexLocation); 92 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0); 93 | 94 | var texCoordLocation = _shader.GetAttribLocation("aTexCoord"); 95 | GL.EnableVertexAttribArray(texCoordLocation); 96 | GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); 97 | 98 | _texture = Texture.LoadFromFile("Resources/container.png"); 99 | _texture.Use(TextureUnit.Texture0); 100 | 101 | _texture2 = Texture.LoadFromFile("Resources/awesomeface.png"); 102 | _texture2.Use(TextureUnit.Texture1); 103 | 104 | _shader.SetInt("texture0", 0); 105 | _shader.SetInt("texture1", 1); 106 | 107 | // We initialize the camera so that it is 3 units back from where the rectangle is. 108 | // We also give it the proper aspect ratio. 109 | _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); 110 | 111 | // We make the mouse cursor invisible and captured so we can have proper FPS-camera movement. 112 | CursorState = CursorState.Grabbed; 113 | } 114 | 115 | protected override void OnRenderFrame(FrameEventArgs e) 116 | { 117 | base.OnRenderFrame(e); 118 | 119 | _time += 4.0 * e.Time; 120 | 121 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 122 | 123 | GL.BindVertexArray(_vertexArrayObject); 124 | 125 | _texture.Use(TextureUnit.Texture0); 126 | _texture2.Use(TextureUnit.Texture1); 127 | _shader.Use(); 128 | 129 | var model = Matrix4.Identity * Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time)); 130 | _shader.SetMatrix4("model", model); 131 | _shader.SetMatrix4("view", _camera.GetViewMatrix()); 132 | _shader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 133 | 134 | GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0); 135 | 136 | SwapBuffers(); 137 | } 138 | 139 | protected override void OnUpdateFrame(FrameEventArgs e) 140 | { 141 | base.OnUpdateFrame(e); 142 | 143 | if (!IsFocused) // Check to see if the window is focused 144 | { 145 | return; 146 | } 147 | 148 | var input = KeyboardState; 149 | 150 | if (input.IsKeyDown(Keys.Escape)) 151 | { 152 | Close(); 153 | } 154 | 155 | const float cameraSpeed = 1.5f; 156 | const float sensitivity = 0.2f; 157 | 158 | if (input.IsKeyDown(Keys.W)) 159 | { 160 | _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward 161 | } 162 | 163 | if (input.IsKeyDown(Keys.S)) 164 | { 165 | _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards 166 | } 167 | if (input.IsKeyDown(Keys.A)) 168 | { 169 | _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left 170 | } 171 | if (input.IsKeyDown(Keys.D)) 172 | { 173 | _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right 174 | } 175 | if (input.IsKeyDown(Keys.Space)) 176 | { 177 | _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up 178 | } 179 | if (input.IsKeyDown(Keys.LeftShift)) 180 | { 181 | _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down 182 | } 183 | 184 | // Get the mouse state 185 | var mouse = MouseState; 186 | 187 | if (_firstMove) // This bool variable is initially set to true. 188 | { 189 | _lastPos = new Vector2(mouse.X, mouse.Y); 190 | _firstMove = false; 191 | } 192 | else 193 | { 194 | // Calculate the offset of the mouse position 195 | var deltaX = mouse.X - _lastPos.X; 196 | var deltaY = mouse.Y - _lastPos.Y; 197 | _lastPos = new Vector2(mouse.X, mouse.Y); 198 | 199 | // Apply the camera pitch and yaw (we clamp the pitch in the camera class) 200 | _camera.Yaw += deltaX * sensitivity; 201 | _camera.Pitch -= deltaY * sensitivity; // Reversed since y-coordinates range from bottom to top 202 | } 203 | } 204 | 205 | // In the mouse wheel function, we manage all the zooming of the camera. 206 | // This is simply done by changing the FOV of the camera. 207 | protected override void OnMouseWheel(MouseWheelEventArgs e) 208 | { 209 | base.OnMouseWheel(e); 210 | 211 | _camera.Fov -= e.OffsetY; 212 | } 213 | 214 | protected override void OnResize(ResizeEventArgs e) 215 | { 216 | base.OnResize(e); 217 | 218 | GL.Viewport(0, 0, Size.X, Size.Y); 219 | // We need to update the aspect ratio once the window has been resized. 220 | _camera.AspectRatio = Size.X / (float)Size.Y; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Chapter2/1-Colors/1-Colors.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter2/1-Colors/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/1-Colors/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Colors", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter2/1-Colors/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | uniform vec3 objectColor; 5 | uniform vec3 lightColor; 6 | 7 | void main() 8 | { 9 | // For our physically based coloring we simply want to multiply the color of the light with the objects color 10 | // A much better and in depth explanation of this in the web tutorials. 11 | FragColor = vec4(lightColor * objectColor, 1.0); 12 | } -------------------------------------------------------------------------------- /Chapter2/1-Colors/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/1-Colors/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | 4 | uniform mat4 model; 5 | uniform mat4 view; 6 | uniform mat4 projection; 7 | 8 | void main() 9 | { 10 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 11 | } -------------------------------------------------------------------------------- /Chapter2/1-Colors/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Mathematics; 4 | using OpenTK.Windowing.Common; 5 | using OpenTK.Windowing.GraphicsLibraryFramework; 6 | using OpenTK.Windowing.Desktop; 7 | 8 | namespace LearnOpenTK 9 | { 10 | // In this chapter we will focus on how to use lighting to make our games and other applications more lifelike 11 | 12 | // In this first part the focus will mainly be on setting up a scene for testing the different coloring options. 13 | // We draw two cubes one at 0,0,0 for testing our light shader, the second one is drawn where we have the light. 14 | // Furthermore in the shaders we have set up some basic physically based coloring. 15 | public class Window : GameWindow 16 | { 17 | // The vertices are now used to draw cubes. 18 | // For this example, we aren't using texture coordinates. 19 | // You can use textures with lighting (and we will get onto this), but for simplicity's sake, we'll just use a solid color here 20 | private readonly float[] _vertices = 21 | { 22 | // Position 23 | -0.5f, -0.5f, -0.5f, // Front face 24 | 0.5f, -0.5f, -0.5f, 25 | 0.5f, 0.5f, -0.5f, 26 | 0.5f, 0.5f, -0.5f, 27 | -0.5f, 0.5f, -0.5f, 28 | -0.5f, -0.5f, -0.5f, 29 | 30 | -0.5f, -0.5f, 0.5f, // Back face 31 | 0.5f, -0.5f, 0.5f, 32 | 0.5f, 0.5f, 0.5f, 33 | 0.5f, 0.5f, 0.5f, 34 | -0.5f, 0.5f, 0.5f, 35 | -0.5f, -0.5f, 0.5f, 36 | 37 | -0.5f, 0.5f, 0.5f, // Left face 38 | -0.5f, 0.5f, -0.5f, 39 | -0.5f, -0.5f, -0.5f, 40 | -0.5f, -0.5f, -0.5f, 41 | -0.5f, -0.5f, 0.5f, 42 | -0.5f, 0.5f, 0.5f, 43 | 44 | 0.5f, 0.5f, 0.5f, // Right face 45 | 0.5f, 0.5f, -0.5f, 46 | 0.5f, -0.5f, -0.5f, 47 | 0.5f, -0.5f, -0.5f, 48 | 0.5f, -0.5f, 0.5f, 49 | 0.5f, 0.5f, 0.5f, 50 | 51 | -0.5f, -0.5f, -0.5f, // Bottom face 52 | 0.5f, -0.5f, -0.5f, 53 | 0.5f, -0.5f, 0.5f, 54 | 0.5f, -0.5f, 0.5f, 55 | -0.5f, -0.5f, 0.5f, 56 | -0.5f, -0.5f, -0.5f, 57 | 58 | -0.5f, 0.5f, -0.5f, // Top face 59 | 0.5f, 0.5f, -0.5f, 60 | 0.5f, 0.5f, 0.5f, 61 | 0.5f, 0.5f, 0.5f, 62 | -0.5f, 0.5f, 0.5f, 63 | -0.5f, 0.5f, -0.5f 64 | }; 65 | 66 | // This is the position of both the light and the place the lamp cube will be drawn in the scene 67 | private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f); 68 | 69 | private int _vertexBufferObject; 70 | 71 | // I renamed the vertex array object since we now want two VAO's one for the model (the big cube for testing light shaders), 72 | // and one for the lamp so we can see where the light source comes from. 73 | // In an actual application you would probably either not draw the lamp at all or draw it with a model of a lamp of some sort. 74 | private int _vaoModel; 75 | 76 | private int _vaoLamp; 77 | 78 | // We also need two shaders, one for the lamp and one for our lighting material. 79 | // The lighting shader is where most of this chapter will take place as this is where a lot of the lighting "magic" happens. 80 | private Shader _lampShader; 81 | 82 | private Shader _lightingShader; 83 | 84 | private Camera _camera; 85 | 86 | private bool _firstMove = true; 87 | 88 | private Vector2 _lastPos; 89 | 90 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 91 | : base(gameWindowSettings, nativeWindowSettings) 92 | { 93 | } 94 | 95 | protected override void OnLoad() 96 | { 97 | base.OnLoad(); 98 | 99 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 100 | 101 | GL.Enable(EnableCap.DepthTest); 102 | 103 | _vertexBufferObject = GL.GenBuffer(); 104 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 105 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 106 | 107 | // Load the two different shaders, they use the same vertex shader program. However they have two different fragment shaders. 108 | // 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. 109 | // The lighting shaders uses the lighting.frag shader which is what a large part of this chapter will be about 110 | _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); 111 | _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 112 | 113 | { 114 | // Initialize the vao for the model 115 | _vaoModel = GL.GenVertexArray(); 116 | GL.BindVertexArray(_vaoModel); 117 | 118 | var vertexLocation = _lightingShader.GetAttribLocation("aPos"); 119 | GL.EnableVertexAttribArray(vertexLocation); 120 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); 121 | } 122 | 123 | { 124 | // Initialize the vao for the lamp, this is mostly the same as the code for the model cube 125 | _vaoLamp = GL.GenVertexArray(); 126 | GL.BindVertexArray(_vaoLamp); 127 | 128 | // Set the vertex attributes (only position data for our lamp) 129 | var vertexLocation = _lampShader.GetAttribLocation("aPos"); 130 | GL.EnableVertexAttribArray(vertexLocation); 131 | GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0); 132 | } 133 | 134 | _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); 135 | 136 | CursorState = CursorState.Grabbed; 137 | } 138 | 139 | protected override void OnRenderFrame(FrameEventArgs e) 140 | { 141 | base.OnRenderFrame(e); 142 | 143 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 144 | 145 | // Draw the model/cube with the lighting shader 146 | GL.BindVertexArray(_vaoModel); 147 | 148 | _lightingShader.Use(); 149 | 150 | // Matrix4.Identity is used as the matrix, since we just want to draw it at 0, 0, 0 151 | _lightingShader.SetMatrix4("model", Matrix4.Identity); 152 | _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); 153 | _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 154 | 155 | _lightingShader.SetVector3("objectColor", new Vector3(1.0f, 0.5f, 0.31f)); 156 | _lightingShader.SetVector3("lightColor", new Vector3(1.0f, 1.0f, 1.0f)); 157 | 158 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 159 | 160 | // Draw the lamp, this is mostly the same as for the model cube 161 | GL.BindVertexArray(_vaoLamp); 162 | 163 | _lampShader.Use(); 164 | 165 | Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); // We scale the lamp cube down a bit to make it less dominant 166 | lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos); 167 | 168 | _lampShader.SetMatrix4("model", lampMatrix); 169 | _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); 170 | _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 171 | 172 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 173 | 174 | SwapBuffers(); 175 | } 176 | 177 | protected override void OnUpdateFrame(FrameEventArgs e) 178 | { 179 | base.OnUpdateFrame(e); 180 | 181 | if (!IsFocused) 182 | { 183 | return; 184 | } 185 | 186 | var input = KeyboardState; 187 | 188 | if (input.IsKeyDown(Keys.Escape)) 189 | { 190 | Close(); 191 | } 192 | 193 | const float cameraSpeed = 1.5f; 194 | const float sensitivity = 0.2f; 195 | 196 | if (input.IsKeyDown(Keys.W)) 197 | { 198 | _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward 199 | } 200 | if (input.IsKeyDown(Keys.S)) 201 | { 202 | _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards 203 | } 204 | if (input.IsKeyDown(Keys.A)) 205 | { 206 | _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left 207 | } 208 | if (input.IsKeyDown(Keys.D)) 209 | { 210 | _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right 211 | } 212 | if (input.IsKeyDown(Keys.Space)) 213 | { 214 | _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up 215 | } 216 | if (input.IsKeyDown(Keys.LeftShift)) 217 | { 218 | _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down 219 | } 220 | 221 | var mouse = MouseState; 222 | 223 | if (_firstMove) 224 | { 225 | _lastPos = new Vector2(mouse.X, mouse.Y); 226 | _firstMove = false; 227 | } 228 | else 229 | { 230 | var deltaX = mouse.X - _lastPos.X; 231 | var deltaY = mouse.Y - _lastPos.Y; 232 | _lastPos = new Vector2(mouse.X, mouse.Y); 233 | 234 | _camera.Yaw += deltaX * sensitivity; 235 | _camera.Pitch -= deltaY * sensitivity; 236 | } 237 | } 238 | 239 | protected override void OnMouseWheel(MouseWheelEventArgs e) 240 | { 241 | base.OnMouseWheel(e); 242 | 243 | _camera.Fov -= e.OffsetY; 244 | } 245 | 246 | protected override void OnResize(ResizeEventArgs e) 247 | { 248 | base.OnResize(e); 249 | 250 | GL.Viewport(0, 0, Size.X, Size.Y); 251 | _camera.AspectRatio = Size.X / (float)Size.Y; 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Chapter2/2-BasicLighting/2-BasicLighting.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter2/2-BasicLighting/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/2-BasicLighting/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Basic lighting", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter2/2-BasicLighting/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | //In order to calculate some basic lighting we need a few things per model basis, and a few things per fragment basis: 5 | uniform vec3 objectColor; //The color of the object. 6 | uniform vec3 lightColor; //The color of the light. 7 | uniform vec3 lightPos; //The position of the light. 8 | uniform vec3 viewPos; //The position of the view and/or of the player. 9 | 10 | in vec3 Normal; //The normal of the fragment is calculated in the vertex shader. 11 | in vec3 FragPos; //The fragment position. 12 | 13 | void main() 14 | { 15 | //The ambient color is the color where the light does not directly hit the object. 16 | //You can think of it as an underlying tone throughout the object. Or the light coming from the scene/the sky (not the sun). 17 | float ambientStrength = 0.1; 18 | vec3 ambient = ambientStrength * lightColor; 19 | 20 | //We calculate the light direction, and make sure the normal is normalized. 21 | vec3 norm = normalize(Normal); 22 | vec3 lightDir = normalize(lightPos - FragPos); //Note: The light is pointing from the light to the fragment 23 | 24 | //The diffuse part of the phong model. 25 | //This is the part of the light that gives the most, it is the color of the object where it is hit by light. 26 | float diff = max(dot(norm, lightDir), 0.0); //We make sure the value is non negative with the max function. 27 | vec3 diffuse = diff * lightColor; 28 | 29 | 30 | //The specular light is the light that shines from the object, like light hitting metal. 31 | //The calculations are explained much more detailed in the web version of the tutorials. 32 | float specularStrength = 0.5; 33 | vec3 viewDir = normalize(viewPos - FragPos); 34 | vec3 reflectDir = reflect(-lightDir, norm); 35 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); //The 32 is the shininess of the material. 36 | vec3 specular = specularStrength * spec * lightColor; 37 | 38 | //At last we add all the light components together and multiply with the color of the object. Then we set the color 39 | //and makes sure the alpha value is 1 40 | vec3 result = (ambient + diffuse + specular) * objectColor; 41 | FragColor = vec4(result, 1.0); 42 | 43 | //Note we still use the light color * object color from the last tutorial. 44 | //This time the light values are in the phong model (ambient, diffuse and specular) 45 | } -------------------------------------------------------------------------------- /Chapter2/2-BasicLighting/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/2-BasicLighting/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | 5 | uniform mat4 model; 6 | uniform mat4 view; 7 | uniform mat4 projection; 8 | 9 | out vec3 Normal; 10 | out vec3 FragPos; 11 | 12 | void main() 13 | { 14 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 15 | FragPos = vec3(vec4(aPos, 1.0) * model); 16 | Normal = aNormal * mat3(transpose(inverse(model))); 17 | } -------------------------------------------------------------------------------- /Chapter2/2-BasicLighting/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Mathematics; 4 | using OpenTK.Windowing.Common; 5 | using OpenTK.Windowing.GraphicsLibraryFramework; 6 | using OpenTK.Windowing.Desktop; 7 | 8 | namespace LearnOpenTK 9 | { 10 | // In this tutorial we set up some basic lighting and look at how the phong model works 11 | // For more insight into how it all works look at the web version. If you are just here for the source, 12 | // most of the changes are in the shaders, specifically most of the changes are in the fragment shader as this is 13 | // where the lighting calculations happens. 14 | public class Window : GameWindow 15 | { 16 | // Here we now have added the normals of the vertices 17 | // Remember to define the layouts to the VAO's 18 | private readonly float[] _vertices = 19 | { 20 | // Position Normal 21 | -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, // Front face 22 | 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 23 | 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 24 | 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 25 | -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 26 | -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 27 | 28 | -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, // Back face 29 | 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 30 | 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 31 | 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 32 | -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 33 | -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 34 | 35 | -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, // Left face 36 | -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 37 | -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 38 | -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 39 | -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 40 | -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 41 | 42 | 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Right face 43 | 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 44 | 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 45 | 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 46 | 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 47 | 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 48 | 49 | -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, // Bottom face 50 | 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 51 | 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 52 | 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 53 | -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 54 | -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 55 | 56 | -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // Top face 57 | 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 58 | 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 59 | 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 60 | -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 61 | -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f 62 | }; 63 | 64 | private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f); 65 | 66 | private int _vertexBufferObject; 67 | 68 | private int _vaoModel; 69 | 70 | private int _vaoLamp; 71 | 72 | private Shader _lampShader; 73 | 74 | private Shader _lightingShader; 75 | 76 | private Camera _camera; 77 | 78 | private bool _firstMove = true; 79 | 80 | private Vector2 _lastPos; 81 | 82 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 83 | : base(gameWindowSettings, nativeWindowSettings) 84 | { 85 | } 86 | 87 | protected override void OnLoad() 88 | { 89 | base.OnLoad(); 90 | 91 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 92 | 93 | GL.Enable(EnableCap.DepthTest); 94 | 95 | _vertexBufferObject = GL.GenBuffer(); 96 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 97 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 98 | 99 | _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); 100 | _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 101 | 102 | { 103 | _vaoModel = GL.GenVertexArray(); 104 | GL.BindVertexArray(_vaoModel); 105 | 106 | var positionLocation = _lightingShader.GetAttribLocation("aPos"); 107 | GL.EnableVertexAttribArray(positionLocation); 108 | // Remember to change the stride as we now have 6 floats per vertex 109 | GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); 110 | 111 | // We now need to define the layout of the normal so the shader can use it 112 | var normalLocation = _lightingShader.GetAttribLocation("aNormal"); 113 | GL.EnableVertexAttribArray(normalLocation); 114 | GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float)); 115 | } 116 | 117 | { 118 | _vaoLamp = GL.GenVertexArray(); 119 | GL.BindVertexArray(_vaoLamp); 120 | 121 | var positionLocation = _lampShader.GetAttribLocation("aPos"); 122 | GL.EnableVertexAttribArray(positionLocation); 123 | // Also change the stride here as we now have 6 floats per vertex. Now we don't define the normal for the lamp VAO 124 | // 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 125 | // The two cubes still use the same position, and since the position is already in the graphics memory it is actually 126 | // better to do it this way. Look through the web version for a much better understanding of this. 127 | GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); 128 | } 129 | 130 | _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); 131 | 132 | CursorState = CursorState.Grabbed; 133 | } 134 | 135 | protected override void OnRenderFrame(FrameEventArgs e) 136 | { 137 | base.OnRenderFrame(e); 138 | 139 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 140 | 141 | GL.BindVertexArray(_vaoModel); 142 | 143 | _lightingShader.Use(); 144 | 145 | _lightingShader.SetMatrix4("model", Matrix4.Identity); 146 | _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); 147 | _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 148 | 149 | _lightingShader.SetVector3("objectColor", new Vector3(1.0f, 0.5f, 0.31f)); 150 | _lightingShader.SetVector3("lightColor", new Vector3(1.0f, 1.0f, 1.0f)); 151 | _lightingShader.SetVector3("lightPos", _lightPos); 152 | _lightingShader.SetVector3("viewPos", _camera.Position); 153 | 154 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 155 | 156 | GL.BindVertexArray(_vaoLamp); 157 | 158 | _lampShader.Use(); 159 | 160 | Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); 161 | lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos); 162 | 163 | _lampShader.SetMatrix4("model", lampMatrix); 164 | _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); 165 | _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 166 | 167 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 168 | 169 | SwapBuffers(); 170 | } 171 | 172 | protected override void OnUpdateFrame(FrameEventArgs e) 173 | { 174 | base.OnUpdateFrame(e); 175 | 176 | if (!IsFocused) 177 | { 178 | return; 179 | } 180 | 181 | var input = KeyboardState; 182 | 183 | if (input.IsKeyDown(Keys.Escape)) 184 | { 185 | Close(); 186 | } 187 | 188 | const float cameraSpeed = 1.5f; 189 | const float sensitivity = 0.2f; 190 | 191 | if (input.IsKeyDown(Keys.W)) 192 | { 193 | _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward 194 | } 195 | if (input.IsKeyDown(Keys.S)) 196 | { 197 | _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards 198 | } 199 | if (input.IsKeyDown(Keys.A)) 200 | { 201 | _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left 202 | } 203 | if (input.IsKeyDown(Keys.D)) 204 | { 205 | _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right 206 | } 207 | if (input.IsKeyDown(Keys.Space)) 208 | { 209 | _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up 210 | } 211 | if (input.IsKeyDown(Keys.LeftShift)) 212 | { 213 | _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down 214 | } 215 | 216 | var mouse = MouseState; 217 | 218 | if (_firstMove) 219 | { 220 | _lastPos = new Vector2(mouse.X, mouse.Y); 221 | _firstMove = false; 222 | } 223 | else 224 | { 225 | var deltaX = mouse.X - _lastPos.X; 226 | var deltaY = mouse.Y - _lastPos.Y; 227 | _lastPos = new Vector2(mouse.X, mouse.Y); 228 | 229 | _camera.Yaw += deltaX * sensitivity; 230 | _camera.Pitch -= deltaY * sensitivity; 231 | } 232 | } 233 | 234 | protected override void OnMouseWheel(MouseWheelEventArgs e) 235 | { 236 | base.OnMouseWheel(e); 237 | 238 | _camera.Fov -= e.OffsetY; 239 | } 240 | 241 | protected override void OnResize(ResizeEventArgs e) 242 | { 243 | base.OnResize(e); 244 | 245 | GL.Viewport(0, 0, Size.X, Size.Y); 246 | _camera.AspectRatio = Size.X / (float)Size.Y; 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /Chapter2/3-Materials/3-Materials.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter2/3-Materials/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/3-Materials/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Materials", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter2/3-Materials/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | //The material is a collection of some values that we talked about in the last tutorial, 3 | //some crucial elements to the phong model. 4 | struct Material { 5 | vec3 ambient; 6 | vec3 diffuse; 7 | vec3 specular; 8 | 9 | float shininess; //Shininess is the power the specular light is raised to 10 | }; 11 | //The light contains all the values from the light source, how the ambient diffuse and specular values are from the light source. 12 | //This is technically what we were using in the last episode as we were only applying the phong model directly to the light. 13 | struct Light { 14 | vec3 position; 15 | 16 | vec3 ambient; 17 | vec3 diffuse; 18 | vec3 specular; 19 | }; 20 | //We create the light and the material struct as uniforms. 21 | uniform Light light; 22 | uniform Material material; 23 | //We still need the view position. 24 | uniform vec3 viewPos; 25 | 26 | out vec4 FragColor; 27 | 28 | in vec3 Normal; 29 | in vec3 FragPos; 30 | 31 | void main() 32 | { 33 | //ambient 34 | vec3 ambient = light.ambient * material.ambient; //Remember to use the material here. 35 | 36 | //diffuse 37 | vec3 norm = normalize(Normal); 38 | vec3 lightDir = normalize(light.position - FragPos); 39 | float diff = max(dot(norm, lightDir), 0.0); 40 | vec3 diffuse = light.diffuse * (diff * material.diffuse); //Remember to use the material here. 41 | 42 | //specular 43 | vec3 viewDir = normalize(viewPos - FragPos); 44 | vec3 reflectDir = reflect(-lightDir, norm); 45 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 46 | vec3 specular = light.specular * (spec * material.specular); //Remember to use the material here. 47 | 48 | //Now the result sum has changed a bit, since we now set the objects color in each element, we now dont have to 49 | //multiply the light with the object here, instead we do it for each element seperatly. This allows much better control 50 | //over how each element is applied to different objects. 51 | vec3 result = ambient + diffuse + specular; 52 | FragColor = vec4(result, 1.0); 53 | } -------------------------------------------------------------------------------- /Chapter2/3-Materials/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/3-Materials/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | 5 | uniform mat4 model; 6 | uniform mat4 view; 7 | uniform mat4 projection; 8 | 9 | out vec3 Normal; 10 | out vec3 FragPos; 11 | 12 | void main() 13 | { 14 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 15 | FragPos = vec3(vec4(aPos, 1.0) * model); 16 | Normal = aNormal * mat3(transpose(inverse(model))); 17 | } -------------------------------------------------------------------------------- /Chapter2/3-Materials/Window.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LearnOpenTK.Common; 3 | using OpenTK.Graphics.OpenGL4; 4 | using OpenTK.Mathematics; 5 | using OpenTK.Windowing.Common; 6 | using OpenTK.Windowing.GraphicsLibraryFramework; 7 | using OpenTK.Windowing.Desktop; 8 | using System.Diagnostics; 9 | 10 | namespace LearnOpenTK 11 | { 12 | // In this tutorial we take the code from the last tutorial and create some level of abstraction over it allowing more 13 | // control of the interaction between the light and the material. 14 | // At the end of the web version of the tutorial we also had a bit of fun creating a disco light that changes 15 | // color of the cube over time. 16 | public class Window : GameWindow 17 | { 18 | private readonly float[] _vertices = 19 | { 20 | // Position Normal 21 | -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, // Front face 22 | 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 23 | 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 24 | 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 25 | -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 26 | -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 27 | 28 | -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, // Back face 29 | 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 30 | 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 31 | 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 32 | -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 33 | -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 34 | 35 | -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, // Left face 36 | -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 37 | -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 38 | -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 39 | -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 40 | -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 41 | 42 | 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Right face 43 | 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 44 | 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 45 | 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 46 | 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 47 | 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 48 | 49 | -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, // Bottom face 50 | 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 51 | 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 52 | 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 53 | -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 54 | -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 55 | 56 | -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // Top face 57 | 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 58 | 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 59 | 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 60 | -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 61 | -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f 62 | }; 63 | 64 | private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f); 65 | 66 | private int _vertexBufferObject; 67 | 68 | private int _vaoModel; 69 | 70 | private int _vaoLamp; 71 | 72 | private Shader _lampShader; 73 | 74 | private Shader _lightingShader; 75 | 76 | private Camera _camera; 77 | 78 | private bool _firstMove = true; 79 | 80 | private Vector2 _lastPos; 81 | 82 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 83 | : base(gameWindowSettings, nativeWindowSettings) 84 | { 85 | } 86 | 87 | protected override void OnLoad() 88 | { 89 | base.OnLoad(); 90 | 91 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 92 | 93 | GL.Enable(EnableCap.DepthTest); 94 | 95 | _vertexBufferObject = GL.GenBuffer(); 96 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 97 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 98 | 99 | _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); 100 | _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 101 | 102 | { 103 | _vaoModel = GL.GenVertexArray(); 104 | GL.BindVertexArray(_vaoModel); 105 | 106 | var positionLocation = _lightingShader.GetAttribLocation("aPos"); 107 | GL.EnableVertexAttribArray(positionLocation); 108 | GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); 109 | 110 | var normalLocation = _lightingShader.GetAttribLocation("aNormal"); 111 | GL.EnableVertexAttribArray(normalLocation); 112 | GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float)); 113 | } 114 | 115 | { 116 | _vaoLamp = GL.GenVertexArray(); 117 | GL.BindVertexArray(_vaoLamp); 118 | 119 | var positionLocation = _lampShader.GetAttribLocation("aPos"); 120 | GL.EnableVertexAttribArray(positionLocation); 121 | GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); 122 | } 123 | 124 | _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); 125 | 126 | CursorState = CursorState.Grabbed; 127 | } 128 | 129 | protected override void OnRenderFrame(FrameEventArgs e) 130 | { 131 | base.OnRenderFrame(e); 132 | 133 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 134 | 135 | GL.BindVertexArray(_vaoModel); 136 | 137 | _lightingShader.Use(); 138 | 139 | _lightingShader.SetMatrix4("model", Matrix4.Identity); 140 | _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); 141 | _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 142 | 143 | _lightingShader.SetVector3("viewPos", _camera.Position); 144 | 145 | // Here we set the material values of the cube, the material struct is just a container so to access 146 | // the underlying values we simply type "material.value" to get the location of the uniform 147 | _lightingShader.SetVector3("material.ambient", new Vector3(1.0f, 0.5f, 0.31f)); 148 | _lightingShader.SetVector3("material.diffuse", new Vector3(1.0f, 0.5f, 0.31f)); 149 | _lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f)); 150 | _lightingShader.SetFloat("material.shininess", 32.0f); 151 | 152 | // This is where we change the lights color over time using the sin function 153 | Vector3 lightColor; 154 | float time = DateTime.Now.Second + DateTime.Now.Millisecond / 1000f; 155 | lightColor.X = (MathF.Sin(time * 2.0f) + 1) / 2f; 156 | lightColor.Y = (MathF.Sin(time * 0.7f) + 1) / 2f; 157 | lightColor.Z = (MathF.Sin(time * 1.3f) + 1) / 2f; 158 | 159 | // The ambient light is less intensive than the diffuse light in order to make it less dominant 160 | Vector3 ambientColor = lightColor * new Vector3(0.2f); 161 | Vector3 diffuseColor = lightColor * new Vector3(0.5f); 162 | 163 | _lightingShader.SetVector3("light.position", _lightPos); 164 | _lightingShader.SetVector3("light.ambient", ambientColor); 165 | _lightingShader.SetVector3("light.diffuse", diffuseColor); 166 | _lightingShader.SetVector3("light.specular", new Vector3(1.0f, 1.0f, 1.0f)); 167 | 168 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 169 | 170 | GL.BindVertexArray(_vaoLamp); 171 | 172 | _lampShader.Use(); 173 | 174 | Matrix4 lampMatrix = Matrix4.Identity; 175 | lampMatrix *= Matrix4.CreateScale(0.2f); 176 | lampMatrix *= Matrix4.CreateTranslation(_lightPos); 177 | 178 | _lampShader.SetMatrix4("model", lampMatrix); 179 | _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); 180 | _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 181 | 182 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 183 | 184 | SwapBuffers(); 185 | } 186 | 187 | protected override void OnUpdateFrame(FrameEventArgs e) 188 | { 189 | base.OnUpdateFrame(e); 190 | 191 | if (!IsFocused) 192 | { 193 | return; 194 | } 195 | 196 | var input = KeyboardState; 197 | 198 | if (input.IsKeyDown(Keys.Escape)) 199 | { 200 | Close(); 201 | } 202 | 203 | const float cameraSpeed = 1.5f; 204 | const float sensitivity = 0.2f; 205 | 206 | if (input.IsKeyDown(Keys.W)) 207 | { 208 | _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward 209 | } 210 | if (input.IsKeyDown(Keys.S)) 211 | { 212 | _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards 213 | } 214 | if (input.IsKeyDown(Keys.A)) 215 | { 216 | _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left 217 | } 218 | if (input.IsKeyDown(Keys.D)) 219 | { 220 | _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right 221 | } 222 | if (input.IsKeyDown(Keys.Space)) 223 | { 224 | _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up 225 | } 226 | if (input.IsKeyDown(Keys.LeftShift)) 227 | { 228 | _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down 229 | } 230 | 231 | var mouse = MouseState; 232 | 233 | if (_firstMove) 234 | { 235 | _lastPos = new Vector2(mouse.X, mouse.Y); 236 | _firstMove = false; 237 | } 238 | else 239 | { 240 | var deltaX = mouse.X - _lastPos.X; 241 | var deltaY = mouse.Y - _lastPos.Y; 242 | _lastPos = new Vector2(mouse.X, mouse.Y); 243 | 244 | _camera.Yaw += deltaX * sensitivity; 245 | _camera.Pitch -= deltaY * sensitivity; 246 | } 247 | } 248 | 249 | protected override void OnMouseWheel(MouseWheelEventArgs e) 250 | { 251 | base.OnMouseWheel(e); 252 | 253 | _camera.Fov -= e.OffsetY; 254 | } 255 | 256 | protected override void OnResize(ResizeEventArgs e) 257 | { 258 | base.OnResize(e); 259 | 260 | GL.Viewport(0, 0, Size.X, Size.Y); 261 | _camera.AspectRatio = Size.X / (float)Size.Y; 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/4-LightingMaps.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | PreserveNewest 13 | 14 | 15 | PreserveNewest 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Lighting maps", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/Resources/container2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/4-LightingMaps/Resources/container2.png -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/Resources/container2_specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/4-LightingMaps/Resources/container2_specular.png -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | // Now the diffuse and the specular values are controlled by textures, this is what we in graphics call mapping something. 3 | // This means they are now based on textures instead of a color, and can be controlled much better per fragment. 4 | // This also allows us the ability to texture our objects again. 5 | 6 | // Note: We dont have a value for the ambient, as that is mostly the same the diffuse in pretty much every single situation. 7 | struct Material { 8 | sampler2D diffuse; 9 | sampler2D specular; 10 | float shininess; 11 | }; 12 | struct Light { 13 | vec3 position; 14 | 15 | vec3 ambient; 16 | vec3 diffuse; 17 | vec3 specular; 18 | }; 19 | 20 | uniform Light light; 21 | uniform Material material; 22 | uniform vec3 viewPos; 23 | 24 | out vec4 FragColor; 25 | 26 | in vec3 Normal; 27 | in vec3 FragPos; 28 | 29 | // Now we need the texture coordinates, however we only need one set even though we have 2 textures, 30 | // as every fragment should have the same texture position no matter what texture we are using. 31 | in vec2 TexCoords; 32 | 33 | void main() 34 | { 35 | // Each of the 3 different components now use a texture for the material values instead of the object wide color they had before. 36 | // Note: The ambient and the diffuse share the same texture. 37 | // ambient 38 | vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); 39 | 40 | // Diffuse 41 | vec3 norm = normalize(Normal); 42 | vec3 lightDir = normalize(light.position - FragPos); 43 | float diff = max(dot(norm, lightDir), 0.0); 44 | vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); 45 | 46 | // Specular 47 | vec3 viewDir = normalize(viewPos - FragPos); 48 | vec3 reflectDir = reflect(-lightDir, norm); 49 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 50 | vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 51 | 52 | vec3 result = ambient + diffuse + specular; 53 | FragColor = vec4(result, 1.0); 54 | } -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | layout (location = 2) in vec2 aTexCoords; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 projection; 9 | 10 | out vec3 Normal; 11 | out vec3 FragPos; 12 | out vec2 TexCoords; 13 | 14 | void main() 15 | { 16 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 17 | FragPos = vec3(vec4(aPos, 1.0) * model); 18 | Normal = aNormal * mat3(transpose(inverse(model))); 19 | TexCoords = aTexCoords; 20 | } -------------------------------------------------------------------------------- /Chapter2/4-LightingMaps/Window.cs: -------------------------------------------------------------------------------- 1 | using LearnOpenTK.Common; 2 | using OpenTK.Graphics.OpenGL4; 3 | using OpenTK.Mathematics; 4 | using OpenTK.Windowing.Common; 5 | using OpenTK.Windowing.GraphicsLibraryFramework; 6 | using OpenTK.Windowing.Desktop; 7 | 8 | namespace LearnOpenTK 9 | { 10 | // In this tutorial we take a look at how we can use textures to make the light settings we set up in the last episode 11 | // different per fragment instead of making them per object. 12 | // Remember to check out the shaders for how we converted to using textures there. 13 | public class Window : GameWindow 14 | { 15 | // Since we are going to use textures we of course have to include two new floats per vertex, the texture coords. 16 | private readonly float[] _vertices = 17 | { 18 | // Positions Normals Texture coords 19 | -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 20 | 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 21 | 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 22 | 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 23 | -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 24 | -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 25 | 26 | -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 27 | 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 28 | 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 29 | 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 30 | -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 31 | -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 32 | 33 | -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 34 | -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 35 | -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 36 | -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 37 | -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 38 | -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 39 | 40 | 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 41 | 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 42 | 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 43 | 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 44 | 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 45 | 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 46 | 47 | -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 48 | 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 49 | 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 50 | 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 51 | -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 52 | -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 53 | 54 | -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 55 | 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 56 | 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 57 | 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 58 | -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 59 | -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f 60 | }; 61 | 62 | private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f); 63 | 64 | private int _vertexBufferObject; 65 | 66 | private int _vaoModel; 67 | 68 | private int _vaoLamp; 69 | 70 | private Shader _lampShader; 71 | 72 | private Shader _lightingShader; 73 | 74 | // The texture containing information for the diffuse map, this would more commonly 75 | // just be called the color/texture of the object. 76 | private Texture _diffuseMap; 77 | 78 | // The specular map is a black/white representation of how specular each part of the texture is. 79 | private Texture _specularMap; 80 | 81 | private Camera _camera; 82 | 83 | private bool _firstMove = true; 84 | 85 | private Vector2 _lastPos; 86 | 87 | public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 88 | : base(gameWindowSettings, nativeWindowSettings) 89 | { 90 | } 91 | 92 | protected override void OnLoad() 93 | { 94 | base.OnLoad(); 95 | 96 | GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); 97 | 98 | GL.Enable(EnableCap.DepthTest); 99 | 100 | _vertexBufferObject = GL.GenBuffer(); 101 | GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); 102 | GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw); 103 | 104 | _lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag"); 105 | _lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag"); 106 | 107 | { 108 | _vaoModel = GL.GenVertexArray(); 109 | GL.BindVertexArray(_vaoModel); 110 | 111 | // All of the vertex attributes have been updated to now have a stride of 8 float sizes. 112 | var positionLocation = _lightingShader.GetAttribLocation("aPos"); 113 | GL.EnableVertexAttribArray(positionLocation); 114 | GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); 115 | 116 | var normalLocation = _lightingShader.GetAttribLocation("aNormal"); 117 | GL.EnableVertexAttribArray(normalLocation); 118 | GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float)); 119 | 120 | // The texture coords have now been added too, remember we only have 2 coordinates as the texture is 2d, 121 | // so the size parameter should only be 2 for the texture coordinates. 122 | var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords"); 123 | GL.EnableVertexAttribArray(texCoordLocation); 124 | GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float)); 125 | } 126 | 127 | { 128 | _vaoLamp = GL.GenVertexArray(); 129 | GL.BindVertexArray(_vaoLamp); 130 | 131 | // The lamp shader should have its stride updated aswell, however we dont actually 132 | // use the texture coords for the lamp, so we dont need to add any extra attributes. 133 | var positionLocation = _lampShader.GetAttribLocation("aPos"); 134 | GL.EnableVertexAttribArray(positionLocation); 135 | GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); 136 | } 137 | 138 | // Our two textures are loaded in from memory, you should head over and 139 | // check them out and compare them to the results. 140 | _diffuseMap = Texture.LoadFromFile("Resources/container2.png"); 141 | _specularMap = Texture.LoadFromFile("Resources/container2_specular.png"); 142 | 143 | _camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y); 144 | 145 | CursorState = CursorState.Grabbed; 146 | } 147 | 148 | protected override void OnRenderFrame(FrameEventArgs e) 149 | { 150 | base.OnRenderFrame(e); 151 | 152 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 153 | 154 | GL.BindVertexArray(_vaoModel); 155 | 156 | // The two textures need to be used, in this case we use the diffuse map as our 0th texture 157 | // and the specular map as our 1st texture. 158 | _diffuseMap.Use(TextureUnit.Texture0); 159 | _specularMap.Use(TextureUnit.Texture1); 160 | _lightingShader.Use(); 161 | 162 | _lightingShader.SetMatrix4("model", Matrix4.Identity); 163 | _lightingShader.SetMatrix4("view", _camera.GetViewMatrix()); 164 | _lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 165 | 166 | _lightingShader.SetVector3("viewPos", _camera.Position); 167 | 168 | // Here we specify to the shaders what textures they should refer to when we want to get the positions. 169 | _lightingShader.SetInt("material.diffuse", 0); 170 | _lightingShader.SetInt("material.specular", 1); 171 | _lightingShader.SetFloat("material.shininess", 32.0f); 172 | 173 | _lightingShader.SetVector3("light.position", _lightPos); 174 | _lightingShader.SetVector3("light.ambient", new Vector3(0.2f)); 175 | _lightingShader.SetVector3("light.diffuse", new Vector3(0.5f)); 176 | _lightingShader.SetVector3("light.specular", new Vector3(1.0f)); 177 | 178 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 179 | 180 | GL.BindVertexArray(_vaoLamp); 181 | 182 | _lampShader.Use(); 183 | 184 | Matrix4 lampMatrix = Matrix4.Identity; 185 | lampMatrix *= Matrix4.CreateScale(0.2f); 186 | lampMatrix *= Matrix4.CreateTranslation(_lightPos); 187 | 188 | _lampShader.SetMatrix4("model", lampMatrix); 189 | _lampShader.SetMatrix4("view", _camera.GetViewMatrix()); 190 | _lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix()); 191 | 192 | GL.DrawArrays(PrimitiveType.Triangles, 0, 36); 193 | 194 | SwapBuffers(); 195 | } 196 | 197 | protected override void OnUpdateFrame(FrameEventArgs e) 198 | { 199 | base.OnUpdateFrame(e); 200 | 201 | if (!IsFocused) 202 | { 203 | return; 204 | } 205 | 206 | var input = KeyboardState; 207 | 208 | if (input.IsKeyDown(Keys.Escape)) 209 | { 210 | Close(); 211 | } 212 | 213 | const float cameraSpeed = 1.5f; 214 | const float sensitivity = 0.2f; 215 | 216 | if (input.IsKeyDown(Keys.W)) 217 | { 218 | _camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward 219 | } 220 | if (input.IsKeyDown(Keys.S)) 221 | { 222 | _camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards 223 | } 224 | if (input.IsKeyDown(Keys.A)) 225 | { 226 | _camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left 227 | } 228 | if (input.IsKeyDown(Keys.D)) 229 | { 230 | _camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right 231 | } 232 | if (input.IsKeyDown(Keys.Space)) 233 | { 234 | _camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up 235 | } 236 | if (input.IsKeyDown(Keys.LeftShift)) 237 | { 238 | _camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down 239 | } 240 | 241 | var mouse = MouseState; 242 | 243 | if (_firstMove) 244 | { 245 | _lastPos = new Vector2(mouse.X, mouse.Y); 246 | _firstMove = false; 247 | } 248 | else 249 | { 250 | var deltaX = mouse.X - _lastPos.X; 251 | var deltaY = mouse.Y - _lastPos.Y; 252 | _lastPos = new Vector2(mouse.X, mouse.Y); 253 | 254 | _camera.Yaw += deltaX * sensitivity; 255 | _camera.Pitch -= deltaY * sensitivity; 256 | } 257 | } 258 | 259 | protected override void OnMouseWheel(MouseWheelEventArgs e) 260 | { 261 | base.OnMouseWheel(e); 262 | 263 | _camera.Fov -= e.OffsetY; 264 | } 265 | 266 | protected override void OnResize(ResizeEventArgs e) 267 | { 268 | base.OnResize(e); 269 | 270 | GL.Viewport(0, 0, Size.X, Size.Y); 271 | _camera.AspectRatio = Size.X / (float)Size.Y; 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/5-LightCasters-DirectionalLights.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Windowing.Common; 2 | using OpenTK.Windowing.Desktop; 3 | 4 | namespace LearnOpenTK 5 | { 6 | public static class Program 7 | { 8 | private static void Main() 9 | { 10 | var nativeWindowSettings = new NativeWindowSettings() 11 | { 12 | ClientSize = new OpenTK.Mathematics.Vector2i(800, 600), 13 | Title = "LearnOpenTK - Light caster - directional", 14 | // This is needed to run on macos 15 | Flags = ContextFlags.ForwardCompatible, 16 | }; 17 | 18 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 19 | { 20 | window.Run(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/Resources/container2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/5-LightCasters-DirectionalLights/Resources/container2.png -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/Resources/container2_specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/5-LightCasters-DirectionalLights/Resources/container2_specular.png -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | struct Material { 3 | sampler2D diffuse; 4 | sampler2D specular; 5 | float shininess; 6 | }; 7 | struct Light { 8 | //For a directional light we dont need the lights position to calculate the direction. 9 | //Since the direction is the same no matter the position of the fragment we also dont need that. 10 | vec3 direction; 11 | 12 | vec3 ambient; 13 | vec3 diffuse; 14 | vec3 specular; 15 | }; 16 | 17 | uniform Light light; 18 | uniform Material material; 19 | uniform vec3 viewPos; 20 | 21 | out vec4 FragColor; 22 | 23 | in vec3 Normal; 24 | in vec2 TexCoords; 25 | 26 | void main() 27 | { 28 | // ambient 29 | vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); 30 | 31 | // diffuse 32 | vec3 norm = normalize(Normal); 33 | vec3 lightDir = normalize(-light.direction);//We still normalize the light direction since we techically dont know, 34 | //wether it was normalized for us or not. 35 | float diff = max(dot(norm, lightDir), 0.0); 36 | vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); 37 | 38 | // specular 39 | vec3 viewDir = normalize(viewPos); 40 | vec3 reflectDir = reflect(-lightDir, norm); 41 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 42 | vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 43 | 44 | vec3 result = ambient + diffuse + specular; 45 | FragColor = vec4(result, 1.0); 46 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | layout (location = 2) in vec2 aTexCoords; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 projection; 9 | 10 | out vec3 Normal; 11 | out vec2 TexCoords; 12 | 13 | void main() 14 | { 15 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 16 | Normal = aNormal * mat3(transpose(inverse(model))); 17 | TexCoords = aTexCoords; 18 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/5-LightCasters-PointLights.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Light casters - point lights", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/Resources/container2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/5-LightCasters-PointLights/Resources/container2.png -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/Resources/container2_specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/5-LightCasters-PointLights/Resources/container2_specular.png -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | struct Material { 3 | sampler2D diffuse; 4 | sampler2D specular; 5 | float shininess; 6 | }; 7 | //This light structure is pretty much the same as the one from the last few parts of the tutorials 8 | struct Light { 9 | vec3 position; 10 | 11 | vec3 ambient; 12 | vec3 diffuse; 13 | vec3 specular; 14 | 15 | //In the web version you can see why we need the constant the linear and the quadratic values. 16 | //However to keep a brief explanation here, it is to make the light more dim the further you go away. 17 | //These are constants defining the graph the intensity of the light follows. 18 | float constant; 19 | float linear; 20 | float quadratic; 21 | }; 22 | 23 | uniform Light light; 24 | uniform Material material; 25 | uniform vec3 viewPos; 26 | 27 | out vec4 FragColor; 28 | 29 | in vec3 Normal; 30 | in vec3 FragPos; 31 | in vec2 TexCoords; 32 | 33 | void main() 34 | { 35 | 36 | //ambient 37 | vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); 38 | 39 | //diffuse 40 | vec3 norm = normalize(Normal); 41 | vec3 lightDir = normalize(light.position - FragPos); 42 | float diff = max(dot(norm, lightDir), 0.0); 43 | vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); 44 | 45 | //specular 46 | vec3 viewDir = normalize(viewPos - FragPos); 47 | vec3 reflectDir = reflect(-lightDir, norm); 48 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 49 | vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 50 | 51 | //attenuation 52 | //The attenuation is the term we use when talking about how dim the light gets over distance 53 | float distance = length(light.position - FragPos); 54 | //This formula is the so called attenuation formula used to calculate the attenuation 55 | float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); 56 | //To apply the attenuation simply multiply it into each of the elements 57 | ambient *= attenuation; 58 | diffuse *= attenuation; 59 | specular *= attenuation; 60 | 61 | vec3 result = ambient + diffuse + specular; 62 | FragColor = vec4(result, 1.0); 63 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-PointLights/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | layout (location = 2) in vec2 aTexCoords; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 projection; 9 | 10 | out vec3 Normal; 11 | out vec3 FragPos; 12 | out vec2 TexCoords; 13 | 14 | void main() 15 | { 16 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 17 | FragPos = vec3(vec4(aPos, 1.0) * model); 18 | Normal = aNormal * mat3(transpose(inverse(model))); 19 | TexCoords = aTexCoords; 20 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/5-LightCasters-Spotlight.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Light casters - spotlight", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/Resources/container2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/5-LightCasters-Spotlight/Resources/container2.png -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/Resources/container2_specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/5-LightCasters-Spotlight/Resources/container2_specular.png -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | struct Material { 3 | sampler2D diffuse; 4 | sampler2D specular; 5 | float shininess; 6 | }; 7 | //The spotlight is a pointlight in essence, however we only want to show the light within a certain angle. 8 | //That angle is the cutoff, the outercutoff is used to make a more smooth border to the spotlight. 9 | struct Light { 10 | vec3 position; 11 | vec3 direction; 12 | float cutOff; 13 | float outerCutOff; 14 | 15 | vec3 ambient; 16 | vec3 diffuse; 17 | vec3 specular; 18 | 19 | float constant; 20 | float linear; 21 | float quadratic; 22 | }; 23 | 24 | uniform Light light; 25 | uniform Material material; 26 | uniform vec3 viewPos; 27 | 28 | out vec4 FragColor; 29 | 30 | in vec3 Normal; 31 | in vec3 FragPos; 32 | in vec2 TexCoords; 33 | 34 | void main() 35 | { 36 | //ambient 37 | vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); 38 | 39 | //diffuse 40 | vec3 norm = normalize(Normal); 41 | vec3 lightDir = normalize(light.position - FragPos); 42 | float diff = max(dot(norm, lightDir), 0.0); 43 | vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); 44 | 45 | //specular 46 | vec3 viewDir = normalize(viewPos - FragPos); 47 | vec3 reflectDir = reflect(-lightDir, norm); 48 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 49 | vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 50 | 51 | //attenuation 52 | float distance = length(light.position - FragPos); 53 | float attenuation = 1.0 / (light.constant + light.linear * distance + 54 | light.quadratic * (distance * distance)); 55 | 56 | //spotlight intensity 57 | //This is how we calculate the spotlight, for a more in depth explanation of how this works. Check out the web tutorials. 58 | float theta = dot(lightDir, normalize(-light.direction)); 59 | float epsilon = light.cutOff - light.outerCutOff; 60 | float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); //The intensity, is the lights intensity on a given fragment, 61 | //this is used to make the smooth border. 62 | //When applying the spotlight intensity we want to multiply it. 63 | ambient *= attenuation; //Remember the ambient is where the light dosen't hit, this means the spotlight shouldn't be applied 64 | diffuse *= attenuation * intensity; 65 | specular *= attenuation * intensity; 66 | 67 | vec3 result = ambient + diffuse + specular; 68 | FragColor = vec4(result, 1.0); 69 | 70 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/5-LightCasters-Spotlight/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | layout (location = 2) in vec2 aTexCoords; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 projection; 9 | 10 | out vec3 Normal; 11 | out vec3 FragPos; 12 | out vec2 TexCoords; 13 | 14 | void main() 15 | { 16 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 17 | FragPos = vec3(vec4(aPos, 1.0) * model); 18 | Normal = aNormal * mat3(transpose(inverse(model))); 19 | TexCoords = aTexCoords; 20 | } -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/6-MultipleLights.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | LearnOpenTK 5 | LearnOpenTK 6 | net8.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using OpenTK.Windowing.Common; 3 | using OpenTK.Windowing.Desktop; 4 | 5 | namespace LearnOpenTK 6 | { 7 | public static class Program 8 | { 9 | private static void Main() 10 | { 11 | var nativeWindowSettings = new NativeWindowSettings() 12 | { 13 | ClientSize = new Vector2i(800, 600), 14 | Title = "LearnOpenTK - Multiple lights", 15 | // This is needed to run on macos 16 | Flags = ContextFlags.ForwardCompatible, 17 | }; 18 | 19 | using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings)) 20 | { 21 | window.Run(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/Resources/container2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/6-MultipleLights/Resources/container2.png -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/Resources/container2_specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentk/LearnOpenTK/7b876bdb79fcce973b3717859fab2022ea06a865/Chapter2/6-MultipleLights/Resources/container2_specular.png -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/Shaders/lighting.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | //In this tutorial it might seem like a lot is going on, but really we just combine the last tutorials, 3 pieces of source code into one 3 | //and added 3 extra point lights. 4 | struct Material { 5 | sampler2D diffuse; 6 | sampler2D specular; 7 | float shininess; 8 | }; 9 | //This is the directional light struct, where we only need the directions 10 | struct DirLight { 11 | vec3 direction; 12 | 13 | vec3 ambient; 14 | vec3 diffuse; 15 | vec3 specular; 16 | }; 17 | uniform DirLight dirLight; 18 | //This is our pointlight where we need the position aswell as the constants defining the attenuation of the light. 19 | struct PointLight { 20 | vec3 position; 21 | 22 | float constant; 23 | float linear; 24 | float quadratic; 25 | 26 | vec3 ambient; 27 | vec3 diffuse; 28 | vec3 specular; 29 | }; 30 | //We have a total of 4 point lights now, so we define a preprossecor directive to tell the gpu the size of our point light array 31 | #define NR_POINT_LIGHTS 4 32 | uniform PointLight pointLights[NR_POINT_LIGHTS]; 33 | //This is our spotlight where we need the position, attenuation along with the cutoff and the outer cutoff. Plus the direction of the light 34 | struct SpotLight{ 35 | vec3 position; 36 | vec3 direction; 37 | float cutOff; 38 | float outerCutOff; 39 | 40 | vec3 ambient; 41 | vec3 diffuse; 42 | vec3 specular; 43 | 44 | float constant; 45 | float linear; 46 | float quadratic; 47 | }; 48 | uniform SpotLight spotLight; 49 | 50 | uniform Material material; 51 | uniform vec3 viewPos; 52 | 53 | out vec4 FragColor; 54 | 55 | in vec3 Normal; 56 | in vec3 FragPos; 57 | in vec2 TexCoords; 58 | 59 | //Here we have some function prototypes, these are the signatures the gpu will use to know how the 60 | //parameters of each light calculation is layed out. 61 | //We have one function per light, since this makes it so we dont have to take up to much space in the main function. 62 | vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir); 63 | vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir); 64 | vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir); 65 | 66 | void main() 67 | { 68 | //properties 69 | vec3 norm = normalize(Normal); 70 | vec3 viewDir = normalize(viewPos - FragPos); 71 | 72 | //phase 1: Directional lighting 73 | vec3 result = CalcDirLight(dirLight, norm, viewDir); 74 | //phase 2: Point lights 75 | for(int i = 0; i < NR_POINT_LIGHTS; i++) 76 | result += CalcPointLight(pointLights[i], norm, FragPos, viewDir); 77 | //phase 3: Spot light 78 | result += CalcSpotLight(spotLight, norm, FragPos, viewDir); 79 | 80 | FragColor = vec4(result, 1.0); 81 | } 82 | 83 | vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) 84 | { 85 | vec3 lightDir = normalize(-light.direction); 86 | //diffuse shading 87 | float diff = max(dot(normal, lightDir), 0.0); 88 | //specular shading 89 | vec3 reflectDir = reflect(-lightDir, normal); 90 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 91 | //combine results 92 | vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); 93 | vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); 94 | vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 95 | return (ambient + diffuse + specular); 96 | } 97 | 98 | vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) 99 | { 100 | vec3 lightDir = normalize(light.position - fragPos); 101 | //diffuse shading 102 | float diff = max(dot(normal, lightDir), 0.0); 103 | //specular shading 104 | vec3 reflectDir = reflect(-lightDir, normal); 105 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 106 | //attenuation 107 | float distance = length(light.position - fragPos); 108 | float attenuation = 1.0 / (light.constant + light.linear * distance + 109 | light.quadratic * (distance * distance)); 110 | //combine results 111 | vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); 112 | vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); 113 | vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 114 | ambient *= attenuation; 115 | diffuse *= attenuation; 116 | specular *= attenuation; 117 | return (ambient + diffuse + specular); 118 | } 119 | vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) 120 | { 121 | 122 | //diffuse shading 123 | vec3 lightDir = normalize(light.position - FragPos); 124 | float diff = max(dot(normal, lightDir), 0.0); 125 | 126 | //specular shading 127 | vec3 reflectDir = reflect(-lightDir, normal); 128 | float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 129 | 130 | //attenuation 131 | float distance = length(light.position - FragPos); 132 | float attenuation = 1.0 / (light.constant + light.linear * distance + 133 | light.quadratic * (distance * distance)); 134 | 135 | //spotlight intensity 136 | float theta = dot(lightDir, normalize(-light.direction)); 137 | float epsilon = light.cutOff - light.outerCutOff; 138 | float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); 139 | 140 | //combine results 141 | vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); 142 | vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); 143 | vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); 144 | ambient *= attenuation; 145 | diffuse *= attenuation * intensity; 146 | specular *= attenuation * intensity; 147 | return (ambient + diffuse + specular); 148 | } -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/Shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | void main() 5 | { 6 | FragColor = vec4(1.0); // set all 4 vector values to 1.0 7 | } -------------------------------------------------------------------------------- /Chapter2/6-MultipleLights/Shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aNormal; 4 | layout (location = 2) in vec2 aTexCoords; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 projection; 9 | 10 | out vec3 Normal; 11 | out vec3 FragPos; 12 | out vec2 TexCoords; 13 | 14 | void main() 15 | { 16 | gl_Position = vec4(aPos, 1.0) * model * view * projection; 17 | FragPos = vec3(vec4(aPos, 1.0) * model); 18 | Normal = aNormal * mat3(transpose(inverse(model))); 19 | TexCoords = aTexCoords; 20 | } -------------------------------------------------------------------------------- /Common/Camera.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Mathematics; 2 | using System; 3 | 4 | namespace LearnOpenTK.Common 5 | { 6 | // This is the camera class as it could be set up after the tutorials on the website. 7 | // It is important to note there are a few ways you could have set up this camera. 8 | // For example, you could have also managed the player input inside the camera class, 9 | // and a lot of the properties could have been made into functions. 10 | 11 | // TL;DR: This is just one of many ways in which we could have set up the camera. 12 | // 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. 13 | public class Camera 14 | { 15 | // Those vectors are directions pointing outwards from the camera to define how it rotated. 16 | private Vector3 _front = -Vector3.UnitZ; 17 | 18 | private Vector3 _up = Vector3.UnitY; 19 | 20 | private Vector3 _right = Vector3.UnitX; 21 | 22 | // Rotation around the X axis (radians) 23 | private float _pitch; 24 | 25 | // Rotation around the Y axis (radians) 26 | private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right. 27 | 28 | // The field of view of the camera (radians) 29 | private float _fov = MathHelper.PiOver2; 30 | 31 | public Camera(Vector3 position, float aspectRatio) 32 | { 33 | Position = position; 34 | AspectRatio = aspectRatio; 35 | } 36 | 37 | // The position of the camera 38 | public Vector3 Position { get; set; } 39 | 40 | // This is simply the aspect ratio of the viewport, used for the projection matrix. 41 | public float AspectRatio { private get; set; } 42 | 43 | public Vector3 Front => _front; 44 | 45 | public Vector3 Up => _up; 46 | 47 | public Vector3 Right => _right; 48 | 49 | // We convert from degrees to radians as soon as the property is set to improve performance. 50 | public float Pitch 51 | { 52 | get => MathHelper.RadiansToDegrees(_pitch); 53 | set 54 | { 55 | // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch 56 | // of weird "bugs" when you are using euler angles for rotation. 57 | // If you want to read more about this you can try researching a topic called gimbal lock 58 | var angle = MathHelper.Clamp(value, -89f, 89f); 59 | _pitch = MathHelper.DegreesToRadians(angle); 60 | UpdateVectors(); 61 | } 62 | } 63 | 64 | // We convert from degrees to radians as soon as the property is set to improve performance. 65 | public float Yaw 66 | { 67 | get => MathHelper.RadiansToDegrees(_yaw); 68 | set 69 | { 70 | _yaw = MathHelper.DegreesToRadians(value); 71 | UpdateVectors(); 72 | } 73 | } 74 | 75 | // The field of view (FOV) is the vertical angle of the camera view. 76 | // This has been discussed more in depth in a previous tutorial, 77 | // but in this tutorial, you have also learned how we can use this to simulate a zoom feature. 78 | // We convert from degrees to radians as soon as the property is set to improve performance. 79 | public float Fov 80 | { 81 | get => MathHelper.RadiansToDegrees(_fov); 82 | set 83 | { 84 | var angle = MathHelper.Clamp(value, 1f, 90f); 85 | _fov = MathHelper.DegreesToRadians(angle); 86 | } 87 | } 88 | 89 | // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials 90 | public Matrix4 GetViewMatrix() 91 | { 92 | return Matrix4.LookAt(Position, Position + _front, _up); 93 | } 94 | 95 | // Get the projection matrix using the same method we have used up until this point 96 | public Matrix4 GetProjectionMatrix() 97 | { 98 | return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f); 99 | } 100 | 101 | // This function is going to update the direction vertices using some of the math learned in the web tutorials. 102 | private void UpdateVectors() 103 | { 104 | // First, the front matrix is calculated using some basic trigonometry. 105 | _front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw); 106 | _front.Y = MathF.Sin(_pitch); 107 | _front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw); 108 | 109 | // We need to make sure the vectors are all normalized, as otherwise we would get some funky results. 110 | _front = Vector3.Normalize(_front); 111 | 112 | // Calculate both the right and the up vector using cross product. 113 | // Note that we are calculating the right from the global up; this behaviour might 114 | // not be what you need for all cameras so keep this in mind if you do not want a FPS camera. 115 | _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY)); 116 | _up = Vector3.Normalize(Vector3.Cross(_right, _front)); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /Common/Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | LearnOpenTK.Common 4 | LearnOpenTK.Common 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Common/Shader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Collections.Generic; 5 | using OpenTK.Graphics.OpenGL4; 6 | using OpenTK.Mathematics; 7 | 8 | namespace LearnOpenTK.Common 9 | { 10 | // A simple class meant to help create shaders. 11 | public class Shader 12 | { 13 | public readonly int Handle; 14 | 15 | private readonly Dictionary _uniformLocations; 16 | 17 | // This is how you create a simple shader. 18 | // Shaders are written in GLSL, which is a language very similar to C in its semantics. 19 | // The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on. 20 | // A commented example of GLSL can be found in shader.vert. 21 | public Shader(string vertPath, string fragPath) 22 | { 23 | // There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders. 24 | // The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader. 25 | // The vertex shader won't be too important here, but they'll be more important later. 26 | // The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel. 27 | // The fragment shader is what we'll be using the most here. 28 | 29 | // Load vertex shader and compile 30 | var shaderSource = File.ReadAllText(vertPath); 31 | 32 | // GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created. 33 | var vertexShader = GL.CreateShader(ShaderType.VertexShader); 34 | 35 | // Now, bind the GLSL source code 36 | GL.ShaderSource(vertexShader, shaderSource); 37 | 38 | // And then compile 39 | CompileShader(vertexShader); 40 | 41 | // We do the same for the fragment shader. 42 | shaderSource = File.ReadAllText(fragPath); 43 | var fragmentShader = GL.CreateShader(ShaderType.FragmentShader); 44 | GL.ShaderSource(fragmentShader, shaderSource); 45 | CompileShader(fragmentShader); 46 | 47 | // These two shaders must then be merged into a shader program, which can then be used by OpenGL. 48 | // To do this, create a program... 49 | Handle = GL.CreateProgram(); 50 | 51 | // Attach both shaders... 52 | GL.AttachShader(Handle, vertexShader); 53 | GL.AttachShader(Handle, fragmentShader); 54 | 55 | // And then link them together. 56 | LinkProgram(Handle); 57 | 58 | // 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. 59 | // Detach them, and then delete them. 60 | GL.DetachShader(Handle, vertexShader); 61 | GL.DetachShader(Handle, fragmentShader); 62 | GL.DeleteShader(fragmentShader); 63 | GL.DeleteShader(vertexShader); 64 | 65 | // The shader is now ready to go, but first, we're going to cache all the shader uniform locations. 66 | // Querying this from the shader is very slow, so we do it once on initialization and reuse those values 67 | // later. 68 | 69 | // First, we have to get the number of active uniforms in the shader. 70 | GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms); 71 | 72 | // Next, allocate the dictionary to hold the locations. 73 | _uniformLocations = new Dictionary(); 74 | 75 | // Loop over all the uniforms, 76 | for (var i = 0; i < numberOfUniforms; i++) 77 | { 78 | // get the name of this uniform, 79 | var key = GL.GetActiveUniform(Handle, i, out _, out _); 80 | 81 | // get the location, 82 | var location = GL.GetUniformLocation(Handle, key); 83 | 84 | // and then add it to the dictionary. 85 | _uniformLocations.Add(key, location); 86 | } 87 | } 88 | 89 | private static void CompileShader(int shader) 90 | { 91 | // Try to compile the shader 92 | GL.CompileShader(shader); 93 | 94 | // Check for compilation errors 95 | GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); 96 | if (code != (int)All.True) 97 | { 98 | // We can use `GL.GetShaderInfoLog(shader)` to get information about the error. 99 | var infoLog = GL.GetShaderInfoLog(shader); 100 | throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}"); 101 | } 102 | } 103 | 104 | private static void LinkProgram(int program) 105 | { 106 | // We link the program 107 | GL.LinkProgram(program); 108 | 109 | // Check for linking errors 110 | GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code); 111 | if (code != (int)All.True) 112 | { 113 | // We can use `GL.GetProgramInfoLog(program)` to get information about the error. 114 | throw new Exception($"Error occurred whilst linking Program({program})"); 115 | } 116 | } 117 | 118 | // A wrapper function that enables the shader program. 119 | public void Use() 120 | { 121 | GL.UseProgram(Handle); 122 | } 123 | 124 | // The shader sources provided with this project use hardcoded layout(location)-s. If you want to do it dynamically, 125 | // you can omit the layout(location=X) lines in the vertex shader, and use this in VertexAttribPointer instead of the hardcoded values. 126 | public int GetAttribLocation(string attribName) 127 | { 128 | return GL.GetAttribLocation(Handle, attribName); 129 | } 130 | 131 | // Uniform setters 132 | // Uniforms are variables that can be set by user code, instead of reading them from the VBO. 133 | // You use VBOs for vertex-related data, and uniforms for almost everything else. 134 | 135 | // Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method: 136 | // 1. Bind the program you want to set the uniform on 137 | // 2. Get a handle to the location of the uniform with GL.GetUniformLocation. 138 | // 3. Use the appropriate GL.Uniform* function to set the uniform. 139 | 140 | /// 141 | /// Set a uniform int on this shader. 142 | /// 143 | /// The name of the uniform 144 | /// The data to set 145 | public void SetInt(string name, int data) 146 | { 147 | GL.UseProgram(Handle); 148 | GL.Uniform1(_uniformLocations[name], data); 149 | } 150 | 151 | /// 152 | /// Set a uniform float on this shader. 153 | /// 154 | /// The name of the uniform 155 | /// The data to set 156 | public void SetFloat(string name, float data) 157 | { 158 | GL.UseProgram(Handle); 159 | GL.Uniform1(_uniformLocations[name], data); 160 | } 161 | 162 | /// 163 | /// Set a uniform Matrix4 on this shader 164 | /// 165 | /// The name of the uniform 166 | /// The data to set 167 | /// 168 | /// 169 | /// The matrix is transposed before being sent to the shader. 170 | /// 171 | /// 172 | public void SetMatrix4(string name, Matrix4 data) 173 | { 174 | GL.UseProgram(Handle); 175 | GL.UniformMatrix4(_uniformLocations[name], true, ref data); 176 | } 177 | 178 | /// 179 | /// Set a uniform Vector3 on this shader. 180 | /// 181 | /// The name of the uniform 182 | /// The data to set 183 | public void SetVector3(string name, Vector3 data) 184 | { 185 | GL.UseProgram(Handle); 186 | GL.Uniform3(_uniformLocations[name], data); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Common/Texture.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Graphics.OpenGL4; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using PixelFormat = OpenTK.Graphics.OpenGL4.PixelFormat; 5 | using StbImageSharp; 6 | using System.IO; 7 | 8 | namespace LearnOpenTK.Common 9 | { 10 | // A helper class, much like Shader, meant to simplify loading textures. 11 | public class Texture 12 | { 13 | public readonly int Handle; 14 | 15 | public static Texture LoadFromFile(string path) 16 | { 17 | // Generate handle 18 | int handle = GL.GenTexture(); 19 | 20 | // Bind the handle 21 | GL.ActiveTexture(TextureUnit.Texture0); 22 | GL.BindTexture(TextureTarget.Texture2D, handle); 23 | 24 | // For this example, we're going to use .NET's built-in System.Drawing library to load textures. 25 | 26 | // OpenGL has it's texture origin in the lower left corner instead of the top left corner, 27 | // so we tell StbImageSharp to flip the image when loading. 28 | StbImage.stbi_set_flip_vertically_on_load(1); 29 | 30 | // Here we open a stream to the file and pass it to StbImageSharp to load. 31 | using (Stream stream = File.OpenRead(path)) 32 | { 33 | ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); 34 | 35 | // Now that our pixels are prepared, it's time to generate a texture. We do this with GL.TexImage2D. 36 | // Arguments: 37 | // The type of texture we're generating. There are various different types of textures, but the only one we need right now is Texture2D. 38 | // 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. 39 | // Target format of the pixels. This is the format OpenGL will store our image with. 40 | // Width of the image 41 | // Height of the image. 42 | // Border of the image. This must always be 0; it's a legacy parameter that Khronos never got rid of. 43 | // The format of the pixels, explained above. Since we loaded the pixels as RGBA earlier, we need to use PixelFormat.Rgba. 44 | // Data type of the pixels. 45 | // And finally, the actual pixels. 46 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data); 47 | } 48 | 49 | // Now that our texture is loaded, we can set a few settings to affect how the image appears on rendering. 50 | 51 | // First, we set the min and mag filter. These are used for when the texture is scaled down and up, respectively. 52 | // Here, we use Linear for both. This means that OpenGL will try to blend pixels, meaning that textures scaled too far will look blurred. 53 | // You could also use (amongst other options) Nearest, which just grabs the nearest pixel, which makes the texture look pixelated if scaled too far. 54 | // NOTE: The default settings for both of these are LinearMipmap. If you leave these as default but don't generate mipmaps, 55 | // your image will fail to render at all (usually resulting in pure black instead). 56 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 57 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 58 | 59 | // Now, set the wrapping mode. S is for the X axis, and T is for the Y axis. 60 | // We set this to Repeat so that textures will repeat when wrapped. Not demonstrated here since the texture coordinates exactly match 61 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); 62 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); 63 | 64 | // Next, generate mipmaps. 65 | // Mipmaps are smaller copies of the texture, scaled down. Each mipmap level is half the size of the previous one 66 | // Generated mipmaps go all the way down to just one pixel. 67 | // OpenGL will automatically switch between mipmaps when an object gets sufficiently far away. 68 | // This prevents moiré effects, as well as saving on texture bandwidth. 69 | // Here you can see and read about the morié effect https://en.wikipedia.org/wiki/Moir%C3%A9_pattern 70 | // Here is an example of mips in action https://en.wikipedia.org/wiki/File:Mipmap_Aliasing_Comparison.png 71 | GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); 72 | 73 | return new Texture(handle); 74 | } 75 | 76 | public Texture(int glHandle) 77 | { 78 | Handle = glHandle; 79 | } 80 | 81 | // Activate texture 82 | // Multiple textures can be bound, if your shader needs more than just one. 83 | // If you want to do that, use GL.ActiveTexture to set which slot GL.BindTexture binds to. 84 | // The OpenGL standard requires that there be at least 16, but there can be more depending on your graphics card. 85 | public void Use(TextureUnit unit) 86 | { 87 | GL.ActiveTexture(unit); 88 | GL.BindTexture(TextureTarget.Texture2D, Handle); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LearnOpenTK 2 | For a more comprehensive written tutorial go to the [Learn section of OpenTK.net](https://opentk.net/learn/index.html) site. 3 | 4 | A port of [the tutorials at LearnOpenGL](https://learnopengl.com/) to C#/OpenTK. 5 | These tutorials serve as a complement to the website tutorial and contains working samples of the different chapters. 6 | 7 | --------------------------------------------------------------------------------