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