Repository: opentk/LearnOpenTK
Branch: master
Commit: 7b876bdb79fc
Files: 118
Total size: 270.9 KB
Directory structure:
gitextract_wlj10zxr/
├── .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
│ │ ├── Shaders/
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ ├── 6-MultipleTextures/
│ │ ├── 6-MultipleTextures.csproj
│ │ ├── Program.cs
│ │ ├── Shaders/
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ ├── 7-Transformations/
│ │ ├── 7-Transformations.csproj
│ │ ├── Program.cs
│ │ ├── Shaders/
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ ├── 8-CoordinatesSystems/
│ │ ├── 8-CoordinatesSystems.csproj
│ │ ├── Program.cs
│ │ ├── Shaders/
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ └── 9-Camera/
│ ├── 9-Camera.csproj
│ ├── Program.cs
│ ├── 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
│ │ ├── Shaders/
│ │ │ ├── lighting.frag
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ ├── 5-LightCasters-DirectionalLights/
│ │ ├── 5-LightCasters-DirectionalLights.csproj
│ │ ├── OpenTK.dll.config
│ │ ├── Program.cs
│ │ ├── Shaders/
│ │ │ ├── lighting.frag
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ ├── 5-LightCasters-PointLights/
│ │ ├── 5-LightCasters-PointLights.csproj
│ │ ├── OpenTK.dll.config
│ │ ├── Program.cs
│ │ ├── Shaders/
│ │ │ ├── lighting.frag
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ ├── 5-LightCasters-Spotlight/
│ │ ├── 5-LightCasters-Spotlight.csproj
│ │ ├── OpenTK.dll.config
│ │ ├── Program.cs
│ │ ├── Shaders/
│ │ │ ├── lighting.frag
│ │ │ ├── shader.frag
│ │ │ └── shader.vert
│ │ └── Window.cs
│ └── 6-MultipleLights/
│ ├── 6-MultipleLights.csproj
│ ├── OpenTK.dll.config
│ ├── Program.cs
│ ├── Shaders/
│ │ ├── lighting.frag
│ │ ├── shader.frag
│ │ └── shader.vert
│ └── Window.cs
├── Common/
│ ├── Camera.cs
│ ├── Common.csproj
│ ├── Shader.cs
│ └── Texture.cs
├── LICENSE
├── LearnOpenTK.sln
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
================================================
FILE: Chapter1/1-CreatingAWindow/1-CreatingAWindow.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/1-CreatingAWindow/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Creating a Window",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
// To create a new window, create a class that extends GameWindow, then call Run() on it.
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
// And that's it! That's all it takes to create a window with OpenTK.
}
}
}
================================================
FILE: Chapter1/1-CreatingAWindow/Window.cs
================================================
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// This is where all OpenGL code will be written.
// OpenToolkit allows for several functions to be overriden to extend functionality; this is how we'll be writing code.
public class Window : GameWindow
{
// A simple constructor to let us set properties like window size, title, FPS, etc. on the window.
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
// This function runs on every update frame.
protected override void OnUpdateFrame(FrameEventArgs e)
{
// Check if the Escape button is currently being pressed.
if (KeyboardState.IsKeyDown(Keys.Escape))
{
// If it is, close the window.
Close();
}
base.OnUpdateFrame(e);
}
}
}
================================================
FILE: Chapter1/2-HelloTriangle/2-HelloTriangle.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
PreserveNewest
PreserveNewest
================================================
FILE: Chapter1/2-HelloTriangle/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Creating a Window",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/2-HelloTriangle/Shaders/shader.frag
================================================
#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(1.0, 1.0, 0.0, 1.0);
}
================================================
FILE: Chapter1/2-HelloTriangle/Shaders/shader.vert
================================================
// For more information on how shaders work, check out the web version of this tutorial.
// I'll include a simpler summary here.
// First non-comment line should always be a #version statement; this just tells the GLSL compiler what version it should use.
#version 330 core
// GLSL's syntax is somewhat like C, but it has a few differences.
// There are four different types of variables in GLSL: input, output, uniform, and internal.
// - Input variables are sent from the buffer, in a way defined by GL.VertexAttribPointer.
// - Output variables are sent from this shader to the next one in the chain (which will be the fragment shader most of the time).
// - Uniforms will be touched on in the next tutorial.
// - Internal variables are defined in the shader file and only used there.
// The vertex shader is run once for every vertex. In C# pseudocode, it might look something like:
// foreach(var vertex in vertices)
// shader(vertex)
// This defines our input variable, aPosition.
// It starts with the line "layout(location = 0)". This defines where this input variable will be located, which is needed for GL.VertexAttribPointer.
// However, you can omit it, and replace this with just "in vec3 aPosition". If you do that, you'll have to replace the 0 in GL.VertexAttribPointer with
// a call to GL.GetAttribLocation(shaderHandle, attributeName)
// Next, the keyword "in" defines this as an input variable. We'll have an example of the "out" keyword in the next tutorial.
// Then, the keyword "vec3" means this is a vector with 3 floats inside.
layout(location = 0) in vec3 aPosition;
// Like C, we have an entrypoint function. In this case, it takes void and returns void, and must be named main.
// You can do all sorts of calculations here to modify your vertices, but right now, we don't need to do any of that.
// gl_Position is the final vertex position; pass a vec4 to it and you're done.
// Keep in mind that we only pass a vec3 to this shader; the fourth component of a vertex is known as "w".
// It's only used in some more advanced OpenGL functions; it's not needed here.
// So with a call to the vec4 function, we just give it a constant value of 1.0.
void main(void)
{
gl_Position = vec4(aPosition, 1.0);
}
================================================
FILE: Chapter1/2-HelloTriangle/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// Be warned, there is a LOT of stuff here. It might seem complicated, but just take it slow and you'll be fine.
// OpenGL's initial hurdle is quite large, but once you get past that, things will start making more sense.
public class Window : GameWindow
{
// Create the vertices for our triangle. These are listed in normalized device coordinates (NDC)
// In NDC, (0, 0) is the center of the screen.
// Negative X coordinates move to the left, positive X move to the right.
// Negative Y coordinates move to the bottom, positive Y move to the top.
// OpenGL only supports rendering in 3D, so to create a flat triangle, the Z coordinate will be kept as 0.
private readonly float[] _vertices =
{
-0.5f, -0.5f, 0.0f, // Bottom-left vertex
0.5f, -0.5f, 0.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f // Top vertex
};
// These are the handles to OpenGL objects. A handle is an integer representing where the object lives on the
// graphics card. Consider them sort of like a pointer; we can't do anything with them directly, but we can
// send them to OpenGL functions that need them.
// What these objects are will be explained in OnLoad.
private int _vertexBufferObject;
private int _vertexArrayObject;
// This class is a wrapper around a shader, which helps us manage it.
// The shader class's code is in the Common project.
// What shaders are and what they're used for will be explained later in this tutorial.
private Shader _shader;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
// Now, we start initializing OpenGL.
protected override void OnLoad()
{
base.OnLoad();
// This will be the color of the background after we clear it, in normalized colors.
// Normalized colors are mapped on a range of 0.0 to 1.0, with 0.0 representing black, and 1.0 representing
// the largest possible value for that channel.
// This is a deep green.
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// We need to send our vertices over to the graphics card so OpenGL can use them.
// To do this, we need to create what's called a Vertex Buffer Object (VBO).
// These allow you to upload a bunch of data to a buffer, and send the buffer to the graphics card.
// This effectively sends all the vertices at the same time.
// First, we need to create a buffer. This function returns a handle to it, but as of right now, it's empty.
_vertexBufferObject = GL.GenBuffer();
// Now, bind the buffer. OpenGL uses one global state, so after calling this,
// all future calls that modify the VBO will be applied to this buffer until another buffer is bound instead.
// The first argument is an enum, specifying what type of buffer we're binding. A VBO is an ArrayBuffer.
// There are multiple types of buffers, but for now, only the VBO is necessary.
// The second argument is the handle to our buffer.
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
// Finally, upload the vertices to the buffer.
// Arguments:
// Which buffer the data should be sent to.
// How much data is being sent, in bytes. You can generally set this to the length of your array, multiplied by sizeof(array type).
// The vertices themselves.
// How the buffer will be used, so that OpenGL can write the data to the proper memory space on the GPU.
// There are three different BufferUsageHints for drawing:
// StaticDraw: This buffer will rarely, if ever, update after being initially uploaded.
// DynamicDraw: This buffer will change frequently after being initially uploaded.
// StreamDraw: This buffer will change on every frame.
// Writing to the proper memory space is important! Generally, you'll only want StaticDraw,
// but be sure to use the right one for your use case.
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
// One notable thing about the buffer we just loaded data into is that it doesn't have any structure to it. It's just a bunch of floats (which are actaully just bytes).
// The opengl driver doesn't know how this data should be interpreted or how it should be divided up into vertices. To do this opengl introduces the idea of a
// Vertex Array Obejct (VAO) which has the job of keeping track of what parts or what buffers correspond to what data. In this example we want to set our VAO up so that
// it tells opengl that we want to interpret 12 bytes as 3 floats and divide the buffer into vertices using that.
// To do this we generate and bind a VAO (which looks deceptivly similar to creating and binding a VBO, but they are different!).
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
// Now, we need to setup how the vertex shader will interpret the VBO data; you can send almost any C datatype (and a few non-C ones too) to it.
// While this makes them incredibly flexible, it means we have to specify how that data will be mapped to the shader's input variables.
// To do this, we use the GL.VertexAttribPointer function
// This function has two jobs, to tell opengl about the format of the data, but also to associate the current array buffer with the VAO.
// This means that after this call, we have setup this attribute to source data from the current array buffer and interpret it in the way we specified.
// Arguments:
// Location of the input variable in the shader. the layout(location = 0) line in the vertex shader explicitly sets it to 0.
// How many elements will be sent to the variable. In this case, 3 floats for every vertex.
// The data type of the elements set, in this case float.
// Whether or not the data should be converted to normalized device coordinates. In this case, false, because that's already done.
// The stride; this is how many bytes are between the last element of one vertex and the first element of the next. 3 * sizeof(float) in this case.
// The offset; this is how many bytes it should skip to find the first element of the first vertex. 0 as of right now.
// Stride and Offset are just sort of glossed over for now, but when we get into texture coordinates they'll be shown in better detail.
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
// Enable variable 0 in the shader.
GL.EnableVertexAttribArray(0);
// We've got the vertices done, but how exactly should this be converted to pixels for the final image?
// Modern OpenGL makes this pipeline very free, giving us a lot of freedom on how vertices are turned to pixels.
// The drawback is that we actually need two more programs for this! These are called "shaders".
// Shaders are tiny programs that live on the GPU. OpenGL uses them to handle the vertex-to-pixel pipeline.
// Check out the Shader class in Common to see how we create our shaders, as well as a more in-depth explanation of how shaders work.
// shader.vert and shader.frag contain the actual shader code.
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
// Now, enable the shader.
// Just like the VBO, this is global, so every function that uses a shader will modify this one until a new one is bound instead.
_shader.Use();
// Setup is now complete! Now we move to the OnRenderFrame function to finally draw the triangle.
}
// Now that initialization is done, let's create our render loop.
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
// This clears the image, using what you set as GL.ClearColor earlier.
// OpenGL provides several different types of data that can be rendered.
// You can clear multiple buffers by using multiple bit flags.
// However, we only modify the color, so ColorBufferBit is all we need to clear.
GL.Clear(ClearBufferMask.ColorBufferBit);
// To draw an object in OpenGL, it's typically as simple as binding your shader,
// setting shader uniforms (not done here, will be shown in a future tutorial)
// binding the VAO,
// and then calling an OpenGL function to render.
// Bind the shader
_shader.Use();
// Bind the VAO
GL.BindVertexArray(_vertexArrayObject);
// And then call our drawing function.
// For this tutorial, we'll use GL.DrawArrays, which is a very simple rendering function.
// Arguments:
// Primitive type; What sort of geometric primitive the vertices represent.
// OpenGL used to support many different primitive types, but almost all of the ones still supported
// is some variant of a triangle. Since we just want a single triangle, we use Triangles.
// Starting index; this is just the start of the data you want to draw. 0 here.
// How many vertices you want to draw. 3 for a triangle.
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
// OpenTK windows are what's known as "double-buffered". In essence, the window manages two buffers.
// One is rendered to while the other is currently displayed by the window.
// This avoids screen tearing, a visual artifact that can happen if the buffer is modified while being displayed.
// After drawing, call this function to swap the buffers. If you don't, it won't display what you've rendered.
SwapBuffers();
// And that's all you have to do for rendering! You should now see a yellow triangle on a black screen.
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
// When the window gets resized, we have to call GL.Viewport to resize OpenGL's viewport to match the new size.
// If we don't, the NDC will no longer be correct.
GL.Viewport(0, 0, Size.X, Size.Y);
}
// Now, for cleanup.
// You should generally not do cleanup of opengl resources when exiting an application,
// as that is handled by the driver and operating system when the application exits.
//
// There are reasons to delete opengl resources, but exiting the application is not one of them.
// This is provided here as a reference on how resource cleanup is done in opengl, but
// should not be done when exiting the application.
//
// Places where cleanup is appropriate would be: to delete textures that are no
// longer used for whatever reason (e.g. a new scene is loaded that doesn't use a texture).
// This would free up video ram (VRAM) that can be used for new textures.
//
// The coming chapters will not have this code.
protected override void OnUnload()
{
// Unbind all the resources by binding the targets to 0/null.
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
GL.BindVertexArray(0);
GL.UseProgram(0);
// Delete all the resources.
GL.DeleteBuffer(_vertexBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shader.Handle);
base.OnUnload();
}
}
}
================================================
FILE: Chapter1/3-ElementBufferObjects/3-ElementBufferObjects.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/3-ElementBufferObjects/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Element Buffer Objects",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/3-ElementBufferObjects/Shaders/shader.frag
================================================
#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(1.0, 1.0, 0.0, 1.0);
}
================================================
FILE: Chapter1/3-ElementBufferObjects/Shaders/shader.vert
================================================
#version 330 core
layout(location = 0) in vec3 aPosition;
void main(void)
{
gl_Position = vec4(aPosition, 1.0);
}
================================================
FILE: Chapter1/3-ElementBufferObjects/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// So you've drawn the first triangle. But what about drawing multiple?
// You may consider just adding more vertices to the array, and that would technically work, but say you're drawing a rectangle.
// It only needs four vertices, but since OpenGL works in triangles, you'd need to define 6.
// 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
// doing it that way would need 36 vertices!
// OpenGL provides a way to reuse vertices, which can heavily reduce memory usage on complex objects.
// This is called an Element Buffer Object. This tutorial will be all about how to set one up.
public class Window : GameWindow
{
// We modify the vertex array to include four vertices for our rectangle.
private readonly float[] _vertices =
{
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, // top left
};
// Then, we create a new array: indices.
// This array controls how the EBO will use those vertices to create triangles
private readonly uint[] _indices =
{
// Note that indices start at 0!
0, 1, 3, // The first triangle will be the top-right half of the triangle
1, 2, 3 // Then the second will be the bottom-left half of the triangle
};
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
// Add a handle for the EBO
private int _elementBufferObject;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
// 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.
// The binding spot for ElementArrayBuffer is not actually a global binding spot like ArrayBuffer is.
// Instead it's actually a property of the currently bound VertexArrayObject, and binding an EBO with no VAO is undefined behaviour.
// This also means that if you bind another VAO, the current ElementArrayBuffer is going to change with it.
// Another sneaky part is that you don't need to unbind the buffer in ElementArrayBuffer as unbinding the VAO is going to do this,
// and unbinding the EBO will remove it from the VAO instead of unbinding it like you would for VBOs or VAOs.
_elementBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
// We also upload data to the EBO the same way as we did with VBOs.
GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw);
// The EBO has now been properly setup. Go to the Render function to see how we draw our rectangle now!
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
_shader.Use();
// Because ElementArrayObject is a property of the currently bound VAO,
// the buffer you will find in the ElementArrayBuffer will change with the currently bound VAO.
GL.BindVertexArray(_vertexArrayObject);
// Then replace your call to DrawTriangles with one to DrawElements
// Arguments:
// Primitive type to draw. Triangles in this case.
// How many indices should be drawn. Six in this case.
// Data type of the indices. The indices are an unsigned int, so we want that here too.
// Offset in the EBO. Set this to 0 because we want to draw the whole thing.
GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/4-Shaders-InsAndOuts/4-Shaders-InsAndOuts.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/4-Shaders-InsAndOuts/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Shaders In and Outs!",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/4-Shaders-InsAndOuts/Shaders/shader.frag
================================================
#version 330 core
out vec4 outputColor;
// This is where the color variable we declared and assigned in vertex shader
// Gets pass to, this is enabled by using the in keyword
// Keep in mind the vec type must match in order for this to work
in vec4 vertexColor;
void main()
{
outputColor = vertexColor;
}
================================================
FILE: Chapter1/4-Shaders-InsAndOuts/Shaders/shader.vert
================================================
#version 330 core
// the position variable has attribute position 0
layout(location = 0) in vec3 aPosition;
// This variable uses the keyword out in order to pass on the value to the
// next shader down in the chain, in this case the frag shader
out vec4 vertexColor;
void main(void)
{
// see how we directly give a vec3 to vec4's constructor
gl_Position = vec4(aPosition, 1.0);
// Here we assign the variable a dark red color to the out variable
vertexColor = vec4(0.5, 0.0, 0.0, 1.0);
}
================================================
FILE: Chapter1/4-Shaders-InsAndOuts/Window.cs
================================================
using System;
using OpenTK.Graphics.OpenGL4;
using LearnOpenTK.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using System.Diagnostics;
namespace LearnOpenTK
{ // Here we'll be elaborating on what shaders can do from the Hello World project we worked on before.
// Specifically we'll be showing how shaders deal with input and output from the main program
// and between each other.
public class Window : GameWindow
{
private readonly float[] _vertices =
{
-0.5f, -0.5f, 0.0f, // Bottom-left vertex
0.5f, -0.5f, 0.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f // Top vertex
};
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
// Vertex attributes are the data we send as input into the vertex shader from the main program.
// So here we're checking to see how many vertex attributes our hardware can handle.
// OpenGL at minimum supports 16 vertex attributes. This only needs to be called
// when your intensive attribute work and need to know exactly how many are available to you.
GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount);
Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}");
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
_shader.Use();
GL.BindVertexArray(_vertexArrayObject);
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/4-Shaders-MoreAttributes/4-Shaders-MoreAttributes.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/4-Shaders-MoreAttributes/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Shaders More Attributes!",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/4-Shaders-MoreAttributes/Shaders/shader.frag
================================================
#version 330 core
out vec4 outputColor;
in vec3 ourColor;
void main()
{
outputColor = vec4(ourColor, 1.0);
}
================================================
FILE: Chapter1/4-Shaders-MoreAttributes/Shaders/shader.vert
================================================
#version 330 core
// the position variable has attribute position 0
layout(location = 0) in vec3 aPosition;
// This is where the color values we assigned in the main program goes to
layout(location = 1) in vec3 aColor;
out vec3 ourColor; // output a color to the fragment shader
void main(void)
{
// see how we directly give a vec3 to vec4's constructor
gl_Position = vec4(aPosition, 1.0);
// We use the outColor variable to pass on the color information to the frag shader
ourColor = aColor;
}
================================================
FILE: Chapter1/4-Shaders-MoreAttributes/Window.cs
================================================
using System;
using OpenTK.Graphics.OpenGL4;
using LearnOpenTK.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using System.Diagnostics;
namespace LearnOpenTK
{
// In this project, we will be assigning 3 colors to the triangle, one for each vertex.
// The output will be an interpolated value based on the distance from each vertex.
// If you want to look more into it, the in-between step is called a Rasterizer.
public class Window : GameWindow
{
// We're assigning three different colors at the asscoiate vertex position:
// blue for the top, green for the bottom left and red for the bottom right.
private readonly float[] _vertices =
{
// positions // colors
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
// Now, we start initializing OpenGL.
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
// Just like before, we create a pointer for the 3 position components of our vertices.
// The only difference here is that we need to account for the 3 color values in the stride variable.
// Therefore, the stride contains the size of 6 floats instead of 3.
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
// We create a new pointer for the color values.
// Much like the previous pointer, we assign 6 in the stride value.
// We also need to correctly set the offset to get the color values.
// The color data starts after the position data, so the offset is the size of 3 floats.
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float));
// We then enable color attribute (location=1) so it is available to the shader.
GL.EnableVertexAttribArray(1);
GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount);
Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}");
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
_shader.Use();
GL.BindVertexArray(_vertexArrayObject);
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/4-Shaders-Uniforms/4-Shaders-Uniforms.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/4-Shaders-Uniforms/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Shaders Uniforms!",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/4-Shaders-Uniforms/Shaders/shader.frag
================================================
#version 330 core
out vec4 outputColor;
// The Uniform keyword allows you to access a shader variable at any stage of the shader chain
// It's also accessible across all of the main program
// Whatever you set this variable to it keeps it until you either reset the value or updated it
uniform vec4 ourColor;
void main()
{
outputColor = ourColor;
}
================================================
FILE: Chapter1/4-Shaders-Uniforms/Shaders/shader.vert
================================================
#version 330 core
layout(location = 0) in vec3 aPosition; // the position variable has attribute position 0
void main(void)
{
// see how we directly give a vec3 to vec4's constructor
gl_Position = vec4(aPosition, 1.0);
}
================================================
FILE: Chapter1/4-Shaders-Uniforms/Window.cs
================================================
using System;
using System.Diagnostics;
using OpenTK.Graphics.OpenGL4;
using LearnOpenTK.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace LearnOpenTK
{
// This project will explore how to use uniform variable type which allows you to assign values
// to shaders at any point during the project.
public class Window : GameWindow
{
private readonly float[] _vertices =
{
-0.5f, -0.5f, 0.0f, // Bottom-left vertex
0.5f, -0.5f, 0.0f, // Bottom-right vertex
0.0f, 0.5f, 0.0f // Top vertex
};
// So we're going make the triangle pulsate between a color range.
// In order to do that, we'll need a constantly changing value.
// The stopwatch is perfect for this as it is constantly going up.
private Stopwatch _timer;
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
GL.GetInteger(GetPName.MaxVertexAttribs, out int maxAttributeCount);
Debug.WriteLine($"Maximum number of vertex attributes supported: {maxAttributeCount}");
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
// We start the stopwatch here as this method is only called once.
_timer = new Stopwatch();
_timer.Start();
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
_shader.Use();
// Here, we get the total seconds that have elapsed since the last time this method has reset
// and we assign it to the timeValue variable so it can be used for the pulsating color.
double timeValue = _timer.Elapsed.TotalSeconds;
// We're increasing / decreasing the green value we're passing into
// the shader based off of timeValue we created in the previous line,
// as well as using some built in math functions to help the change be smoother.
float greenValue = (float)Math.Sin(timeValue) / 2.0f + 0.5f;
// This gets the uniform variable location from the frag shader so that we can
// assign the new green value to it.
int vertexColorLocation = GL.GetUniformLocation(_shader.Handle, "ourColor");
// Here we're assigning the ourColor variable in the frag shader
// via the OpenGL Uniform method which takes in the value as the individual vec values (which total 4 in this instance).
GL.Uniform4(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
// You can alternatively use this overload of the same function.
// GL.Uniform4(vertexColorLocation, new OpenTK.Mathematics.Color4(0f, greenValue, 0f, 0f));
// Bind the VAO
GL.BindVertexArray(_vertexArrayObject);
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/5-Textures/5-Textures.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/5-Textures/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Textures",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/5-Textures/Shaders/shader.frag
================================================
#version 330
out vec4 outputColor;
in vec2 texCoord;
// A sampler2d is the representation of a texture in a shader.
// Each sampler is bound to a texture unit (texture units are described in Texture.cs on the Use function).
// By default, the unit is 0, so no code-related setup is actually needed.
// Multiple samplers will be demonstrated in section 1.5.
uniform sampler2D texture0;
void main()
{
// To use a texture, you call the texture() function.
// It takes two parameters: the sampler to use, and a vec2, used as texture coordinates.
outputColor = texture(texture0, texCoord);
}
================================================
FILE: Chapter1/5-Textures/Shaders/shader.vert
================================================
#version 330 core
layout(location = 0) in vec3 aPosition;
// We add another input variable for the texture coordinates.
layout(location = 1) in vec2 aTexCoord;
// ...However, they aren't needed for the vertex shader itself.
// Instead, we create an output variable so we can send that data to the fragment shader.
out vec2 texCoord;
void main(void)
{
// Then, we further the input texture coordinate to the output one.
// texCoord can now be used in the fragment shader.
texCoord = aTexCoord;
gl_Position = vec4(aPosition, 1.0);
}
================================================
FILE: Chapter1/5-Textures/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public class Window : GameWindow
{
// Because we're adding a texture, we modify the vertex array to include texture coordinates.
// 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.
// The new layout is three floats to create a vertex, then two floats to create the coordinates.
private readonly float[] _vertices =
{
// Position Texture coordinates
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
private readonly uint[] _indices =
{
0, 1, 3,
1, 2, 3
};
private int _elementBufferObject;
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
// For documentation on this, check Texture.cs.
private Texture _texture;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_elementBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw);
// The shaders have been modified to include the texture coordinates, check them out after finishing the OnLoad function.
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
// Because there's now 5 floats between the start of the first vertex and the start of the second,
// we modify the stride from 3 * sizeof(float) to 5 * sizeof(float).
// This will now pass the new vertex array to the buffer.
var vertexLocation = _shader.GetAttribLocation("aPosition");
GL.EnableVertexAttribArray(vertexLocation);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
// Next, we also setup texture coordinates. It works in much the same way.
// We add an offset of 3, since the texture coordinates comes after the position data.
// We also change the amount of data to 2 because there's only 2 floats for texture coordinates.
var texCoordLocation = _shader.GetAttribLocation("aTexCoord");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
_texture = Texture.LoadFromFile("Resources/container.png");
_texture.Use(TextureUnit.Texture0);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BindVertexArray(_vertexArrayObject);
_texture.Use(TextureUnit.Texture0);
_shader.Use();
GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/6-MultipleTextures/6-MultipleTextures.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/6-MultipleTextures/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Multiple Textures",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/6-MultipleTextures/Shaders/shader.frag
================================================
#version 330
out vec4 outputColor;
in vec2 texCoord;
uniform sampler2D texture0;
uniform sampler2D texture1;
void main()
{
outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2);
}
================================================
FILE: Chapter1/6-MultipleTextures/Shaders/shader.vert
================================================
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 texCoord;
void main(void)
{
texCoord = aTexCoord;
gl_Position = vec4(aPosition, 1.0);
}
================================================
FILE: Chapter1/6-MultipleTextures/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Position Texture coordinates
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
private readonly uint[] _indices =
{
0, 1, 3,
1, 2, 3
};
private int _elementBufferObject;
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
private Texture _texture;
private Texture _texture2;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_elementBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw);
// shader.frag has been modified yet again, take a look at it as well.
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
var vertexLocation = _shader.GetAttribLocation("aPosition");
GL.EnableVertexAttribArray(vertexLocation);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
var texCoordLocation = _shader.GetAttribLocation("aTexCoord");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
_texture = Texture.LoadFromFile("Resources/container.png");
// Texture units are explained in Texture.cs, at the Use function.
// First texture goes in texture unit 0.
_texture.Use(TextureUnit.Texture0);
// This is helpful because System.Drawing reads the pixels differently than OpenGL expects.
_texture2 = Texture.LoadFromFile("Resources/awesomeface.png");
// Then, the second goes in texture unit 1.
_texture2.Use(TextureUnit.Texture1);
// Next, we must setup the samplers in the shaders to use the right textures.
// The int we send to the uniform indicates which texture unit the sampler should use.
_shader.SetInt("texture0", 0);
_shader.SetInt("texture1", 1);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BindVertexArray(_vertexArrayObject);
_texture.Use(TextureUnit.Texture0);
_texture2.Use(TextureUnit.Texture1);
_shader.Use();
GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/7-Transformations/7-Transformations.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/7-Transformations/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Transformations",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/7-Transformations/Shaders/shader.frag
================================================
#version 330
out vec4 outputColor;
in vec2 texCoord;
uniform sampler2D texture0;
uniform sampler2D texture1;
void main()
{
outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2);
}
================================================
FILE: Chapter1/7-Transformations/Shaders/shader.vert
================================================
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 texCoord;
// Add a uniform for the transformation matrix.
uniform mat4 transform;
void main(void)
{
texCoord = aTexCoord;
// Then all you have to do is multiply the vertices by the transformation matrix, and you'll see your transformation in the scene!
gl_Position = vec4(aPosition, 1.0) * transform;
}
================================================
FILE: Chapter1/7-Transformations/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// So you can setup OpenGL, you can draw basic shapes without wasting vertices, and you can texture them.
// There's one big thing left, though: moving the shapes.
// To do this, we use linear algebra to move the vertices in the vertex shader.
// Just as a disclaimer: this tutorial will NOT explain linear algebra or matrices; those topics are wayyyyy too complex to do with comments.
// If you want a more detailed understanding of what's going on here, look at the web version of this tutorial instead.
// 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.
// Head down to RenderFrame to see how we can apply transformations to our shape.
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Position Texture coordinates
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
private readonly uint[] _indices =
{
0, 1, 3,
1, 2, 3
};
private int _elementBufferObject;
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
private Texture _texture;
private Texture _texture2;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_elementBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw);
// shader.vert has been modified, take a look at it as well.
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
var vertexLocation = _shader.GetAttribLocation("aPosition");
GL.EnableVertexAttribArray(vertexLocation);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
var texCoordLocation = _shader.GetAttribLocation("aTexCoord");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
_texture = Texture.LoadFromFile("Resources/container.png");
_texture.Use(TextureUnit.Texture0);
_texture2 = Texture.LoadFromFile("Resources/awesomeface.png");
_texture2.Use(TextureUnit.Texture1);
_shader.SetInt("texture0", 0);
_shader.SetInt("texture1", 1);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BindVertexArray(_vertexArrayObject);
// Note: The matrices we'll use for transformations are all 4x4.
// We start with an identity matrix. This is just a simple matrix that doesn't move the vertices at all.
var transform = Matrix4.Identity;
// The next few steps just show how to use OpenTK's matrix functions, and aren't necessary for the transform matrix to actually work.
// If you want, you can just pass the identity matrix to the shader, though it won't affect the vertices at all.
// A fact to note about matrices is that the order of multiplications matter. "matrixA * matrixB" and "matrixB * matrixA" mean different things.
// 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:
// https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/geometry/row-major-vs-column-major-vector
// What it means for us is that we can think of matrix multiplication as going left to right.
// 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).
// 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.
// Note that all Matrix4.CreateRotation functions take radians, not degrees. Use MathHelper.DegreesToRadians() to convert to radians, if you want to use degrees.
transform = transform * Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(20f));
// Next, we scale the matrix. This will make the rectangle slightly larger.
transform = transform * Matrix4.CreateScale(1.1f);
// Then, we translate the matrix, which will move it slightly towards the top-right.
// Note that we aren't using a full coordinate system yet, so the translation is in normalized device coordinates.
// The next tutorial will be about how to set one up so we can use more human-readable numbers.
transform = transform * Matrix4.CreateTranslation(0.1f, 0.1f, 0.0f);
_texture.Use(TextureUnit.Texture0);
_texture2.Use(TextureUnit.Texture1);
_shader.Use();
// Now that the matrix is finished, pass it to the vertex shader.
// Go over to shader.vert to see how we finally apply this to the vertices.
_shader.SetMatrix4("transform", transform);
// And that's it for now! In the next tutorial, we'll see how to setup a full coordinates system.
GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/8-CoordinatesSystems/8-CoordinatesSystems.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/8-CoordinatesSystems/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Coordinates Systems",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/8-CoordinatesSystems/Shaders/shader.frag
================================================
#version 330
out vec4 outputColor;
in vec2 texCoord;
uniform sampler2D texture0;
uniform sampler2D texture1;
void main()
{
outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2);
}
================================================
FILE: Chapter1/8-CoordinatesSystems/Shaders/shader.vert
================================================
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 texCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main(void)
{
texCoord = aTexCoord;
gl_Position = vec4(aPosition, 1.0) * model * view * projection;
}
================================================
FILE: Chapter1/8-CoordinatesSystems/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// We can now move around objects. However, how can we move our "camera", or modify our perspective?
// In this tutorial, I'll show you how to setup a full projection/view/model (PVM) matrix.
// In addition, we'll make the rectangle rotate over time.
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Position Texture coordinates
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
private readonly uint[] _indices =
{
0, 1, 3,
1, 2, 3
};
private int _elementBufferObject;
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
private Texture _texture;
private Texture _texture2;
// We create a double to hold how long has passed since the program was opened.
private double _time;
// Then, we create two matrices to hold our view and projection. They're initialized at the bottom of OnLoad.
// The view matrix is what you might consider the "camera". It represents the current viewport in the window.
private Matrix4 _view;
// This represents how the vertices will be projected. It's hard to explain through comments,
// so check out the web version for a good demonstration of what this does.
private Matrix4 _projection;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// We enable depth testing here. If you try to draw something more complex than one plane without this,
// you'll notice that polygons further in the background will occasionally be drawn over the top of the ones in the foreground.
// Obviously, we don't want this, so we enable depth testing. We also clear the depth buffer in GL.Clear over in OnRenderFrame.
GL.Enable(EnableCap.DepthTest);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_elementBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw);
// shader.vert has been modified. Take a look at it after the explanation in OnRenderFrame.
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
var vertexLocation = _shader.GetAttribLocation("aPosition");
GL.EnableVertexAttribArray(vertexLocation);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
var texCoordLocation = _shader.GetAttribLocation("aTexCoord");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
_texture = Texture.LoadFromFile("Resources/container.png");
_texture.Use(TextureUnit.Texture0);
_texture2 = Texture.LoadFromFile("Resources/awesomeface.png");
_texture2.Use(TextureUnit.Texture1);
_shader.SetInt("texture0", 0);
_shader.SetInt("texture1", 1);
// For the view, we don't do too much here. Next tutorial will be all about a Camera class that will make it much easier to manipulate the view.
// For now, we move it backwards three units on the Z axis.
_view = Matrix4.CreateTranslation(0.0f, 0.0f, -3.0f);
// For the matrix, we use a few parameters.
// 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
// Aspect ratio. This should be set to Width / Height.
// Near-clipping. Any vertices closer to the camera than this value will be clipped.
// Far-clipping. Any vertices farther away from the camera than this value will be clipped.
_projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(45f), Size.X / (float) Size.Y, 0.1f, 100.0f);
// Now, head over to OnRenderFrame to see how we setup the model matrix.
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
// We add the time elapsed since last frame, times 4.0 to speed up animation, to the total amount of time passed.
_time += 4.0 * e.Time;
// We clear the depth buffer in addition to the color buffer.
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vertexArrayObject);
_texture.Use(TextureUnit.Texture0);
_texture2.Use(TextureUnit.Texture1);
_shader.Use();
// Finally, we have the model matrix. This determines the position of the model.
var model = Matrix4.Identity * Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time));
// Then, we pass all of these matrices to the vertex shader.
// You could also multiply them here and then pass, which is faster, but having the separate matrices available is used for some advanced effects.
// IMPORTANT: OpenTK's matrix types are transposed from what OpenGL would expect - rows and columns are reversed.
// They are then transposed properly when passed to the shader.
// This means that we retain the same multiplication order in both OpenTK c# code and GLSL shader code.
// If you pass the individual matrices to the shader and multiply there, you have to do in the order "model * view * projection".
// You can think like this: first apply the modelToWorld (aka model) matrix, then apply the worldToView (aka view) matrix,
// and finally apply the viewToProjectedSpace (aka projection) matrix.
_shader.SetMatrix4("model", model);
_shader.SetMatrix4("view", _view);
_shader.SetMatrix4("projection", _projection);
GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
}
}
}
================================================
FILE: Chapter1/9-Camera/9-Camera.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter1/9-Camera/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Camera",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter1/9-Camera/Shaders/shader.frag
================================================
#version 330
out vec4 outputColor;
in vec2 texCoord;
uniform sampler2D texture0;
uniform sampler2D texture1;
void main()
{
outputColor = mix(texture(texture0, texCoord), texture(texture1, texCoord), 0.2);
}
================================================
FILE: Chapter1/9-Camera/Shaders/shader.vert
================================================
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 texCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main(void)
{
texCoord = aTexCoord;
gl_Position = vec4(aPosition, 1.0) * model * view * projection;
}
================================================
FILE: Chapter1/9-Camera/Window.cs
================================================
using System;
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// We now have a rotating rectangle but how can we make the view move based on the users input?
// In this tutorial we will take a look at how you could implement a camera class
// and start responding to user input.
// You can move to the camera class to see a lot of the new code added.
// Otherwise you can move to Load to see how the camera is initialized.
// In reality, we can't move the camera but we actually move the rectangle.
// This will explained more in depth in the web version, however it pretty much gives us the same result
// as if the view itself was moved.
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Position Texture coordinates
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
private readonly uint[] _indices =
{
0, 1, 3,
1, 2, 3
};
private int _elementBufferObject;
private int _vertexBufferObject;
private int _vertexArrayObject;
private Shader _shader;
private Texture _texture;
private Texture _texture2;
// The view and projection matrices have been removed as we don't need them here anymore.
// They can now be found in the new camera class.
// We need an instance of the new camera class so it can manage the view and projection matrix code.
// We also need a boolean set to true to detect whether or not the mouse has been moved for the first time.
// Finally, we add the last position of the mouse so we can calculate the mouse offset easily.
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
private double _time;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_elementBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
GL.BufferData(BufferTarget.ElementArrayBuffer, _indices.Length * sizeof(uint), _indices, BufferUsageHint.StaticDraw);
_shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
_shader.Use();
var vertexLocation = _shader.GetAttribLocation("aPosition");
GL.EnableVertexAttribArray(vertexLocation);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
var texCoordLocation = _shader.GetAttribLocation("aTexCoord");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
_texture = Texture.LoadFromFile("Resources/container.png");
_texture.Use(TextureUnit.Texture0);
_texture2 = Texture.LoadFromFile("Resources/awesomeface.png");
_texture2.Use(TextureUnit.Texture1);
_shader.SetInt("texture0", 0);
_shader.SetInt("texture1", 1);
// We initialize the camera so that it is 3 units back from where the rectangle is.
// We also give it the proper aspect ratio.
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
// We make the mouse cursor invisible and captured so we can have proper FPS-camera movement.
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
_time += 4.0 * e.Time;
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vertexArrayObject);
_texture.Use(TextureUnit.Texture0);
_texture2.Use(TextureUnit.Texture1);
_shader.Use();
var model = Matrix4.Identity * Matrix4.CreateRotationX((float)MathHelper.DegreesToRadians(_time));
_shader.SetMatrix4("model", model);
_shader.SetMatrix4("view", _camera.GetViewMatrix());
_shader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawElements(PrimitiveType.Triangles, _indices.Length, DrawElementsType.UnsignedInt, 0);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused) // Check to see if the window is focused
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
// Get the mouse state
var mouse = MouseState;
if (_firstMove) // This bool variable is initially set to true.
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
// Calculate the offset of the mouse position
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
// Apply the camera pitch and yaw (we clamp the pitch in the camera class)
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity; // Reversed since y-coordinates range from bottom to top
}
}
// In the mouse wheel function, we manage all the zooming of the camera.
// This is simply done by changing the FOV of the camera.
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
// We need to update the aspect ratio once the window has been resized.
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/1-Colors/1-Colors.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter2/1-Colors/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/1-Colors/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Colors",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/1-Colors/Shaders/lighting.frag
================================================
#version 330 core
out vec4 FragColor;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
// For our physically based coloring we simply want to multiply the color of the light with the objects color
// A much better and in depth explanation of this in the web tutorials.
FragColor = vec4(lightColor * objectColor, 1.0);
}
================================================
FILE: Chapter2/1-Colors/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/1-Colors/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
}
================================================
FILE: Chapter2/1-Colors/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// In this chapter we will focus on how to use lighting to make our games and other applications more lifelike
// In this first part the focus will mainly be on setting up a scene for testing the different coloring options.
// We draw two cubes one at 0,0,0 for testing our light shader, the second one is drawn where we have the light.
// Furthermore in the shaders we have set up some basic physically based coloring.
public class Window : GameWindow
{
// The vertices are now used to draw cubes.
// For this example, we aren't using texture coordinates.
// 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
private readonly float[] _vertices =
{
// Position
-0.5f, -0.5f, -0.5f, // Front face
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f, // Back face
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f, // Left face
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f, // Right face
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f, // Bottom face
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f, // Top face
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f
};
// This is the position of both the light and the place the lamp cube will be drawn in the scene
private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f);
private int _vertexBufferObject;
// I renamed the vertex array object since we now want two VAO's one for the model (the big cube for testing light shaders),
// and one for the lamp so we can see where the light source comes from.
// 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.
private int _vaoModel;
private int _vaoLamp;
// We also need two shaders, one for the lamp and one for our lighting material.
// The lighting shader is where most of this chapter will take place as this is where a lot of the lighting "magic" happens.
private Shader _lampShader;
private Shader _lightingShader;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
// Load the two different shaders, they use the same vertex shader program. However they have two different fragment shaders.
// This is because the lamp only uses a basic shader to turn it white, it wouldn't make sense to have the lamp lit in other colors.
// The lighting shaders uses the lighting.frag shader which is what a large part of this chapter will be about
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
// Initialize the vao for the model
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
var vertexLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(vertexLocation);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
}
{
// Initialize the vao for the lamp, this is mostly the same as the code for the model cube
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
// Set the vertex attributes (only position data for our lamp)
var vertexLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(vertexLocation);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
}
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
// Draw the model/cube with the lighting shader
GL.BindVertexArray(_vaoModel);
_lightingShader.Use();
// Matrix4.Identity is used as the matrix, since we just want to draw it at 0, 0, 0
_lightingShader.SetMatrix4("model", Matrix4.Identity);
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("objectColor", new Vector3(1.0f, 0.5f, 0.31f));
_lightingShader.SetVector3("lightColor", new Vector3(1.0f, 1.0f, 1.0f));
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
// Draw the lamp, this is mostly the same as for the model cube
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
Matrix4 lampMatrix = Matrix4.CreateScale(0.2f); // We scale the lamp cube down a bit to make it less dominant
lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos);
_lampShader.SetMatrix4("model", lampMatrix);
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/2-BasicLighting/2-BasicLighting.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter2/2-BasicLighting/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/2-BasicLighting/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Basic lighting",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/2-BasicLighting/Shaders/lighting.frag
================================================
#version 330 core
out vec4 FragColor;
//In order to calculate some basic lighting we need a few things per model basis, and a few things per fragment basis:
uniform vec3 objectColor; //The color of the object.
uniform vec3 lightColor; //The color of the light.
uniform vec3 lightPos; //The position of the light.
uniform vec3 viewPos; //The position of the view and/or of the player.
in vec3 Normal; //The normal of the fragment is calculated in the vertex shader.
in vec3 FragPos; //The fragment position.
void main()
{
//The ambient color is the color where the light does not directly hit the object.
//You can think of it as an underlying tone throughout the object. Or the light coming from the scene/the sky (not the sun).
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
//We calculate the light direction, and make sure the normal is normalized.
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos); //Note: The light is pointing from the light to the fragment
//The diffuse part of the phong model.
//This is the part of the light that gives the most, it is the color of the object where it is hit by light.
float diff = max(dot(norm, lightDir), 0.0); //We make sure the value is non negative with the max function.
vec3 diffuse = diff * lightColor;
//The specular light is the light that shines from the object, like light hitting metal.
//The calculations are explained much more detailed in the web version of the tutorials.
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); //The 32 is the shininess of the material.
vec3 specular = specularStrength * spec * lightColor;
//At last we add all the light components together and multiply with the color of the object. Then we set the color
//and makes sure the alpha value is 1
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
//Note we still use the light color * object color from the last tutorial.
//This time the light values are in the phong model (ambient, diffuse and specular)
}
================================================
FILE: Chapter2/2-BasicLighting/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/2-BasicLighting/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec3 FragPos;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
FragPos = vec3(vec4(aPos, 1.0) * model);
Normal = aNormal * mat3(transpose(inverse(model)));
}
================================================
FILE: Chapter2/2-BasicLighting/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// In this tutorial we set up some basic lighting and look at how the phong model works
// For more insight into how it all works look at the web version. If you are just here for the source,
// most of the changes are in the shaders, specifically most of the changes are in the fragment shader as this is
// where the lighting calculations happens.
public class Window : GameWindow
{
// Here we now have added the normals of the vertices
// Remember to define the layouts to the VAO's
private readonly float[] _vertices =
{
// Position Normal
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, // Front face
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, // Back face
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, // Left face
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Right face
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, // Bottom face
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // Top face
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
};
private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f);
private int _vertexBufferObject;
private int _vaoModel;
private int _vaoLamp;
private Shader _lampShader;
private Shader _lightingShader;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
var positionLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
// Remember to change the stride as we now have 6 floats per vertex
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0);
// We now need to define the layout of the normal so the shader can use it
var normalLocation = _lightingShader.GetAttribLocation("aNormal");
GL.EnableVertexAttribArray(normalLocation);
GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float));
}
{
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
var positionLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
// Also change the stride here as we now have 6 floats per vertex. Now we don't define the normal for the lamp VAO
// this is because it isn't used, it might seem like a waste to use the same VBO if they dont have the same data
// The two cubes still use the same position, and since the position is already in the graphics memory it is actually
// better to do it this way. Look through the web version for a much better understanding of this.
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0);
}
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vaoModel);
_lightingShader.Use();
_lightingShader.SetMatrix4("model", Matrix4.Identity);
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("objectColor", new Vector3(1.0f, 0.5f, 0.31f));
_lightingShader.SetVector3("lightColor", new Vector3(1.0f, 1.0f, 1.0f));
_lightingShader.SetVector3("lightPos", _lightPos);
_lightingShader.SetVector3("viewPos", _camera.Position);
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
Matrix4 lampMatrix = Matrix4.CreateScale(0.2f);
lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos);
_lampShader.SetMatrix4("model", lampMatrix);
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/3-Materials/3-Materials.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter2/3-Materials/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/3-Materials/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Materials",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/3-Materials/Shaders/lighting.frag
================================================
#version 330 core
//The material is a collection of some values that we talked about in the last tutorial,
//some crucial elements to the phong model.
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess; //Shininess is the power the specular light is raised to
};
//The light contains all the values from the light source, how the ambient diffuse and specular values are from the light source.
//This is technically what we were using in the last episode as we were only applying the phong model directly to the light.
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
//We create the light and the material struct as uniforms.
uniform Light light;
uniform Material material;
//We still need the view position.
uniform vec3 viewPos;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
void main()
{
//ambient
vec3 ambient = light.ambient * material.ambient; //Remember to use the material here.
//diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * material.diffuse); //Remember to use the material here.
//specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular); //Remember to use the material here.
//Now the result sum has changed a bit, since we now set the objects color in each element, we now dont have to
//multiply the light with the object here, instead we do it for each element seperatly. This allows much better control
//over how each element is applied to different objects.
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
================================================
FILE: Chapter2/3-Materials/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/3-Materials/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec3 FragPos;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
FragPos = vec3(vec4(aPos, 1.0) * model);
Normal = aNormal * mat3(transpose(inverse(model)));
}
================================================
FILE: Chapter2/3-Materials/Window.cs
================================================
using System;
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
using System.Diagnostics;
namespace LearnOpenTK
{
// In this tutorial we take the code from the last tutorial and create some level of abstraction over it allowing more
// control of the interaction between the light and the material.
// At the end of the web version of the tutorial we also had a bit of fun creating a disco light that changes
// color of the cube over time.
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Position Normal
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, // Front face
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, // Back face
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, // Left face
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // Right face
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, // Bottom face
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // Top face
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f
};
private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f);
private int _vertexBufferObject;
private int _vaoModel;
private int _vaoLamp;
private Shader _lampShader;
private Shader _lightingShader;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
var positionLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0);
var normalLocation = _lightingShader.GetAttribLocation("aNormal");
GL.EnableVertexAttribArray(normalLocation);
GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float));
}
{
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
var positionLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0);
}
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vaoModel);
_lightingShader.Use();
_lightingShader.SetMatrix4("model", Matrix4.Identity);
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("viewPos", _camera.Position);
// Here we set the material values of the cube, the material struct is just a container so to access
// the underlying values we simply type "material.value" to get the location of the uniform
_lightingShader.SetVector3("material.ambient", new Vector3(1.0f, 0.5f, 0.31f));
_lightingShader.SetVector3("material.diffuse", new Vector3(1.0f, 0.5f, 0.31f));
_lightingShader.SetVector3("material.specular", new Vector3(0.5f, 0.5f, 0.5f));
_lightingShader.SetFloat("material.shininess", 32.0f);
// This is where we change the lights color over time using the sin function
Vector3 lightColor;
float time = DateTime.Now.Second + DateTime.Now.Millisecond / 1000f;
lightColor.X = (MathF.Sin(time * 2.0f) + 1) / 2f;
lightColor.Y = (MathF.Sin(time * 0.7f) + 1) / 2f;
lightColor.Z = (MathF.Sin(time * 1.3f) + 1) / 2f;
// The ambient light is less intensive than the diffuse light in order to make it less dominant
Vector3 ambientColor = lightColor * new Vector3(0.2f);
Vector3 diffuseColor = lightColor * new Vector3(0.5f);
_lightingShader.SetVector3("light.position", _lightPos);
_lightingShader.SetVector3("light.ambient", ambientColor);
_lightingShader.SetVector3("light.diffuse", diffuseColor);
_lightingShader.SetVector3("light.specular", new Vector3(1.0f, 1.0f, 1.0f));
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
Matrix4 lampMatrix = Matrix4.Identity;
lampMatrix *= Matrix4.CreateScale(0.2f);
lampMatrix *= Matrix4.CreateTranslation(_lightPos);
_lampShader.SetMatrix4("model", lampMatrix);
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/4-LightingMaps/4-LightingMaps.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
PreserveNewest
PreserveNewest
================================================
FILE: Chapter2/4-LightingMaps/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/4-LightingMaps/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Lighting maps",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/4-LightingMaps/Shaders/lighting.frag
================================================
#version 330 core
// Now the diffuse and the specular values are controlled by textures, this is what we in graphics call mapping something.
// This means they are now based on textures instead of a color, and can be controlled much better per fragment.
// This also allows us the ability to texture our objects again.
// Note: We dont have a value for the ambient, as that is mostly the same the diffuse in pretty much every single situation.
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
uniform Material material;
uniform vec3 viewPos;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
// Now we need the texture coordinates, however we only need one set even though we have 2 textures,
// as every fragment should have the same texture position no matter what texture we are using.
in vec2 TexCoords;
void main()
{
// Each of the 3 different components now use a texture for the material values instead of the object wide color they had before.
// Note: The ambient and the diffuse share the same texture.
// ambient
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
// Diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
// Specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
================================================
FILE: Chapter2/4-LightingMaps/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/4-LightingMaps/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
FragPos = vec3(vec4(aPos, 1.0) * model);
Normal = aNormal * mat3(transpose(inverse(model)));
TexCoords = aTexCoords;
}
================================================
FILE: Chapter2/4-LightingMaps/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// 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
// different per fragment instead of making them per object.
// Remember to check out the shaders for how we converted to using textures there.
public class Window : GameWindow
{
// Since we are going to use textures we of course have to include two new floats per vertex, the texture coords.
private readonly float[] _vertices =
{
// Positions Normals Texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f);
private int _vertexBufferObject;
private int _vaoModel;
private int _vaoLamp;
private Shader _lampShader;
private Shader _lightingShader;
// The texture containing information for the diffuse map, this would more commonly
// just be called the color/texture of the object.
private Texture _diffuseMap;
// The specular map is a black/white representation of how specular each part of the texture is.
private Texture _specularMap;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
// All of the vertex attributes have been updated to now have a stride of 8 float sizes.
var positionLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
var normalLocation = _lightingShader.GetAttribLocation("aNormal");
GL.EnableVertexAttribArray(normalLocation);
GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float));
// The texture coords have now been added too, remember we only have 2 coordinates as the texture is 2d,
// so the size parameter should only be 2 for the texture coordinates.
var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float));
}
{
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
// The lamp shader should have its stride updated aswell, however we dont actually
// use the texture coords for the lamp, so we dont need to add any extra attributes.
var positionLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
}
// Our two textures are loaded in from memory, you should head over and
// check them out and compare them to the results.
_diffuseMap = Texture.LoadFromFile("Resources/container2.png");
_specularMap = Texture.LoadFromFile("Resources/container2_specular.png");
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vaoModel);
// The two textures need to be used, in this case we use the diffuse map as our 0th texture
// and the specular map as our 1st texture.
_diffuseMap.Use(TextureUnit.Texture0);
_specularMap.Use(TextureUnit.Texture1);
_lightingShader.Use();
_lightingShader.SetMatrix4("model", Matrix4.Identity);
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("viewPos", _camera.Position);
// Here we specify to the shaders what textures they should refer to when we want to get the positions.
_lightingShader.SetInt("material.diffuse", 0);
_lightingShader.SetInt("material.specular", 1);
_lightingShader.SetFloat("material.shininess", 32.0f);
_lightingShader.SetVector3("light.position", _lightPos);
_lightingShader.SetVector3("light.ambient", new Vector3(0.2f));
_lightingShader.SetVector3("light.diffuse", new Vector3(0.5f));
_lightingShader.SetVector3("light.specular", new Vector3(1.0f));
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
Matrix4 lampMatrix = Matrix4.Identity;
lampMatrix *= Matrix4.CreateScale(0.2f);
lampMatrix *= Matrix4.CreateTranslation(_lightPos);
_lampShader.SetMatrix4("model", lampMatrix);
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/5-LightCasters-DirectionalLights/5-LightCasters-DirectionalLights.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter2/5-LightCasters-DirectionalLights/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/5-LightCasters-DirectionalLights/Program.cs
================================================
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new OpenTK.Mathematics.Vector2i(800, 600),
Title = "LearnOpenTK - Light caster - directional",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/5-LightCasters-DirectionalLights/Shaders/lighting.frag
================================================
#version 330 core
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
struct Light {
//For a directional light we dont need the lights position to calculate the direction.
//Since the direction is the same no matter the position of the fragment we also dont need that.
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
uniform Material material;
uniform vec3 viewPos;
out vec4 FragColor;
in vec3 Normal;
in vec2 TexCoords;
void main()
{
// ambient
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(-light.direction);//We still normalize the light direction since we techically dont know,
//wether it was normalized for us or not.
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
// specular
vec3 viewDir = normalize(viewPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
================================================
FILE: Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/5-LightCasters-DirectionalLights/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
Normal = aNormal * mat3(transpose(inverse(model)));
TexCoords = aTexCoords;
}
================================================
FILE: Chapter2/5-LightCasters-DirectionalLights/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// This tutorial is split up into multiple different bits, one for each type of light.
// The following is the code for the directional light, a light that has a direction but no position.
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Positions Normals Texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
// We draw multiple different cubes and it helps to store all
// their positions in an array for later when we want to draw them
private readonly Vector3[] _cubePositions =
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(2.0f, 5.0f, -15.0f),
new Vector3(-1.5f, -2.2f, -2.5f),
new Vector3(-3.8f, -2.0f, -12.3f),
new Vector3(2.4f, -0.4f, -3.5f),
new Vector3(-1.7f, 3.0f, -7.5f),
new Vector3(1.3f, -2.0f, -2.5f),
new Vector3(1.5f, 2.0f, -2.5f),
new Vector3(1.5f, 0.2f, -1.5f),
new Vector3(-1.3f, 1.0f, -1.5f)
};
private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f);
private int _vertexBufferObject;
private int _vaoModel;
private int _vaoLamp;
private Shader _lampShader;
private Shader _lightingShader;
private Texture _diffuseMap;
private Texture _specularMap;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
var positionLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
var normalLocation = _lightingShader.GetAttribLocation("aNormal");
GL.EnableVertexAttribArray(normalLocation);
GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float));
var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float));
}
{
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
var positionLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
}
_diffuseMap = Texture.LoadFromFile("Resources/container2.png");
_specularMap = Texture.LoadFromFile("Resources/container2_specular.png");
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vaoModel);
_diffuseMap.Use(TextureUnit.Texture0);
_specularMap.Use(TextureUnit.Texture1);
_lightingShader.Use();
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("viewPos", _camera.Position);
_lightingShader.SetInt("material.diffuse", 0);
_lightingShader.SetInt("material.specular", 1);
_lightingShader.SetFloat("material.shininess", 32.0f);
// Directional light needs a direction, in this example we just use (-0.2, -1.0, -0.3f) as the lights direction
_lightingShader.SetVector3("light.direction", new Vector3(-0.2f, -1.0f, -0.3f));
_lightingShader.SetVector3("light.ambient", new Vector3(0.2f));
_lightingShader.SetVector3("light.diffuse", new Vector3(0.5f));
_lightingShader.SetVector3("light.specular", new Vector3(1.0f));
// We want to draw all the cubes at their respective positions
for (int i = 0; i < _cubePositions.Length; i++)
{
// Then we translate said matrix by the cube position
Matrix4 model = Matrix4.CreateTranslation(_cubePositions[i]);
// We then calculate the angle and rotate the model around an axis
float angle = 20.0f * i;
model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle);
// Remember to set the model at last so it can be used by opentk
_lightingShader.SetMatrix4("model", model);
// At last we draw all our cubes
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
}
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
Matrix4 lampMatrix = Matrix4.CreateScale(0.2f);
lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos);
_lampShader.SetMatrix4("model", lampMatrix);
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/5-LightCasters-PointLights/5-LightCasters-PointLights.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter2/5-LightCasters-PointLights/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/5-LightCasters-PointLights/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Light casters - point lights",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/5-LightCasters-PointLights/Shaders/lighting.frag
================================================
#version 330 core
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
//This light structure is pretty much the same as the one from the last few parts of the tutorials
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
//In the web version you can see why we need the constant the linear and the quadratic values.
//However to keep a brief explanation here, it is to make the light more dim the further you go away.
//These are constants defining the graph the intensity of the light follows.
float constant;
float linear;
float quadratic;
};
uniform Light light;
uniform Material material;
uniform vec3 viewPos;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
void main()
{
//ambient
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
//diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
//specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
//attenuation
//The attenuation is the term we use when talking about how dim the light gets over distance
float distance = length(light.position - FragPos);
//This formula is the so called attenuation formula used to calculate the attenuation
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
//To apply the attenuation simply multiply it into each of the elements
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
================================================
FILE: Chapter2/5-LightCasters-PointLights/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/5-LightCasters-PointLights/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
FragPos = vec3(vec4(aPos, 1.0) * model);
Normal = aNormal * mat3(transpose(inverse(model)));
TexCoords = aTexCoords;
}
================================================
FILE: Chapter2/5-LightCasters-PointLights/Window.cs
================================================
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// This tutorial is split up into multiple different bits, one for each type of light.
// The following is the code for the point light, a point light is in essence the same as we made in tutorial 1-4
// except it diminishes over distance (attenuation)
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Positions Normals Texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
// We draw multiple different cubes and it helps to store all
// their positions in an array for later when we want to draw them
private readonly Vector3[] _cubePositions =
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(2.0f, 5.0f, -15.0f),
new Vector3(-1.5f, -2.2f, -2.5f),
new Vector3(-3.8f, -2.0f, -12.3f),
new Vector3(2.4f, -0.4f, -3.5f),
new Vector3(-1.7f, 3.0f, -7.5f),
new Vector3(1.3f, -2.0f, -2.5f),
new Vector3(1.5f, 2.0f, -2.5f),
new Vector3(1.5f, 0.2f, -1.5f),
new Vector3(-1.3f, 1.0f, -1.5f)
};
private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f);
private int _vertexBufferObject;
private int _vaoModel;
private int _vaoLamp;
private Shader _lampShader;
private Shader _lightingShader;
private Texture _diffuseMap;
private Texture _specularMap;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
var positionLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
var normalLocation = _lightingShader.GetAttribLocation("aNormal");
GL.EnableVertexAttribArray(normalLocation);
GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float));
var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float));
}
{
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
var positionLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
}
_diffuseMap = Texture.LoadFromFile("Resources/container2.png");
_specularMap = Texture.LoadFromFile("Resources/container2_specular.png");
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vaoModel);
_diffuseMap.Use(TextureUnit.Texture0);
_specularMap.Use(TextureUnit.Texture1);
_lightingShader.Use();
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("viewPos", _camera.Position);
_lightingShader.SetInt("material.diffuse", 0);
_lightingShader.SetInt("material.specular", 1);
_lightingShader.SetFloat("material.shininess", 32.0f);
_lightingShader.SetVector3("light.position", _lightPos);
_lightingShader.SetFloat("light.constant", 1.0f);
_lightingShader.SetFloat("light.linear", 0.09f);
_lightingShader.SetFloat("light.quadratic", 0.032f);
_lightingShader.SetVector3("light.ambient", new Vector3(0.2f));
_lightingShader.SetVector3("light.diffuse", new Vector3(0.5f));
_lightingShader.SetVector3("light.specular", new Vector3(1.0f));
// We want to draw all the cubes at their respective positions
for (int i = 0; i < _cubePositions.Length; i++)
{
// First we create a model from an identity matrix
// Then we translate said matrix by the cube position
Matrix4 model = Matrix4.CreateTranslation(_cubePositions[i]);
// We then calculate the angle and rotate the model around an axis
float angle = 20.0f * i;
model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle);
// Remember to set the model at last so it can be used by opentk
_lightingShader.SetMatrix4("model", model);
// At last we draw all our cubes
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
}
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
Matrix4 lampMatrix = Matrix4.CreateScale(0.2f);
lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos);
_lampShader.SetMatrix4("model", lampMatrix);
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/5-LightCasters-Spotlight/5-LightCasters-Spotlight.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter2/5-LightCasters-Spotlight/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/5-LightCasters-Spotlight/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Light casters - spotlight",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/5-LightCasters-Spotlight/Shaders/lighting.frag
================================================
#version 330 core
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
//The spotlight is a pointlight in essence, however we only want to show the light within a certain angle.
//That angle is the cutoff, the outercutoff is used to make a more smooth border to the spotlight.
struct Light {
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
uniform Light light;
uniform Material material;
uniform vec3 viewPos;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
void main()
{
//ambient
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
//diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
//specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
//attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
//spotlight intensity
//This is how we calculate the spotlight, for a more in depth explanation of how this works. Check out the web tutorials.
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); //The intensity, is the lights intensity on a given fragment,
//this is used to make the smooth border.
//When applying the spotlight intensity we want to multiply it.
ambient *= attenuation; //Remember the ambient is where the light dosen't hit, this means the spotlight shouldn't be applied
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
================================================
FILE: Chapter2/5-LightCasters-Spotlight/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/5-LightCasters-Spotlight/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
FragPos = vec3(vec4(aPos, 1.0) * model);
Normal = aNormal * mat3(transpose(inverse(model)));
TexCoords = aTexCoords;
}
================================================
FILE: Chapter2/5-LightCasters-Spotlight/Window.cs
================================================
using System;
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// This tutorial is split up into multiple different bits, one for each type of light.
// The following is the code for the spotlight, the functionality is much the same as the point light except it
// only shines in one direction, for this we need the angle between the spotlight direction and the lightDir
// then we can check if that angle is within the cutoff of the spotlight, if it is we light it accordingly
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Positions Normals Texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
// We draw multiple different cubes and it helps to store all
// their positions in an array for later when we want to draw them
private readonly Vector3[] _cubePositions =
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(2.0f, 5.0f, -15.0f),
new Vector3(-1.5f, -2.2f, -2.5f),
new Vector3(-3.8f, -2.0f, -12.3f),
new Vector3(2.4f, -0.4f, -3.5f),
new Vector3(-1.7f, 3.0f, -7.5f),
new Vector3(1.3f, -2.0f, -2.5f),
new Vector3(1.5f, 2.0f, -2.5f),
new Vector3(1.5f, 0.2f, -1.5f),
new Vector3(-1.3f, 1.0f, -1.5f)
};
private readonly Vector3 _lightPos = new Vector3(1.2f, 1.0f, 2.0f);
private int _vertexBufferObject;
private int _vaoModel;
private int _vaoLamp;
private Shader _lampShader;
private Shader _lightingShader;
private Texture _diffuseMap;
private Texture _specularMap;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
var positionLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
var normalLocation = _lightingShader.GetAttribLocation("aNormal");
GL.EnableVertexAttribArray(normalLocation);
GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float));
var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float));
}
{
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
var positionLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
}
_diffuseMap = Texture.LoadFromFile("Resources/container2.png");
_specularMap = Texture.LoadFromFile("Resources/container2_specular.png");
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vaoModel);
_diffuseMap.Use(TextureUnit.Texture0);
_specularMap.Use(TextureUnit.Texture1);
_lightingShader.Use();
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("viewPos", _camera.Position);
_lightingShader.SetInt("material.diffuse", 0);
_lightingShader.SetInt("material.specular", 1);
_lightingShader.SetFloat("material.shininess", 32.0f);
_lightingShader.SetVector3("light.position", _camera.Position);
_lightingShader.SetVector3("light.direction", _camera.Front);
_lightingShader.SetFloat("light.cutOff", MathF.Cos(MathHelper.DegreesToRadians(12.5f)));
_lightingShader.SetFloat("light.outerCutOff", MathF.Cos(MathHelper.DegreesToRadians(17.5f)));
_lightingShader.SetFloat("light.constant", 1.0f);
_lightingShader.SetFloat("light.linear", 0.09f);
_lightingShader.SetFloat("light.quadratic", 0.032f);
_lightingShader.SetVector3("light.ambient", new Vector3(0.2f));
_lightingShader.SetVector3("light.diffuse", new Vector3(0.5f));
_lightingShader.SetVector3("light.specular", new Vector3(1.0f));
// We want to draw all the cubes at their respective positions
for (int i = 0; i < _cubePositions.Length; i++)
{
// Then we translate said matrix by the cube position
Matrix4 model = Matrix4.CreateTranslation(_cubePositions[i]);
// We then calculate the angle and rotate the model around an axis
float angle = 20.0f * i;
model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle);
// Remember to set the model at last so it can be used by opentk
_lightingShader.SetMatrix4("model", model);
// At last we draw all our cubes
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
}
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
Matrix4 lampMatrix = Matrix4.CreateScale(0.2f);
lampMatrix = lampMatrix * Matrix4.CreateTranslation(_lightPos);
_lampShader.SetMatrix4("model", lampMatrix);
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Chapter2/6-MultipleLights/6-MultipleLights.csproj
================================================
WinExe
LearnOpenTK
LearnOpenTK
net8.0
================================================
FILE: Chapter2/6-MultipleLights/OpenTK.dll.config
================================================
================================================
FILE: Chapter2/6-MultipleLights/Program.cs
================================================
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
public static class Program
{
private static void Main()
{
var nativeWindowSettings = new NativeWindowSettings()
{
ClientSize = new Vector2i(800, 600),
Title = "LearnOpenTK - Multiple lights",
// This is needed to run on macos
Flags = ContextFlags.ForwardCompatible,
};
using (var window = new Window(GameWindowSettings.Default, nativeWindowSettings))
{
window.Run();
}
}
}
}
================================================
FILE: Chapter2/6-MultipleLights/Shaders/lighting.frag
================================================
#version 330 core
//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
//and added 3 extra point lights.
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
//This is the directional light struct, where we only need the directions
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
//This is our pointlight where we need the position aswell as the constants defining the attenuation of the light.
struct PointLight {
vec3 position;
float constant;
float linear;
float quadratic;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
//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
#define NR_POINT_LIGHTS 4
uniform PointLight pointLights[NR_POINT_LIGHTS];
//This is our spotlight where we need the position, attenuation along with the cutoff and the outer cutoff. Plus the direction of the light
struct SpotLight{
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
uniform SpotLight spotLight;
uniform Material material;
uniform vec3 viewPos;
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
//Here we have some function prototypes, these are the signatures the gpu will use to know how the
//parameters of each light calculation is layed out.
//We have one function per light, since this makes it so we dont have to take up to much space in the main function.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
void main()
{
//properties
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
//phase 1: Directional lighting
vec3 result = CalcDirLight(dirLight, norm, viewDir);
//phase 2: Point lights
for(int i = 0; i < NR_POINT_LIGHTS; i++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
//phase 3: Spot light
result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
//diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
//specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
//combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
return (ambient + diffuse + specular);
}
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
//diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
//specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
//attenuation
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
//combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
return (ambient + diffuse + specular);
}
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
//diffuse shading
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(normal, lightDir), 0.0);
//specular shading
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
//attenuation
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
//spotlight intensity
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
//combine results
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
ambient *= attenuation;
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
return (ambient + diffuse + specular);
}
================================================
FILE: Chapter2/6-MultipleLights/Shaders/shader.frag
================================================
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
================================================
FILE: Chapter2/6-MultipleLights/Shaders/shader.vert
================================================
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
FragPos = vec3(vec4(aPos, 1.0) * model);
Normal = aNormal * mat3(transpose(inverse(model)));
TexCoords = aTexCoords;
}
================================================
FILE: Chapter2/6-MultipleLights/Window.cs
================================================
using System;
using LearnOpenTK.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using OpenTK.Windowing.Desktop;
namespace LearnOpenTK
{
// In this tutorial we focus on how to set up a scene with multiple lights, both of different types but also
// with several point lights
public class Window : GameWindow
{
private readonly float[] _vertices =
{
// Positions Normals Texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
private readonly Vector3[] _cubePositions =
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(2.0f, 5.0f, -15.0f),
new Vector3(-1.5f, -2.2f, -2.5f),
new Vector3(-3.8f, -2.0f, -12.3f),
new Vector3(2.4f, -0.4f, -3.5f),
new Vector3(-1.7f, 3.0f, -7.5f),
new Vector3(1.3f, -2.0f, -2.5f),
new Vector3(1.5f, 2.0f, -2.5f),
new Vector3(1.5f, 0.2f, -1.5f),
new Vector3(-1.3f, 1.0f, -1.5f)
};
// We need the point lights' positions to draw the lamps and to get light the materials properly
private readonly Vector3[] _pointLightPositions =
{
new Vector3(0.7f, 0.2f, 2.0f),
new Vector3(2.3f, -3.3f, -4.0f),
new Vector3(-4.0f, 2.0f, -12.0f),
new Vector3(0.0f, 0.0f, -3.0f)
};
private int _vertexBufferObject;
private int _vaoModel;
private int _vaoLamp;
private Shader _lampShader;
private Shader _lightingShader;
private Texture _diffuseMap;
private Texture _specularMap;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
public Window(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Enable(EnableCap.DepthTest);
_vertexBufferObject = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * sizeof(float), _vertices, BufferUsageHint.StaticDraw);
_lightingShader = new Shader("Shaders/shader.vert", "Shaders/lighting.frag");
_lampShader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
{
_vaoModel = GL.GenVertexArray();
GL.BindVertexArray(_vaoModel);
var positionLocation = _lightingShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
var normalLocation = _lightingShader.GetAttribLocation("aNormal");
GL.EnableVertexAttribArray(normalLocation);
GL.VertexAttribPointer(normalLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 3 * sizeof(float));
var texCoordLocation = _lightingShader.GetAttribLocation("aTexCoords");
GL.EnableVertexAttribArray(texCoordLocation);
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 8 * sizeof(float), 6 * sizeof(float));
}
{
_vaoLamp = GL.GenVertexArray();
GL.BindVertexArray(_vaoLamp);
var positionLocation = _lampShader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0);
}
_diffuseMap = Texture.LoadFromFile("Resources/container2.png");
_specularMap = Texture.LoadFromFile("Resources/container2_specular.png");
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
CursorState = CursorState.Grabbed;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BindVertexArray(_vaoModel);
_diffuseMap.Use(TextureUnit.Texture0);
_specularMap.Use(TextureUnit.Texture1);
_lightingShader.Use();
_lightingShader.SetMatrix4("view", _camera.GetViewMatrix());
_lightingShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
_lightingShader.SetVector3("viewPos", _camera.Position);
_lightingShader.SetInt("material.diffuse", 0);
_lightingShader.SetInt("material.specular", 1);
_lightingShader.SetFloat("material.shininess", 32.0f);
/*
Here we set all the uniforms for the 5/6 types of lights we have. We have to set them manually and index
the proper PointLight struct in the array to set each uniform variable. This can be done more code-friendly
by defining light types as classes and set their values in there, or by using a more efficient uniform approach
by using 'Uniform buffer objects', but that is something we'll discuss in the 'Advanced GLSL' tutorial.
*/
// Directional light
_lightingShader.SetVector3("dirLight.direction", new Vector3(-0.2f, -1.0f, -0.3f));
_lightingShader.SetVector3("dirLight.ambient", new Vector3(0.05f, 0.05f, 0.05f));
_lightingShader.SetVector3("dirLight.diffuse", new Vector3(0.4f, 0.4f, 0.4f));
_lightingShader.SetVector3("dirLight.specular", new Vector3(0.5f, 0.5f, 0.5f));
// Point lights
for (int i = 0; i < _pointLightPositions.Length; i++)
{
_lightingShader.SetVector3($"pointLights[{i}].position", _pointLightPositions[i]);
_lightingShader.SetVector3($"pointLights[{i}].ambient", new Vector3(0.05f, 0.05f, 0.05f));
_lightingShader.SetVector3($"pointLights[{i}].diffuse", new Vector3(0.8f, 0.8f, 0.8f));
_lightingShader.SetVector3($"pointLights[{i}].specular", new Vector3(1.0f, 1.0f, 1.0f));
_lightingShader.SetFloat($"pointLights[{i}].constant", 1.0f);
_lightingShader.SetFloat($"pointLights[{i}].linear", 0.09f);
_lightingShader.SetFloat($"pointLights[{i}].quadratic", 0.032f);
}
// Spot light
_lightingShader.SetVector3("spotLight.position", _camera.Position);
_lightingShader.SetVector3("spotLight.direction", _camera.Front);
_lightingShader.SetVector3("spotLight.ambient", new Vector3(0.0f, 0.0f, 0.0f));
_lightingShader.SetVector3("spotLight.diffuse", new Vector3(1.0f, 1.0f, 1.0f));
_lightingShader.SetVector3("spotLight.specular", new Vector3(1.0f, 1.0f, 1.0f));
_lightingShader.SetFloat("spotLight.constant", 1.0f);
_lightingShader.SetFloat("spotLight.linear", 0.09f);
_lightingShader.SetFloat("spotLight.quadratic", 0.032f);
_lightingShader.SetFloat("spotLight.cutOff", MathF.Cos(MathHelper.DegreesToRadians(12.5f)));
_lightingShader.SetFloat("spotLight.outerCutOff", MathF.Cos(MathHelper.DegreesToRadians(17.5f)));
for (int i = 0; i < _cubePositions.Length; i++)
{
Matrix4 model = Matrix4.CreateTranslation(_cubePositions[i]);
float angle = 20.0f * i;
model = model * Matrix4.CreateFromAxisAngle(new Vector3(1.0f, 0.3f, 0.5f), angle);
_lightingShader.SetMatrix4("model", model);
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
}
GL.BindVertexArray(_vaoLamp);
_lampShader.Use();
_lampShader.SetMatrix4("view", _camera.GetViewMatrix());
_lampShader.SetMatrix4("projection", _camera.GetProjectionMatrix());
// We use a loop to draw all the lights at the proper position
for (int i = 0; i < _pointLightPositions.Length; i++)
{
Matrix4 lampMatrix = Matrix4.CreateScale(0.2f);
lampMatrix = lampMatrix * Matrix4.CreateTranslation(_pointLightPositions[i]);
_lampShader.SetMatrix4("model", lampMatrix);
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
}
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsFocused)
{
return;
}
var input = KeyboardState;
if (input.IsKeyDown(Keys.Escape))
{
Close();
}
const float cameraSpeed = 1.5f;
const float sensitivity = 0.2f;
if (input.IsKeyDown(Keys.W))
{
_camera.Position += _camera.Front * cameraSpeed * (float)e.Time; // Forward
}
if (input.IsKeyDown(Keys.S))
{
_camera.Position -= _camera.Front * cameraSpeed * (float)e.Time; // Backwards
}
if (input.IsKeyDown(Keys.A))
{
_camera.Position -= _camera.Right * cameraSpeed * (float)e.Time; // Left
}
if (input.IsKeyDown(Keys.D))
{
_camera.Position += _camera.Right * cameraSpeed * (float)e.Time; // Right
}
if (input.IsKeyDown(Keys.Space))
{
_camera.Position += _camera.Up * cameraSpeed * (float)e.Time; // Up
}
if (input.IsKeyDown(Keys.LeftShift))
{
_camera.Position -= _camera.Up * cameraSpeed * (float)e.Time; // Down
}
var mouse = MouseState;
if (_firstMove)
{
_lastPos = new Vector2(mouse.X, mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2(mouse.X, mouse.Y);
_camera.Yaw += deltaX * sensitivity;
_camera.Pitch -= deltaY * sensitivity;
}
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
_camera.Fov -= e.OffsetY;
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}
}
================================================
FILE: Common/Camera.cs
================================================
using OpenTK.Mathematics;
using System;
namespace LearnOpenTK.Common
{
// This is the camera class as it could be set up after the tutorials on the website.
// It is important to note there are a few ways you could have set up this camera.
// For example, you could have also managed the player input inside the camera class,
// and a lot of the properties could have been made into functions.
// TL;DR: This is just one of many ways in which we could have set up the camera.
// Check out the web version if you don't know why we are doing a specific thing or want to know more about the code.
public class Camera
{
// Those vectors are directions pointing outwards from the camera to define how it rotated.
private Vector3 _front = -Vector3.UnitZ;
private Vector3 _up = Vector3.UnitY;
private Vector3 _right = Vector3.UnitX;
// Rotation around the X axis (radians)
private float _pitch;
// Rotation around the Y axis (radians)
private float _yaw = -MathHelper.PiOver2; // Without this, you would be started rotated 90 degrees right.
// The field of view of the camera (radians)
private float _fov = MathHelper.PiOver2;
public Camera(Vector3 position, float aspectRatio)
{
Position = position;
AspectRatio = aspectRatio;
}
// The position of the camera
public Vector3 Position { get; set; }
// This is simply the aspect ratio of the viewport, used for the projection matrix.
public float AspectRatio { private get; set; }
public Vector3 Front => _front;
public Vector3 Up => _up;
public Vector3 Right => _right;
// We convert from degrees to radians as soon as the property is set to improve performance.
public float Pitch
{
get => MathHelper.RadiansToDegrees(_pitch);
set
{
// We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch
// of weird "bugs" when you are using euler angles for rotation.
// If you want to read more about this you can try researching a topic called gimbal lock
var angle = MathHelper.Clamp(value, -89f, 89f);
_pitch = MathHelper.DegreesToRadians(angle);
UpdateVectors();
}
}
// We convert from degrees to radians as soon as the property is set to improve performance.
public float Yaw
{
get => MathHelper.RadiansToDegrees(_yaw);
set
{
_yaw = MathHelper.DegreesToRadians(value);
UpdateVectors();
}
}
// The field of view (FOV) is the vertical angle of the camera view.
// This has been discussed more in depth in a previous tutorial,
// but in this tutorial, you have also learned how we can use this to simulate a zoom feature.
// We convert from degrees to radians as soon as the property is set to improve performance.
public float Fov
{
get => MathHelper.RadiansToDegrees(_fov);
set
{
var angle = MathHelper.Clamp(value, 1f, 90f);
_fov = MathHelper.DegreesToRadians(angle);
}
}
// Get the view matrix using the amazing LookAt function described more in depth on the web tutorials
public Matrix4 GetViewMatrix()
{
return Matrix4.LookAt(Position, Position + _front, _up);
}
// Get the projection matrix using the same method we have used up until this point
public Matrix4 GetProjectionMatrix()
{
return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f);
}
// This function is going to update the direction vertices using some of the math learned in the web tutorials.
private void UpdateVectors()
{
// First, the front matrix is calculated using some basic trigonometry.
_front.X = MathF.Cos(_pitch) * MathF.Cos(_yaw);
_front.Y = MathF.Sin(_pitch);
_front.Z = MathF.Cos(_pitch) * MathF.Sin(_yaw);
// We need to make sure the vectors are all normalized, as otherwise we would get some funky results.
_front = Vector3.Normalize(_front);
// Calculate both the right and the up vector using cross product.
// Note that we are calculating the right from the global up; this behaviour might
// not be what you need for all cameras so keep this in mind if you do not want a FPS camera.
_right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY));
_up = Vector3.Normalize(Vector3.Cross(_right, _front));
}
}
}
================================================
FILE: Common/Common.csproj
================================================
LearnOpenTK.Common
LearnOpenTK.Common
net8.0
================================================
FILE: Common/Shader.cs
================================================
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace LearnOpenTK.Common
{
// A simple class meant to help create shaders.
public class Shader
{
public readonly int Handle;
private readonly Dictionary _uniformLocations;
// This is how you create a simple shader.
// Shaders are written in GLSL, which is a language very similar to C in its semantics.
// The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on.
// A commented example of GLSL can be found in shader.vert.
public Shader(string vertPath, string fragPath)
{
// There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders.
// The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader.
// The vertex shader won't be too important here, but they'll be more important later.
// The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel.
// The fragment shader is what we'll be using the most here.
// Load vertex shader and compile
var shaderSource = File.ReadAllText(vertPath);
// GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created.
var vertexShader = GL.CreateShader(ShaderType.VertexShader);
// Now, bind the GLSL source code
GL.ShaderSource(vertexShader, shaderSource);
// And then compile
CompileShader(vertexShader);
// We do the same for the fragment shader.
shaderSource = File.ReadAllText(fragPath);
var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fragmentShader, shaderSource);
CompileShader(fragmentShader);
// These two shaders must then be merged into a shader program, which can then be used by OpenGL.
// To do this, create a program...
Handle = GL.CreateProgram();
// Attach both shaders...
GL.AttachShader(Handle, vertexShader);
GL.AttachShader(Handle, fragmentShader);
// And then link them together.
LinkProgram(Handle);
// When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program.
// Detach them, and then delete them.
GL.DetachShader(Handle, vertexShader);
GL.DetachShader(Handle, fragmentShader);
GL.DeleteShader(fragmentShader);
GL.DeleteShader(vertexShader);
// The shader is now ready to go, but first, we're going to cache all the shader uniform locations.
// Querying this from the shader is very slow, so we do it once on initialization and reuse those values
// later.
// First, we have to get the number of active uniforms in the shader.
GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms);
// Next, allocate the dictionary to hold the locations.
_uniformLocations = new Dictionary();
// Loop over all the uniforms,
for (var i = 0; i < numberOfUniforms; i++)
{
// get the name of this uniform,
var key = GL.GetActiveUniform(Handle, i, out _, out _);
// get the location,
var location = GL.GetUniformLocation(Handle, key);
// and then add it to the dictionary.
_uniformLocations.Add(key, location);
}
}
private static void CompileShader(int shader)
{
// Try to compile the shader
GL.CompileShader(shader);
// Check for compilation errors
GL.GetShader(shader, ShaderParameter.CompileStatus, out var code);
if (code != (int)All.True)
{
// We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
var infoLog = GL.GetShaderInfoLog(shader);
throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}");
}
}
private static void LinkProgram(int program)
{
// We link the program
GL.LinkProgram(program);
// Check for linking errors
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code);
if (code != (int)All.True)
{
// We can use `GL.GetProgramInfoLog(program)` to get information about the error.
throw new Exception($"Error occurred whilst linking Program({program})");
}
}
// A wrapper function that enables the shader program.
public void Use()
{
GL.UseProgram(Handle);
}
// The shader sources provided with this project use hardcoded layout(location)-s. If you want to do it dynamically,
// you can omit the layout(location=X) lines in the vertex shader, and use this in VertexAttribPointer instead of the hardcoded values.
public int GetAttribLocation(string attribName)
{
return GL.GetAttribLocation(Handle, attribName);
}
// Uniform setters
// Uniforms are variables that can be set by user code, instead of reading them from the VBO.
// You use VBOs for vertex-related data, and uniforms for almost everything else.
// Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
// 1. Bind the program you want to set the uniform on
// 2. Get a handle to the location of the uniform with GL.GetUniformLocation.
// 3. Use the appropriate GL.Uniform* function to set the uniform.
///
/// Set a uniform int on this shader.
///
/// The name of the uniform
/// The data to set
public void SetInt(string name, int data)
{
GL.UseProgram(Handle);
GL.Uniform1(_uniformLocations[name], data);
}
///
/// Set a uniform float on this shader.
///
/// The name of the uniform
/// The data to set
public void SetFloat(string name, float data)
{
GL.UseProgram(Handle);
GL.Uniform1(_uniformLocations[name], data);
}
///
/// Set a uniform Matrix4 on this shader
///
/// The name of the uniform
/// The data to set
///
///
/// The matrix is transposed before being sent to the shader.
///
///
public void SetMatrix4(string name, Matrix4 data)
{
GL.UseProgram(Handle);
GL.UniformMatrix4(_uniformLocations[name], true, ref data);
}
///
/// Set a uniform Vector3 on this shader.
///
/// The name of the uniform
/// The data to set
public void SetVector3(string name, Vector3 data)
{
GL.UseProgram(Handle);
GL.Uniform3(_uniformLocations[name], data);
}
}
}
================================================
FILE: Common/Texture.cs
================================================
using OpenTK.Graphics.OpenGL4;
using System.Drawing;
using System.Drawing.Imaging;
using PixelFormat = OpenTK.Graphics.OpenGL4.PixelFormat;
using StbImageSharp;
using System.IO;
namespace LearnOpenTK.Common
{
// A helper class, much like Shader, meant to simplify loading textures.
public class Texture
{
public readonly int Handle;
public static Texture LoadFromFile(string path)
{
// Generate handle
int handle = GL.GenTexture();
// Bind the handle
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, handle);
// For this example, we're going to use .NET's built-in System.Drawing library to load textures.
// OpenGL has it's texture origin in the lower left corner instead of the top left corner,
// so we tell StbImageSharp to flip the image when loading.
StbImage.stbi_set_flip_vertically_on_load(1);
// Here we open a stream to the file and pass it to StbImageSharp to load.
using (Stream stream = File.OpenRead(path))
{
ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
// Now that our pixels are prepared, it's time to generate a texture. We do this with GL.TexImage2D.
// Arguments:
// The type of texture we're generating. There are various different types of textures, but the only one we need right now is Texture2D.
// Level of detail. We can use this to start from a smaller mipmap (if we want), but we don't need to do that, so leave it at 0.
// Target format of the pixels. This is the format OpenGL will store our image with.
// Width of the image
// Height of the image.
// Border of the image. This must always be 0; it's a legacy parameter that Khronos never got rid of.
// The format of the pixels, explained above. Since we loaded the pixels as RGBA earlier, we need to use PixelFormat.Rgba.
// Data type of the pixels.
// And finally, the actual pixels.
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data);
}
// Now that our texture is loaded, we can set a few settings to affect how the image appears on rendering.
// First, we set the min and mag filter. These are used for when the texture is scaled down and up, respectively.
// Here, we use Linear for both. This means that OpenGL will try to blend pixels, meaning that textures scaled too far will look blurred.
// You could also use (amongst other options) Nearest, which just grabs the nearest pixel, which makes the texture look pixelated if scaled too far.
// NOTE: The default settings for both of these are LinearMipmap. If you leave these as default but don't generate mipmaps,
// your image will fail to render at all (usually resulting in pure black instead).
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
// Now, set the wrapping mode. S is for the X axis, and T is for the Y axis.
// We set this to Repeat so that textures will repeat when wrapped. Not demonstrated here since the texture coordinates exactly match
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
// Next, generate mipmaps.
// Mipmaps are smaller copies of the texture, scaled down. Each mipmap level is half the size of the previous one
// Generated mipmaps go all the way down to just one pixel.
// OpenGL will automatically switch between mipmaps when an object gets sufficiently far away.
// This prevents moiré effects, as well as saving on texture bandwidth.
// Here you can see and read about the morié effect https://en.wikipedia.org/wiki/Moir%C3%A9_pattern
// Here is an example of mips in action https://en.wikipedia.org/wiki/File:Mipmap_Aliasing_Comparison.png
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
return new Texture(handle);
}
public Texture(int glHandle)
{
Handle = glHandle;
}
// Activate texture
// Multiple textures can be bound, if your shader needs more than just one.
// If you want to do that, use GL.ActiveTexture to set which slot GL.BindTexture binds to.
// The OpenGL standard requires that there be at least 16, but there can be more depending on your graphics card.
public void Use(TextureUnit unit)
{
GL.ActiveTexture(unit);
GL.BindTexture(TextureTarget.Texture2D, Handle);
}
}
}
================================================
FILE: LICENSE
================================================
Attribution 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution 4.0 International Public License ("Public License"). To the
extent this Public License may be interpreted as a contract, You are
granted the Licensed Rights in consideration of Your acceptance of
these terms and conditions, and the Licensor grants You such rights in
consideration of benefits the Licensor receives from making the
Licensed Material available under these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
d. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
e. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
f. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
4. If You Share Adapted Material You produce, the Adapter's
License You apply must not prevent recipients of the Adapted
Material from complying with this Public License.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.
================================================
FILE: LearnOpenTK.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30309.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1-CreatingAWindow", "Chapter1\1-CreatingAWindow\1-CreatingAWindow.csproj", "{39A0FE24-A920-4C13-9BB9-18483FECD55D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2-HelloTriangle", "Chapter1\2-HelloTriangle\2-HelloTriangle.csproj", "{6E4EC91D-F0D3-42BC-8306-09671FA9E332}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "5-Textures", "Chapter1\5-Textures\5-Textures.csproj", "{63476AD1-106A-4086-8B0E-E295567BC526}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "3-ElementBufferObjects", "Chapter1\3-ElementBufferObjects\3-ElementBufferObjects.csproj", "{BC7E1E6C-30F4-4D37-BEAD-44571FCD4ECD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "6-MultipleTextures", "Chapter1\6-MultipleTextures\6-MultipleTextures.csproj", "{1A16223E-7FC4-41A4-9487-71B1782BC98D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "7-Transformations", "Chapter1\7-Transformations\7-Transformations.csproj", "{3E11B0DB-76CD-4B23-84D3-0976C38B1A20}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "8-CoordinatesSystems", "Chapter1\8-CoordinatesSystems\8-CoordinatesSystems.csproj", "{13DFF44A-3D98-44B9-ADE0-B447053FECFC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "9-Camera", "Chapter1\9-Camera\9-Camera.csproj", "{F2E87EBD-BA5C-4A22-820F-99B492B39BCB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chapter1", "Chapter1", "{F7676E7B-4F14-48D9-8985-3FA73BF633BD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chapter2", "Chapter2", "{F66727D5-726B-41DF-8318-6E12733B278F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1-Colors", "Chapter2\1-Colors\1-Colors.csproj", "{F363CEB6-1E2C-4114-AA8C-85452472F82B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2-BasicLighting", "Chapter2\2-BasicLighting\2-BasicLighting.csproj", "{06BF8581-BA93-4891-9CE8-0BA4E8C7A200}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "3-Materials", "Chapter2\3-Materials\3-Materials.csproj", "{0B57E5F4-0E40-4AB6-82F7-E8A2D63AE2F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "4-LightingMaps", "Chapter2\4-LightingMaps\4-LightingMaps.csproj", "{8C1E543A-D1BF-4B66-86C9-0897DD50B044}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "6-MultipleLights", "Chapter2\6-MultipleLights\6-MultipleLights.csproj", "{519EDF78-AA47-4A55-8953-5DF0CD408679}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "5-LightCasters-DirectionalLights", "Chapter2\5-LightCasters-DirectionalLights\5-LightCasters-DirectionalLights.csproj", "{0E05843C-001E-4773-A1E3-1E48EC18BA36}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "5-LightCasters-PointLights", "Chapter2\5-LightCasters-PointLights\5-LightCasters-PointLights.csproj", "{3531934C-7535-4A18-B598-C21359004324}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "5-LightCasters-Spotlight", "Chapter2\5-LightCasters-Spotlight\5-LightCasters-Spotlight.csproj", "{2DD36D08-FBC6-40E1-8EB9-3C94ABE6985E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4-Shaders-InsAndOuts", "Chapter1\4-Shaders-InsAndOuts\4-Shaders-InsAndOuts.csproj", "{1234CBB6-22C1-42D0-828B-1D1E7085EF33}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4-Shaders-Uniforms", "Chapter1\4-Shaders-Uniforms\4-Shaders-Uniforms.csproj", "{E8DA696D-F948-4967-BF75-1570A2957C3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "4-Shaders-MoreAttributes", "Chapter1\4-Shaders-MoreAttributes\4-Shaders-MoreAttributes.csproj", "{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{39A0FE24-A920-4C13-9BB9-18483FECD55D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39A0FE24-A920-4C13-9BB9-18483FECD55D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39A0FE24-A920-4C13-9BB9-18483FECD55D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39A0FE24-A920-4C13-9BB9-18483FECD55D}.Release|Any CPU.Build.0 = Release|Any CPU
{6E4EC91D-F0D3-42BC-8306-09671FA9E332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E4EC91D-F0D3-42BC-8306-09671FA9E332}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6E4EC91D-F0D3-42BC-8306-09671FA9E332}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E4EC91D-F0D3-42BC-8306-09671FA9E332}.Release|Any CPU.Build.0 = Release|Any CPU
{63476AD1-106A-4086-8B0E-E295567BC526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63476AD1-106A-4086-8B0E-E295567BC526}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63476AD1-106A-4086-8B0E-E295567BC526}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63476AD1-106A-4086-8B0E-E295567BC526}.Release|Any CPU.Build.0 = Release|Any CPU
{BC7E1E6C-30F4-4D37-BEAD-44571FCD4ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC7E1E6C-30F4-4D37-BEAD-44571FCD4ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC7E1E6C-30F4-4D37-BEAD-44571FCD4ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC7E1E6C-30F4-4D37-BEAD-44571FCD4ECD}.Release|Any CPU.Build.0 = Release|Any CPU
{1A16223E-7FC4-41A4-9487-71B1782BC98D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A16223E-7FC4-41A4-9487-71B1782BC98D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A16223E-7FC4-41A4-9487-71B1782BC98D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A16223E-7FC4-41A4-9487-71B1782BC98D}.Release|Any CPU.Build.0 = Release|Any CPU
{3E11B0DB-76CD-4B23-84D3-0976C38B1A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E11B0DB-76CD-4B23-84D3-0976C38B1A20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E11B0DB-76CD-4B23-84D3-0976C38B1A20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E11B0DB-76CD-4B23-84D3-0976C38B1A20}.Release|Any CPU.Build.0 = Release|Any CPU
{13DFF44A-3D98-44B9-ADE0-B447053FECFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13DFF44A-3D98-44B9-ADE0-B447053FECFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13DFF44A-3D98-44B9-ADE0-B447053FECFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13DFF44A-3D98-44B9-ADE0-B447053FECFC}.Release|Any CPU.Build.0 = Release|Any CPU
{F2E87EBD-BA5C-4A22-820F-99B492B39BCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2E87EBD-BA5C-4A22-820F-99B492B39BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2E87EBD-BA5C-4A22-820F-99B492B39BCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2E87EBD-BA5C-4A22-820F-99B492B39BCB}.Release|Any CPU.Build.0 = Release|Any CPU
{2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CEFE30B-F99D-4E62-8D33-2366A6F75F7F}.Release|Any CPU.Build.0 = Release|Any CPU
{F363CEB6-1E2C-4114-AA8C-85452472F82B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F363CEB6-1E2C-4114-AA8C-85452472F82B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F363CEB6-1E2C-4114-AA8C-85452472F82B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F363CEB6-1E2C-4114-AA8C-85452472F82B}.Release|Any CPU.Build.0 = Release|Any CPU
{06BF8581-BA93-4891-9CE8-0BA4E8C7A200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06BF8581-BA93-4891-9CE8-0BA4E8C7A200}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06BF8581-BA93-4891-9CE8-0BA4E8C7A200}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06BF8581-BA93-4891-9CE8-0BA4E8C7A200}.Release|Any CPU.Build.0 = Release|Any CPU
{0B57E5F4-0E40-4AB6-82F7-E8A2D63AE2F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B57E5F4-0E40-4AB6-82F7-E8A2D63AE2F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B57E5F4-0E40-4AB6-82F7-E8A2D63AE2F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B57E5F4-0E40-4AB6-82F7-E8A2D63AE2F5}.Release|Any CPU.Build.0 = Release|Any CPU
{8C1E543A-D1BF-4B66-86C9-0897DD50B044}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C1E543A-D1BF-4B66-86C9-0897DD50B044}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C1E543A-D1BF-4B66-86C9-0897DD50B044}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C1E543A-D1BF-4B66-86C9-0897DD50B044}.Release|Any CPU.Build.0 = Release|Any CPU
{519EDF78-AA47-4A55-8953-5DF0CD408679}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{519EDF78-AA47-4A55-8953-5DF0CD408679}.Debug|Any CPU.Build.0 = Debug|Any CPU
{519EDF78-AA47-4A55-8953-5DF0CD408679}.Release|Any CPU.ActiveCfg = Release|Any CPU
{519EDF78-AA47-4A55-8953-5DF0CD408679}.Release|Any CPU.Build.0 = Release|Any CPU
{0E05843C-001E-4773-A1E3-1E48EC18BA36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E05843C-001E-4773-A1E3-1E48EC18BA36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E05843C-001E-4773-A1E3-1E48EC18BA36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E05843C-001E-4773-A1E3-1E48EC18BA36}.Release|Any CPU.Build.0 = Release|Any CPU
{3531934C-7535-4A18-B598-C21359004324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3531934C-7535-4A18-B598-C21359004324}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3531934C-7535-4A18-B598-C21359004324}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3531934C-7535-4A18-B598-C21359004324}.Release|Any CPU.Build.0 = Release|Any CPU
{2DD36D08-FBC6-40E1-8EB9-3C94ABE6985E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DD36D08-FBC6-40E1-8EB9-3C94ABE6985E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DD36D08-FBC6-40E1-8EB9-3C94ABE6985E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2DD36D08-FBC6-40E1-8EB9-3C94ABE6985E}.Release|Any CPU.Build.0 = Release|Any CPU
{1234CBB6-22C1-42D0-828B-1D1E7085EF33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1234CBB6-22C1-42D0-828B-1D1E7085EF33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1234CBB6-22C1-42D0-828B-1D1E7085EF33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1234CBB6-22C1-42D0-828B-1D1E7085EF33}.Release|Any CPU.Build.0 = Release|Any CPU
{E8DA696D-F948-4967-BF75-1570A2957C3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8DA696D-F948-4967-BF75-1570A2957C3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8DA696D-F948-4967-BF75-1570A2957C3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8DA696D-F948-4967-BF75-1570A2957C3B}.Release|Any CPU.Build.0 = Release|Any CPU
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{39A0FE24-A920-4C13-9BB9-18483FECD55D} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{6E4EC91D-F0D3-42BC-8306-09671FA9E332} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{63476AD1-106A-4086-8B0E-E295567BC526} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{BC7E1E6C-30F4-4D37-BEAD-44571FCD4ECD} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{1A16223E-7FC4-41A4-9487-71B1782BC98D} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{3E11B0DB-76CD-4B23-84D3-0976C38B1A20} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{13DFF44A-3D98-44B9-ADE0-B447053FECFC} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{F2E87EBD-BA5C-4A22-820F-99B492B39BCB} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{F363CEB6-1E2C-4114-AA8C-85452472F82B} = {F66727D5-726B-41DF-8318-6E12733B278F}
{06BF8581-BA93-4891-9CE8-0BA4E8C7A200} = {F66727D5-726B-41DF-8318-6E12733B278F}
{0B57E5F4-0E40-4AB6-82F7-E8A2D63AE2F5} = {F66727D5-726B-41DF-8318-6E12733B278F}
{8C1E543A-D1BF-4B66-86C9-0897DD50B044} = {F66727D5-726B-41DF-8318-6E12733B278F}
{519EDF78-AA47-4A55-8953-5DF0CD408679} = {F66727D5-726B-41DF-8318-6E12733B278F}
{0E05843C-001E-4773-A1E3-1E48EC18BA36} = {F66727D5-726B-41DF-8318-6E12733B278F}
{3531934C-7535-4A18-B598-C21359004324} = {F66727D5-726B-41DF-8318-6E12733B278F}
{2DD36D08-FBC6-40E1-8EB9-3C94ABE6985E} = {F66727D5-726B-41DF-8318-6E12733B278F}
{1234CBB6-22C1-42D0-828B-1D1E7085EF33} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{E8DA696D-F948-4967-BF75-1570A2957C3B} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
{EB44CAF3-EB1B-4FD1-B29C-DA592568E013} = {F7676E7B-4F14-48D9-8985-3FA73BF633BD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2A8747DC-91DB-4ECB-AB4A-5504F9D715D9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2A8747DC-91DB-4ECB-AB4A-5504F9D715D9}
EndGlobalSection
EndGlobal
================================================
FILE: README.md
================================================
# LearnOpenTK
For a more comprehensive written tutorial go to the [Learn section of OpenTK.net](https://opentk.net/learn/index.html) site.
A port of [the tutorials at LearnOpenGL](https://learnopengl.com/) to C#/OpenTK.
These tutorials serve as a complement to the website tutorial and contains working samples of the different chapters.