├── Lab5.MacOS.sln ├── Lab5.sln ├── Lab5 ├── Lab5.MacOS.csproj ├── Lab5.csproj ├── Program.cs ├── References │ ├── OpenTK.dll │ └── OpenTK.dll.config ├── Shaders │ ├── CompositePS.glsl │ ├── CompositeVS.glsl │ ├── CubePS.glsl │ ├── CubeVS.glsl │ ├── LightingPS.glsl │ └── LightingVS.glsl └── Textures │ └── crate.jpg ├── Readme.md ├── screenshot1.jpg └── screenshot2.jpg /Lab5.MacOS.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab5.MacOS", "Lab5\Lab5.MacOS.csproj", "{B94F0150-8993-47E7-9950-A5883BFD2FEB}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {B94F0150-8993-47E7-9950-A5883BFD2FEB}.Debug|x86.ActiveCfg = Debug|x86 13 | {B94F0150-8993-47E7-9950-A5883BFD2FEB}.Debug|x86.Build.0 = Debug|x86 14 | {B94F0150-8993-47E7-9950-A5883BFD2FEB}.Release|x86.ActiveCfg = Release|x86 15 | {B94F0150-8993-47E7-9950-A5883BFD2FEB}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(MonoDevelopProperties) = preSolution 18 | StartupItem = Lab5\Lab5.MacOS.csproj 19 | EndGlobalSection 20 | GlobalSection(SolutionProperties) = preSolution 21 | HideSolutionNode = FALSE 22 | EndGlobalSection 23 | EndGlobal 24 | -------------------------------------------------------------------------------- /Lab5.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab5", "Lab5\Lab5.csproj", "{9FB23A69-954F-4AA6-B90D-8AB4EDE05CD2}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|x86 = Debug|x86 9 | Release|x86 = Release|x86 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {9FB23A69-954F-4AA6-B90D-8AB4EDE05CD2}.Debug|x86.ActiveCfg = Debug|x86 13 | {9FB23A69-954F-4AA6-B90D-8AB4EDE05CD2}.Debug|x86.Build.0 = Debug|x86 14 | {9FB23A69-954F-4AA6-B90D-8AB4EDE05CD2}.Release|x86.ActiveCfg = Release|x86 15 | {9FB23A69-954F-4AA6-B90D-8AB4EDE05CD2}.Release|x86.Build.0 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /Lab5/Lab5.MacOS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 10.0.0 7 | 2.0 8 | {B94F0150-8993-47E7-9950-A5883BFD2FEB} 9 | {948B3504-5B70-4649-8FE4-BDE1FB46EC69};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | Exe 11 | Lab5.MacOS 12 | Lab5.MacOS 13 | 14 | 15 | true 16 | full 17 | false 18 | bin\Debug 19 | DEBUG; 20 | prompt 21 | 4 22 | x86 23 | false 24 | 25 | 26 | none 27 | false 28 | bin\Release 29 | prompt 30 | 4 31 | x86 32 | false 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | References\OpenTK.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | PreserveNewest 54 | 55 | 56 | PreserveNewest 57 | 58 | 59 | PreserveNewest 60 | 61 | 62 | PreserveNewest 63 | 64 | 65 | PreserveNewest 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | -------------------------------------------------------------------------------- /Lab5/Lab5.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {9FB23A69-954F-4AA6-B90D-8AB4EDE05CD2} 9 | Exe 10 | Properties 11 | Lab5 12 | Lab5 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | PreserveNewest 49 | 50 | 51 | PreserveNewest 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | PreserveNewest 58 | 59 | 60 | PreserveNewest 61 | 62 | 63 | PreserveNewest 64 | 65 | 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | 72 | 79 | -------------------------------------------------------------------------------- /Lab5/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Drawing; 4 | 5 | using OpenTK; 6 | using OpenTK.Input; 7 | using OpenTK.Graphics; 8 | using OpenTK.Graphics.OpenGL; 9 | using OpenTK.Platform; 10 | using System.IO; 11 | using System.Reflection; 12 | using System.Collections.Generic; 13 | 14 | namespace Lab5 15 | { 16 | public class SimpleFBO : GameWindow 17 | { 18 | const int TextureSize = 512; 19 | 20 | public SimpleFBO() 21 | : base(TextureSize, TextureSize) 22 | { 23 | } 24 | 25 | uint ColorTexture; 26 | uint NormalTexture; 27 | uint DepthTexture; 28 | uint DeferredFBOHandle; 29 | 30 | uint LightingTexture; 31 | uint LightingFBOHandle; 32 | 33 | int cubeVS; 34 | int cubePS; 35 | int cubeProgram; 36 | 37 | int lightingVS; 38 | int lightingPS; 39 | int lightingProgram; 40 | 41 | int compositeVS; 42 | int compositePS; 43 | int compositeProgram; 44 | 45 | int CubeTexture; 46 | 47 | private int loadTexture(string filename) 48 | { 49 | int id = GL.GenTexture(); 50 | GL.BindTexture(TextureTarget.Texture2D, id); 51 | 52 | Bitmap bmp = new Bitmap(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), filename)); 53 | System.Drawing.Imaging.BitmapData bmp_data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); 54 | 55 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmp_data.Width, bmp_data.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bmp_data.Scan0); 56 | 57 | bmp.UnlockBits(bmp_data); 58 | 59 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 60 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 61 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder); 62 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder); 63 | 64 | return id; 65 | } 66 | 67 | class PointLight 68 | { 69 | public Vector3 Position = Vector3.Zero; 70 | public Vector3 Color = Vector3.One; 71 | public float Radius = 0.5f; 72 | } 73 | 74 | List pointLights = new List(); 75 | 76 | private int compileShader(ShaderType type, string file) 77 | { 78 | int shader = GL.CreateShader(type); 79 | string errorInfo; 80 | int errorCode; 81 | 82 | using (StreamReader source = new StreamReader(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), file))) 83 | { 84 | string data = source.ReadToEnd(); 85 | GL.ShaderSource(shader, data); 86 | } 87 | 88 | GL.CompileShader(shader); 89 | GL.GetShaderInfoLog(shader, out errorInfo); 90 | GL.GetShader(shader, ShaderParameter.CompileStatus, out errorCode); 91 | 92 | if (errorCode != 1) 93 | throw new ApplicationException(errorInfo); 94 | 95 | return shader; 96 | } 97 | 98 | private int compileShaderProgram(int vertexShader, int pixelShader) 99 | { 100 | int program = GL.CreateProgram(); 101 | GL.AttachShader(program, pixelShader); 102 | GL.AttachShader(program, vertexShader); 103 | 104 | GL.LinkProgram(program); 105 | 106 | return program; 107 | } 108 | 109 | private bool showBuffers = false; 110 | 111 | protected override void OnKeyPress(KeyPressEventArgs e) 112 | { 113 | if (e.KeyChar == ' ') 114 | this.showBuffers = !this.showBuffers; 115 | } 116 | 117 | protected override void OnLoad(EventArgs e) 118 | { 119 | if (!GL.GetString(StringName.Extensions).Contains("EXT_framebuffer_object")) 120 | { 121 | System.Windows.Forms.MessageBox.Show 122 | ( 123 | "Your video card does not support Framebuffer Objects. Please update your drivers.", 124 | "FBOs not supported", 125 | System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation 126 | ); 127 | Exit(); 128 | } 129 | 130 | // Add random point lights 131 | const float radius = 0.9f; 132 | Random random = new Random(); 133 | for (int i = 0; i < 100; i++) 134 | { 135 | Vector3 color = new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); 136 | color.Normalize(); 137 | float horizontalAngle = (float)random.NextDouble() * (float)Math.PI * 2.0f; 138 | float verticalAngle = (float)random.NextDouble() * (float)Math.PI; 139 | pointLights.Add(new PointLight 140 | { 141 | Position = new Vector3((float)Math.Sin(horizontalAngle) * (float)Math.Sin(verticalAngle), (float)Math.Cos(verticalAngle), (float)Math.Cos(horizontalAngle) * (float)Math.Sin(verticalAngle)) * radius, 142 | Color = color, 143 | Radius = 0.1f + (float)random.NextDouble() * 0.7f, 144 | }); 145 | } 146 | 147 | GL.Enable(EnableCap.DepthTest); 148 | GL.ClearDepth(1.0f); 149 | GL.DepthFunc(DepthFunction.Lequal); 150 | 151 | this.CubeTexture = this.loadTexture("Textures/crate.jpg"); 152 | 153 | // Create Color Tex 154 | GL.GenTextures(1, out ColorTexture); 155 | GL.BindTexture(TextureTarget.Texture2D, ColorTexture); 156 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, TextureSize, TextureSize, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); 157 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 158 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 159 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder); 160 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder); 161 | 162 | // Create normal tex 163 | GL.GenTextures(1, out NormalTexture); 164 | GL.BindTexture(TextureTarget.Texture2D, NormalTexture); 165 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, TextureSize, TextureSize, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); 166 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 167 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 168 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder); 169 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder); 170 | 171 | // Create Depth Tex 172 | GL.GenTextures(1, out DepthTexture); 173 | GL.BindTexture(TextureTarget.Texture2D, DepthTexture); 174 | GL.TexImage2D(TextureTarget.Texture2D, 0, (PixelInternalFormat)All.R32f, TextureSize, TextureSize, 0, PixelFormat.Red, PixelType.UnsignedInt, IntPtr.Zero); 175 | // things go horribly wrong if DepthComponent's Bitcount does not match the main Framebuffer's Depth 176 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 177 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 178 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder); 179 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder); 180 | 181 | // Create internal depth buffer 182 | uint internalDepthBuffer; 183 | GL.GenTextures(1, out internalDepthBuffer); 184 | GL.BindTexture(TextureTarget.Texture2D, internalDepthBuffer); 185 | GL.TexImage2D(TextureTarget.Texture2D, 0, (PixelInternalFormat)All.DepthComponent32, TextureSize, TextureSize, 0, PixelFormat.DepthComponent, PixelType.UnsignedInt, IntPtr.Zero); 186 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 187 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 188 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder); 189 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder); 190 | 191 | // Create a FBO and attach the textures 192 | GL.Ext.GenFramebuffers(1, out DeferredFBOHandle); 193 | GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, DeferredFBOHandle); 194 | GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, ColorTexture, 0); 195 | GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment1Ext, TextureTarget.Texture2D, NormalTexture, 0); 196 | GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment2Ext, TextureTarget.Texture2D, DepthTexture, 0); 197 | GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.DepthAttachment, TextureTarget.Texture2D, internalDepthBuffer, 0); 198 | 199 | // Create the lighting buffer 200 | GL.GenTextures(1, out LightingTexture); 201 | GL.BindTexture(TextureTarget.Texture2D, LightingTexture); 202 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, TextureSize, TextureSize, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); 203 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); 204 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); 205 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder); 206 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder); 207 | GL.Ext.GenFramebuffers(1, out LightingFBOHandle); 208 | GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, LightingFBOHandle); 209 | GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, LightingTexture, 0); 210 | 211 | GL.PolygonMode(MaterialFace.Front, PolygonMode.Fill); 212 | 213 | this.cubeVS = this.compileShader(ShaderType.VertexShader, "Shaders/CubeVS.glsl"); 214 | this.cubePS = this.compileShader(ShaderType.FragmentShader, "Shaders/CubePS.glsl"); 215 | this.cubeProgram = this.compileShaderProgram(this.cubeVS, this.cubePS); 216 | 217 | this.lightingVS = this.compileShader(ShaderType.VertexShader, "Shaders/LightingVS.glsl"); 218 | this.lightingPS = this.compileShader(ShaderType.FragmentShader, "Shaders/LightingPS.glsl"); 219 | this.lightingProgram = this.compileShaderProgram(this.lightingVS, this.lightingPS); 220 | 221 | this.compositeVS = this.compileShader(ShaderType.VertexShader, "Shaders/CompositeVS.glsl"); 222 | this.compositePS = this.compileShader(ShaderType.FragmentShader, "Shaders/CompositePS.glsl"); 223 | this.compositeProgram = this.compileShaderProgram(this.compositeVS, this.compositePS); 224 | 225 | #region Test for Error 226 | 227 | switch (GL.Ext.CheckFramebufferStatus(FramebufferTarget.FramebufferExt)) 228 | { 229 | case FramebufferErrorCode.FramebufferCompleteExt: 230 | { 231 | Console.WriteLine("FBO: The framebuffer is complete and valid for rendering."); 232 | break; 233 | } 234 | case FramebufferErrorCode.FramebufferIncompleteAttachmentExt: 235 | { 236 | Console.WriteLine("FBO: One or more attachment points are not framebuffer attachment complete. This could mean there’s no texture attached or the format isn’t renderable. For color textures this means the base format must be RGB or RGBA and for depth textures it must be a DEPTH_COMPONENT format. Other causes of this error are that the width or height is zero or the z-offset is out of range in case of render to volume."); 237 | break; 238 | } 239 | case FramebufferErrorCode.FramebufferIncompleteMissingAttachmentExt: 240 | { 241 | Console.WriteLine("FBO: There are no attachments."); 242 | break; 243 | } 244 | /* case FramebufferErrorCode.GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT: 245 | { 246 | Console.WriteLine("FBO: An object has been attached to more than one attachment point."); 247 | break; 248 | }*/ 249 | case FramebufferErrorCode.FramebufferIncompleteDimensionsExt: 250 | { 251 | Console.WriteLine("FBO: Attachments are of different size. All attachments must have the same width and height."); 252 | break; 253 | } 254 | case FramebufferErrorCode.FramebufferIncompleteFormatsExt: 255 | { 256 | Console.WriteLine("FBO: The color attachments have different format. All color attachments must have the same format."); 257 | break; 258 | } 259 | case FramebufferErrorCode.FramebufferIncompleteDrawBufferExt: 260 | { 261 | Console.WriteLine("FBO: An attachment point referenced by GL.DrawBuffers() doesn’t have an attachment."); 262 | break; 263 | } 264 | case FramebufferErrorCode.FramebufferIncompleteReadBufferExt: 265 | { 266 | Console.WriteLine("FBO: The attachment point referenced by GL.ReadBuffers() doesn’t have an attachment."); 267 | break; 268 | } 269 | case FramebufferErrorCode.FramebufferUnsupportedExt: 270 | { 271 | Console.WriteLine("FBO: This particular FBO configuration is not supported by the implementation."); 272 | break; 273 | } 274 | default: 275 | { 276 | Console.WriteLine("FBO: Status unknown. (yes, this is really bad.)"); 277 | break; 278 | } 279 | } 280 | 281 | // using FBO might have changed states, e.g. the FBO might not support stereoscopic views or double buffering 282 | int[] queryinfo = new int[6]; 283 | GL.GetInteger(GetPName.MaxColorAttachmentsExt, out queryinfo[0]); 284 | GL.GetInteger(GetPName.AuxBuffers, out queryinfo[1]); 285 | GL.GetInteger(GetPName.MaxDrawBuffers, out queryinfo[2]); 286 | GL.GetInteger(GetPName.Stereo, out queryinfo[3]); 287 | GL.GetInteger(GetPName.Samples, out queryinfo[4]); 288 | GL.GetInteger(GetPName.Doublebuffer, out queryinfo[5]); 289 | Console.WriteLine("max. ColorBuffers: " + queryinfo[0] + " max. AuxBuffers: " + queryinfo[1] + " max. DrawBuffers: " + queryinfo[2] + 290 | "\nStereo: " + queryinfo[3] + " Samples: " + queryinfo[4] + " DoubleBuffer: " + queryinfo[5]); 291 | 292 | Console.WriteLine("Last GL Error: " + GL.GetError()); 293 | 294 | #endregion Test for Error 295 | } 296 | 297 | protected override void OnUnload(EventArgs e) 298 | { 299 | // Clean up what we allocated before exiting 300 | if (ColorTexture != 0) 301 | GL.DeleteTextures(1, ref ColorTexture); 302 | 303 | if (DepthTexture != 0) 304 | GL.DeleteTextures(1, ref DepthTexture); 305 | 306 | if (NormalTexture != 0) 307 | GL.DeleteTextures(1, ref NormalTexture); 308 | 309 | if (DeferredFBOHandle != 0) 310 | GL.Ext.DeleteFramebuffers(1, ref DeferredFBOHandle); 311 | } 312 | 313 | private Matrix4 projectionMatrix; 314 | 315 | protected override void OnResize(EventArgs e) 316 | { 317 | GL.Viewport(0, 0, Width, Height); 318 | 319 | double aspect_ratio = Width / (double)Height; 320 | 321 | this.projectionMatrix = OpenTK.Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)aspect_ratio, 1, 64); 322 | 323 | base.OnResize(e); 324 | } 325 | 326 | float angle = 0.0f; 327 | 328 | protected override void OnUpdateFrame(FrameEventArgs e) 329 | { 330 | base.OnUpdateFrame(e); 331 | 332 | angle += (float)e.Time; 333 | 334 | if (Keyboard[Key.Escape]) 335 | this.Exit(); 336 | } 337 | 338 | private Matrix4 viewMatrix; 339 | 340 | private void sphereVertex(int horizontal, int vertical, float horizontalInterval, float verticalInterval, float radius) 341 | { 342 | float horizontalAngle = horizontal * horizontalInterval, verticalAngle = vertical * verticalInterval; 343 | Vector3 normal = new Vector3((float)Math.Sin(horizontalAngle) * (float)Math.Sin(verticalAngle), (float)Math.Cos(verticalAngle), (float)Math.Cos(horizontalAngle) * (float)Math.Sin(verticalAngle)); 344 | GL.Normal3(normal); 345 | GL.Vertex3(normal * radius); 346 | } 347 | 348 | protected override void OnRenderFrame(FrameEventArgs e) 349 | { 350 | const float farPlane = 6.0f; 351 | Vector3 cameraPosition = new Vector3(0, 1, 3); 352 | this.viewMatrix = OpenTK.Matrix4.LookAt(cameraPosition, Vector3.Zero, Vector3.UnitY); 353 | Matrix4 viewProjection = this.viewMatrix * this.projectionMatrix; 354 | 355 | GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, DeferredFBOHandle); 356 | GL.PushAttrib(AttribMask.ViewportBit); 357 | { 358 | GL.Viewport(0, 0, TextureSize, TextureSize); 359 | 360 | DrawBuffersEnum[] buffers = new[] 361 | { 362 | (DrawBuffersEnum)FramebufferAttachment.ColorAttachment0Ext, 363 | (DrawBuffersEnum)FramebufferAttachment.ColorAttachment1Ext, 364 | (DrawBuffersEnum)FramebufferAttachment.ColorAttachment2Ext, 365 | }; 366 | 367 | GL.DrawBuffers(buffers.Length, buffers); 368 | 369 | GL.ClearColor(0f, 0f, 0f, 0f); 370 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 371 | 372 | Matrix4 world = Matrix4.CreateRotationY(this.angle); 373 | 374 | GL.UseProgram(this.cubeProgram); 375 | GL.UniformMatrix4(GL.GetUniformLocation(this.cubeProgram, "ViewProjectionMatrix"), false, ref viewProjection); 376 | GL.UniformMatrix4(GL.GetUniformLocation(this.cubeProgram, "WorldMatrix"), false, ref world); 377 | GL.Uniform3(GL.GetUniformLocation(this.cubeProgram, "CameraPosition"), ref cameraPosition); 378 | GL.Uniform1(GL.GetUniformLocation(this.cubeProgram, "FarPlane"), farPlane); 379 | 380 | GL.ActiveTexture(TextureUnit.Texture0); 381 | GL.BindTexture(TextureTarget.Texture2D, CubeTexture); 382 | GL.Uniform1(GL.GetUniformLocation(this.lightingProgram, "DiffuseTexture"), 0); 383 | 384 | GL.CullFace(CullFaceMode.Back); 385 | GL.Begin(BeginMode.Quads); 386 | { 387 | const float size = 0.5f; 388 | 389 | // Back 390 | GL.Normal3(0, 0, -1); 391 | GL.TexCoord2(0, 0); 392 | GL.Vertex3(-size, -size, -size); 393 | GL.TexCoord2(0, 1); 394 | GL.Vertex3(-size, size, -size); 395 | GL.TexCoord2(1, 1); 396 | GL.Vertex3(size, size, -size); 397 | GL.TexCoord2(1, 0); 398 | GL.Vertex3(size, -size, -size); 399 | 400 | // Front 401 | GL.Normal3(0, 0, 1); 402 | GL.TexCoord2(0, 0); 403 | GL.Vertex3(-size, -size, size); 404 | GL.TexCoord2(1, 0); 405 | GL.Vertex3(size, -size, size); 406 | GL.TexCoord2(1, 1); 407 | GL.Vertex3(size, size, size); 408 | GL.TexCoord2(0, 1); 409 | GL.Vertex3(-size, size, size); 410 | 411 | // Left 412 | GL.Normal3(-1, 0, 0); 413 | GL.TexCoord2(0, 1); 414 | GL.Vertex3(-size, -size, size); 415 | GL.TexCoord2(1, 1); 416 | GL.Vertex3(-size, size, size); 417 | GL.TexCoord2(1, 0); 418 | GL.Vertex3(-size, size, -size); 419 | GL.TexCoord2(0, 0); 420 | GL.Vertex3(-size, -size, -size); 421 | 422 | // Right 423 | GL.Normal3(1, 0, 0); 424 | GL.TexCoord2(0, 0); 425 | GL.Vertex3(size, -size, -size); 426 | GL.TexCoord2(0, 1); 427 | GL.Vertex3(size, size, -size); 428 | GL.TexCoord2(1, 1); 429 | GL.Vertex3(size, size, size); 430 | GL.TexCoord2(1, 0); 431 | GL.Vertex3(size, -size, size); 432 | 433 | // Top 434 | GL.Normal3(0, 1, 0); 435 | GL.TexCoord2(0, 1); 436 | GL.Vertex3(-size, size, size); 437 | GL.TexCoord2(1, 1); 438 | GL.Vertex3(size, size, size); 439 | GL.TexCoord2(1, 0); 440 | GL.Vertex3(size, size, -size); 441 | GL.TexCoord2(0, 0); 442 | GL.Vertex3(-size, size, -size); 443 | 444 | // Bottom 445 | GL.Normal3(0, -1, 0); 446 | GL.TexCoord2(0, 0); 447 | GL.Vertex3(-size, -size, -size); 448 | GL.TexCoord2(0, 1); 449 | GL.Vertex3(size, -size, -size); 450 | GL.TexCoord2(1, 1); 451 | GL.Vertex3(size, -size, size); 452 | GL.TexCoord2(0, 1); 453 | GL.Vertex3(-size, -size, size); 454 | } 455 | GL.End(); 456 | GL.UseProgram(0); 457 | } 458 | GL.PopAttrib(); 459 | 460 | // Render lighting buffer 461 | GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, LightingFBOHandle); 462 | GL.PushAttrib(AttribMask.ViewportBit); 463 | { 464 | GL.Viewport(0, 0, TextureSize, TextureSize); 465 | 466 | GL.ClearColor(0f, 0f, 0f, 0f); 467 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 468 | 469 | GL.PolygonMode(MaterialFace.Back, PolygonMode.Fill); 470 | GL.Disable(EnableCap.DepthTest); 471 | GL.Enable(EnableCap.Blend); 472 | GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.One); 473 | GL.UseProgram(this.lightingProgram); 474 | GL.UniformMatrix4(GL.GetUniformLocation(this.lightingProgram, "ViewProjectionMatrix"), false, ref viewProjection); 475 | GL.Uniform3(GL.GetUniformLocation(this.lightingProgram, "CameraPosition"), ref cameraPosition); 476 | GL.Uniform1(GL.GetUniformLocation(this.lightingProgram, "FarPlane"), farPlane); 477 | 478 | // Read from the normal and depth textures 479 | GL.ActiveTexture(TextureUnit.Texture0); 480 | GL.BindTexture(TextureTarget.Texture2D, DepthTexture); 481 | GL.Uniform1(GL.GetUniformLocation(this.lightingProgram, "DepthBuffer"), 0); 482 | 483 | GL.ActiveTexture(TextureUnit.Texture1); 484 | GL.BindTexture(TextureTarget.Texture2D, NormalTexture); 485 | GL.Uniform1(GL.GetUniformLocation(this.lightingProgram, "NormalBuffer"), 1); 486 | 487 | // Display the back faces only. This prevents issues when the camera is inside a point light. 488 | GL.CullFace(CullFaceMode.Front); 489 | 490 | foreach (PointLight light in this.pointLights) 491 | { 492 | GL.Uniform3(GL.GetUniformLocation(this.lightingProgram, "Color"), ref light.Color); 493 | GL.Uniform3(GL.GetUniformLocation(this.lightingProgram, "Position"), ref light.Position); 494 | GL.Uniform1(GL.GetUniformLocation(this.lightingProgram, "Radius"), light.Radius); 495 | Matrix4 world = Matrix4.CreateTranslation(light.Position); 496 | GL.UniformMatrix4(GL.GetUniformLocation(this.lightingProgram, "WorldMatrix"), false, ref world); 497 | 498 | GL.Begin(BeginMode.Quads); 499 | { 500 | const int verticalSegments = 16, horizontalSegments = 16; 501 | const float verticalInterval = (float)Math.PI / (float)verticalSegments, horizontalInterval = ((float)Math.PI * 2.0f) / (float)horizontalSegments; 502 | for (int vertical = 0; vertical < verticalSegments; vertical++) 503 | { 504 | for (int horizontal = 0; horizontal < horizontalSegments; horizontal++) 505 | { 506 | this.sphereVertex(horizontal, vertical, horizontalInterval, verticalInterval, light.Radius); 507 | this.sphereVertex(horizontal, vertical + 1, horizontalInterval, verticalInterval, light.Radius); 508 | this.sphereVertex(horizontal + 1, vertical + 1, horizontalInterval, verticalInterval, light.Radius); 509 | this.sphereVertex(horizontal + 1, vertical, horizontalInterval, verticalInterval, light.Radius); 510 | } 511 | } 512 | } 513 | GL.End(); 514 | } 515 | GL.UseProgram(0); 516 | GL.Disable(EnableCap.Blend); 517 | GL.Enable(EnableCap.DepthTest); 518 | GL.ActiveTexture(TextureUnit.Texture0); 519 | } 520 | GL.PopAttrib(); 521 | GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO 522 | 523 | GL.ClearColor(0f, 0f, 0f, 0f); 524 | 525 | GL.Enable(EnableCap.Texture2D); // enable Texture Mapping 526 | GL.BindTexture(TextureTarget.Texture2D, 0); // bind default texture 527 | 528 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 529 | 530 | GL.MatrixMode(MatrixMode.Projection); 531 | GL.LoadMatrix(ref Matrix4.Identity); 532 | GL.MatrixMode(MatrixMode.Modelview); 533 | GL.CullFace(CullFaceMode.Back); 534 | 535 | if (this.showBuffers) 536 | { 537 | GL.PushMatrix(); 538 | { 539 | // Draw the Color Texture 540 | GL.Scale(0.5f, 0.5f, 0.5f); 541 | GL.Translate(-1.0f, -1.0f, 0f); 542 | GL.BindTexture(TextureTarget.Texture2D, ColorTexture); 543 | GL.Begin(BeginMode.Quads); 544 | { 545 | GL.TexCoord2(0f, 1f); 546 | GL.Vertex2(-1.0f, 1.0f); 547 | GL.TexCoord2(0.0f, 0.0f); 548 | GL.Vertex2(-1.0f, -1.0f); 549 | GL.TexCoord2(1.0f, 0.0f); 550 | GL.Vertex2(1.0f, -1.0f); 551 | GL.TexCoord2(1.0f, 1.0f); 552 | GL.Vertex2(1.0f, 1.0f); 553 | } 554 | GL.End(); 555 | } 556 | GL.PopMatrix(); 557 | 558 | GL.PushMatrix(); 559 | { 560 | // Draw the Depth Texture 561 | GL.Scale(0.5f, 0.5f, 0.5f); 562 | GL.Translate(1.0f, -1.0f, 0f); 563 | GL.BindTexture(TextureTarget.Texture2D, DepthTexture); 564 | GL.Begin(BeginMode.Quads); 565 | { 566 | GL.TexCoord2(0f, 1f); 567 | GL.Vertex2(-1.0f, 1.0f); 568 | GL.TexCoord2(0.0f, 0.0f); 569 | GL.Vertex2(-1.0f, -1.0f); 570 | GL.TexCoord2(1.0f, 0.0f); 571 | GL.Vertex2(1.0f, -1.0f); 572 | GL.TexCoord2(1.0f, 1.0f); 573 | GL.Vertex2(1.0f, 1.0f); 574 | } 575 | GL.End(); 576 | } 577 | GL.PopMatrix(); 578 | 579 | GL.PushMatrix(); 580 | { 581 | // Draw the normal Texture 582 | GL.Scale(0.5f, 0.5f, 0.5f); 583 | GL.Translate(-1.0f, 1.0f, 0f); 584 | GL.BindTexture(TextureTarget.Texture2D, NormalTexture); 585 | GL.Begin(BeginMode.Quads); 586 | { 587 | GL.TexCoord2(0f, 1f); 588 | GL.Vertex2(-1.0f, 1.0f); 589 | GL.TexCoord2(0.0f, 0.0f); 590 | GL.Vertex2(-1.0f, -1.0f); 591 | GL.TexCoord2(1.0f, 0.0f); 592 | GL.Vertex2(1.0f, -1.0f); 593 | GL.TexCoord2(1.0f, 1.0f); 594 | GL.Vertex2(1.0f, 1.0f); 595 | } 596 | GL.End(); 597 | } 598 | GL.PopMatrix(); 599 | 600 | GL.PushMatrix(); 601 | { 602 | // Draw the normal Texture 603 | GL.Scale(0.5f, 0.5f, 0.5f); 604 | GL.Translate(1.0f, 1.0f, 0f); 605 | GL.BindTexture(TextureTarget.Texture2D, LightingTexture); 606 | GL.Begin(BeginMode.Quads); 607 | { 608 | GL.TexCoord2(0f, 1f); 609 | GL.Vertex2(-1.0f, 1.0f); 610 | GL.TexCoord2(0.0f, 0.0f); 611 | GL.Vertex2(-1.0f, -1.0f); 612 | GL.TexCoord2(1.0f, 0.0f); 613 | GL.Vertex2(1.0f, -1.0f); 614 | GL.TexCoord2(1.0f, 1.0f); 615 | GL.Vertex2(1.0f, 1.0f); 616 | } 617 | GL.End(); 618 | } 619 | GL.PopMatrix(); 620 | } 621 | else 622 | { 623 | // Render composite 624 | GL.UseProgram(this.compositeProgram); 625 | GL.ActiveTexture(TextureUnit.Texture0); 626 | GL.BindTexture(TextureTarget.Texture2D, ColorTexture); 627 | GL.Uniform1(GL.GetUniformLocation(this.compositeProgram, "ColorBuffer"), 0); 628 | 629 | GL.ActiveTexture(TextureUnit.Texture1); 630 | GL.BindTexture(TextureTarget.Texture2D, LightingTexture); 631 | GL.Uniform1(GL.GetUniformLocation(this.compositeProgram, "LightingBuffer"), 1); 632 | 633 | GL.Begin(BeginMode.Quads); 634 | { 635 | GL.TexCoord2(0f, 1f); 636 | GL.Vertex2(-1.0f, 1.0f); 637 | GL.TexCoord2(0.0f, 0.0f); 638 | GL.Vertex2(-1.0f, -1.0f); 639 | GL.TexCoord2(1.0f, 0.0f); 640 | GL.Vertex2(1.0f, -1.0f); 641 | GL.TexCoord2(1.0f, 1.0f); 642 | GL.Vertex2(1.0f, 1.0f); 643 | } 644 | GL.End(); 645 | } 646 | 647 | this.SwapBuffers(); 648 | } 649 | 650 | #region public static void Main() 651 | 652 | /// 653 | /// Entry point of this example. 654 | /// 655 | [STAThread] 656 | public static void Main() 657 | { 658 | using (SimpleFBO example = new SimpleFBO()) 659 | example.Run(30.0, 0.0); 660 | } 661 | 662 | #endregion 663 | } 664 | } -------------------------------------------------------------------------------- /Lab5/References/OpenTK.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etodd/opentk-deferred-rendering/972320ae66d158662335533050ac5bfb740071b2/Lab5/References/OpenTK.dll -------------------------------------------------------------------------------- /Lab5/References/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Lab5/Shaders/CompositePS.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 texCoord; 2 | 3 | uniform sampler2D ColorBuffer; 4 | uniform sampler2D LightingBuffer; 5 | 6 | void main() 7 | { 8 | gl_FragColor = vec4(texture2D(LightingBuffer, texCoord).xyz * 2.0 * texture2D(ColorBuffer, texCoord).xyz, 1.0); 9 | } -------------------------------------------------------------------------------- /Lab5/Shaders/CompositeVS.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 ViewProjectionMatrix; 2 | uniform mat4 InverseViewProjectionMatrix; 3 | uniform mat4 WorldMatrix; 4 | uniform vec3 CameraPosition; 5 | uniform float FarPlane; 6 | 7 | attribute vec4 position; 8 | 9 | varying vec2 texCoord; 10 | varying vec3 viewRay; 11 | 12 | void main() 13 | { 14 | gl_Position = position; 15 | vec4 pos = position; 16 | pos.xyz /= pos.w; 17 | texCoord = pos.xy * 0.5 + 0.5; 18 | } -------------------------------------------------------------------------------- /Lab5/Shaders/CubePS.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 normal; 2 | varying float depth; 3 | 4 | uniform sampler2D DiffuseTexture; 5 | 6 | void main() 7 | { 8 | gl_FragData[0] = vec4(texture2D(DiffuseTexture, gl_TexCoord[0].st).xyz, 1.0); 9 | gl_FragData[1] = vec4(normalize(normal) * 0.5 + 0.5, 1.0); 10 | gl_FragData[2] = vec4(depth, 0, 0, 1); 11 | } -------------------------------------------------------------------------------- /Lab5/Shaders/CubeVS.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 ViewProjectionMatrix; 2 | uniform mat4 WorldMatrix; 3 | uniform vec3 CameraPosition; 4 | uniform float FarPlane; 5 | 6 | attribute vec4 position; 7 | 8 | varying vec3 normal; 9 | varying float depth; 10 | 11 | void main() 12 | { 13 | vec4 worldPos = WorldMatrix * position; 14 | gl_Position = ViewProjectionMatrix * worldPos; 15 | normal = (WorldMatrix * vec4(gl_Normal, 0.0)).xyz; 16 | depth = length(worldPos.xyz - CameraPosition) / FarPlane; 17 | gl_TexCoord[0] = gl_MultiTexCoord0; 18 | } -------------------------------------------------------------------------------- /Lab5/Shaders/LightingPS.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 clipSpacePosition; 2 | varying vec3 viewRay; 3 | 4 | uniform vec3 Color; 5 | uniform float Radius; 6 | uniform vec3 Position; 7 | uniform vec3 CameraPosition; 8 | uniform float FarPlane; 9 | 10 | uniform sampler2D DepthBuffer; 11 | uniform sampler2D NormalBuffer; 12 | 13 | void main() 14 | { 15 | float depth = texture2D(DepthBuffer, clipSpacePosition).x * FarPlane; 16 | vec3 pixelPosition = CameraPosition + (normalize(viewRay) * depth); 17 | vec3 toLight = Position - pixelPosition; 18 | float lightDistance = length(toLight); 19 | toLight /= lightDistance; 20 | vec3 normal = (texture2D(NormalBuffer, clipSpacePosition).xyz - 0.5) * 2.0; 21 | gl_FragColor = vec4(Color * clamp(dot(toLight, normal), 0.0, 1.0) * clamp(1.0 - (lightDistance / Radius), 0.0, 1.0) * 0.5, 1.0); 22 | } -------------------------------------------------------------------------------- /Lab5/Shaders/LightingVS.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 ViewProjectionMatrix; 2 | uniform mat4 InverseViewProjectionMatrix; 3 | uniform mat4 WorldMatrix; 4 | uniform vec3 CameraPosition; 5 | uniform float FarPlane; 6 | 7 | attribute vec4 position; 8 | 9 | varying vec2 clipSpacePosition; 10 | varying vec3 viewRay; 11 | 12 | void main() 13 | { 14 | vec4 pos = ViewProjectionMatrix * WorldMatrix * position; 15 | gl_Position = pos; 16 | pos.xyz /= pos.w; 17 | clipSpacePosition.x = pos.x * 0.5 + 0.5; 18 | clipSpacePosition.y = pos.y * 0.5 + 0.5; 19 | viewRay = (WorldMatrix * position).xyz - CameraPosition; 20 | } -------------------------------------------------------------------------------- /Lab5/Textures/crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etodd/opentk-deferred-rendering/972320ae66d158662335533050ac5bfb740071b2/Lab5/Textures/crate.jpg -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # OpenTK Deferred Rendering Sample 4 | by [Evan Todd](http://et1337.wordpress.com) 5 | 6 | ## Hardware Requirements 7 | 8 | Support for Multiple Render Targets and Shader Model 2 9 | 10 | ## Mac Requirements 11 | Mono, MonoDevelop, and OpenTK. 12 | 13 | ## Windows Requirements 14 | Visual Studio and OpenTK. 15 | 16 | Download OpenTK from [here](https://sourceforge.net/projects/opentk/files/latest/download). 17 | 18 | ## Controls 19 | Spacebar - Toggle display mode 20 | 21 | This sample demonstrates a very basic deferred renderer. While a forward 22 | renderer normally calculates lighting as it renders the scene, a deferred 23 | renderer saves information about the materials in the scene to a number of 24 | buffers. These are later sampled to calculate the lighting, and then composited 25 | into a final image. There are many advantages to the technique, but probably 26 | the most useful feature is its ability to render large amounts of lights at 27 | relatively low cost, irrespective of the triangular complexity of the scene. 28 | 29 | This sample renders a simple textured cube with 100 deferred point lights. Press 30 | spacebar to view the buffers used to generate the scene. The buffers are 31 | (clockwise from the top left): normals, light accumulation, depth, and diffuse. 32 | 33 | The renderer is greatly simplified in the interest of time. Real high dynamic 34 | range is essential for a real deferred renderer. This sample simulates it by 35 | storing scaled values in the lighting buffer. 36 | 37 | ## The MIT License (MIT) 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 44 | -------------------------------------------------------------------------------- /screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etodd/opentk-deferred-rendering/972320ae66d158662335533050ac5bfb740071b2/screenshot1.jpg -------------------------------------------------------------------------------- /screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etodd/opentk-deferred-rendering/972320ae66d158662335533050ac5bfb740071b2/screenshot2.jpg --------------------------------------------------------------------------------