· 4 years ago · Aug 16, 2021, 09:30 AM
1using System;
2
3using OpenTK.Graphics.OpenGL;
4using OpenTK.Windowing.Common;
5using OpenTK.Windowing.Desktop;
6using OpenTK.Mathematics;
7
8namespace Renderer
9{
10 internal class RenderWindow : GameWindow
11 {
12 private int vertex_array_object;
13 private int vertex_buffer_object;
14 private Shader _shader;
15
16
17 float[] vertices = {
18 -0.5f, -0.5f, 0.0f, //Bottom-left vertex
19 0.5f, -0.5f, 0.0f, //Bottom-right vertex
20 0.0f, 0.5f, 0.0f //Top vertex
21 };
22
23 public RenderWindow(int width, int height) : base(
24 new GameWindowSettings()
25 {
26 IsMultiThreaded = true
27 },
28 new NativeWindowSettings()
29 {
30 Size = new Vector2i(width, height),
31 Title = "OBJ renderer",
32 NumberOfSamples = 8,
33 APIVersion = new Version(4, 6),
34 API = ContextAPI.OpenGL,
35 Flags = ContextFlags.Offscreen,
36 StartFocused = false
37 })
38 { }
39
40 protected override void OnLoad()
41 {
42 GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
43
44
45 vertex_buffer_object = GL.GenBuffer();
46 GL.BindBuffer(BufferTarget.ArrayBuffer, vertex_buffer_object);
47 GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
48
49 vertex_array_object = GL.GenVertexArray();
50 GL.BindVertexArray(vertex_array_object);
51
52 GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
53 GL.EnableVertexAttribArray(0);
54
55 _shader = new Shader(@"#version 330 core
56layout (location = 0) in vec3 aPosition;
57
58void main()
59{
60 gl_Position = vec4(aPosition, 1.0);
61}", @"#version 330 core
62out vec4 FragColor;
63
64void main()
65{
66 FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
67}");
68
69 _shader.Use();
70
71 //Code goes here
72
73 base.OnLoad();
74
75 }
76 protected override void OnRenderFrame(FrameEventArgs args)
77 {
78 base.OnRenderFrame(args);
79
80 GL.Clear(ClearBufferMask.ColorBufferBit);
81
82 _shader.Use();
83
84 GL.BindVertexArray(vertex_array_object);
85
86 GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
87
88 SwapBuffers();
89 }
90
91 protected override void OnUpdateFrame(FrameEventArgs e)
92 {
93 base.OnUpdateFrame(e);
94
95 }
96
97 protected override void OnResize(ResizeEventArgs e)
98 {
99 base.OnResize(e);
100 GL.Viewport(0, 0, e.Width, e.Height);
101 }
102
103
104 protected override void OnUnload()
105 {
106 GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
107 GL.BindVertexArray(0);
108 GL.UseProgram(0);
109
110 // Delete all the resources.
111 GL.DeleteBuffer(vertex_buffer_object);
112 GL.DeleteVertexArray(vertex_array_object);
113
114 GL.DeleteProgram(_shader.Handle);
115
116 base.OnUnload();
117 }
118 }
119
120 public class Shader
121 {
122 public readonly int Handle;
123
124 private readonly Dictionary<string, int> _uniformLocations;
125
126 // This is how you create a simple shader.
127 // Shaders are written in GLSL, which is a language very similar to C in its semantics.
128 // The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on.
129 // A commented example of GLSL can be found in shader.vert.
130 public Shader(string vertPath, string fragPath)
131 {
132 // There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders.
133 // The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader.
134 // The vertex shader won't be too important here, but they'll be more important later.
135 // The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel.
136 // The fragment shader is what we'll be using the most here.
137
138 // Load vertex shader and compile
139 var shaderSource = vertPath;
140
141 // GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created.
142 var vertexShader = GL.CreateShader(ShaderType.VertexShader);
143
144 // Now, bind the GLSL source code
145 GL.ShaderSource(vertexShader, shaderSource);
146
147 // And then compile
148 CompileShader(vertexShader);
149
150 // We do the same for the fragment shader.
151 shaderSource = fragPath;
152 var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
153 GL.ShaderSource(fragmentShader, shaderSource);
154 CompileShader(fragmentShader);
155
156 // These two shaders must then be merged into a shader program, which can then be used by OpenGL.
157 // To do this, create a program...
158 Handle = GL.CreateProgram();
159
160 // Attach both shaders...
161 GL.AttachShader(Handle, vertexShader);
162 GL.AttachShader(Handle, fragmentShader);
163
164 // And then link them together.
165 LinkProgram(Handle);
166
167 // 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.
168 // Detach them, and then delete them.
169 GL.DetachShader(Handle, vertexShader);
170 GL.DetachShader(Handle, fragmentShader);
171 GL.DeleteShader(fragmentShader);
172 GL.DeleteShader(vertexShader);
173
174 // The shader is now ready to go, but first, we're going to cache all the shader uniform locations.
175 // Querying this from the shader is very slow, so we do it once on initialization and reuse those values
176 // later.
177
178 // First, we have to get the number of active uniforms in the shader.
179 GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms);
180
181 // Next, allocate the dictionary to hold the locations.
182 _uniformLocations = new Dictionary<string, int>();
183
184 // Loop over all the uniforms,
185 for (var i = 0; i < numberOfUniforms; i++)
186 {
187 // get the name of this uniform,
188 var key = GL.GetActiveUniform(Handle, i, out _, out _);
189
190 // get the location,
191 var location = GL.GetUniformLocation(Handle, key);
192
193 // and then add it to the dictionary.
194 _uniformLocations.Add(key, location);
195 }
196 }
197
198 private static void CompileShader(int shader)
199 {
200 // Try to compile the shader
201 GL.CompileShader(shader);
202
203 // Check for compilation errors
204 GL.GetShader(shader, ShaderParameter.CompileStatus, out var code);
205 if (code != (int)All.True)
206 {
207 // We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
208 var infoLog = GL.GetShaderInfoLog(shader);
209 throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}");
210 }
211 }
212
213 private static void LinkProgram(int program)
214 {
215 // We link the program
216 GL.LinkProgram(program);
217
218 // Check for linking errors
219 GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code);
220 if (code != (int)All.True)
221 {
222 // We can use `GL.GetProgramInfoLog(program)` to get information about the error.
223 throw new Exception($"Error occurred whilst linking Program({program})");
224 }
225 }
226
227 // A wrapper function that enables the shader program.
228 public void Use()
229 {
230 GL.UseProgram(Handle);
231 }
232
233 // The shader sources provided with this project use hardcoded layout(location)-s. If you want to do it dynamically,
234 // you can omit the layout(location=X) lines in the vertex shader, and use this in VertexAttribPointer instead of the hardcoded values.
235 public int GetAttribLocation(string attribName)
236 {
237 return GL.GetAttribLocation(Handle, attribName);
238 }
239
240 // Uniform setters
241 // Uniforms are variables that can be set by user code, instead of reading them from the VBO.
242 // You use VBOs for vertex-related data, and uniforms for almost everything else.
243
244 // Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
245 // 1. Bind the program you want to set the uniform on
246 // 2. Get a handle to the location of the uniform with GL.GetUniformLocation.
247 // 3. Use the appropriate GL.Uniform* function to set the uniform.
248
249 /// <summary>
250 /// Set a uniform int on this shader.
251 /// </summary>
252 /// <param name="name">The name of the uniform</param>
253 /// <param name="data">The data to set</param>
254 public void SetInt(string name, int data)
255 {
256 GL.UseProgram(Handle);
257 GL.Uniform1(_uniformLocations[name], data);
258 }
259
260 /// <summary>
261 /// Set a uniform float on this shader.
262 /// </summary>
263 /// <param name="name">The name of the uniform</param>
264 /// <param name="data">The data to set</param>
265 public void SetFloat(string name, float data)
266 {
267 GL.UseProgram(Handle);
268 GL.Uniform1(_uniformLocations[name], data);
269 }
270
271 /// <summary>
272 /// Set a uniform Matrix4 on this shader
273 /// </summary>
274 /// <param name="name">The name of the uniform</param>
275 /// <param name="data">The data to set</param>
276 /// <remarks>
277 /// <para>
278 /// The matrix is transposed before being sent to the shader.
279 /// </para>
280 /// </remarks>
281 public void SetMatrix4(string name, Matrix4 data)
282 {
283 GL.UseProgram(Handle);
284 GL.UniformMatrix4(_uniformLocations[name], true, ref data);
285 }
286
287 /// <summary>
288 /// Set a uniform Vector3 on this shader.
289 /// </summary>
290 /// <param name="name">The name of the uniform</param>
291 /// <param name="data">The data to set</param>
292 public void SetVector3(string name, Vector3 data)
293 {
294 GL.UseProgram(Handle);
295 GL.Uniform3(_uniformLocations[name], data);
296 }
297 }
298}
299