├── .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 |
--------------------------------------------------------------------------------