├── Blinking-Pink ├── blinking-pink.exe └── blinking.odin ├── Hidden-Animal ├── main.odin ├── nodes.txt ├── sc-fragment-shader.glsl ├── sc-vertex-shader.glsl └── triangles.txt ├── Moving-Character ├── fragment.glsl ├── moving-character.exe ├── moving-character.odin └── vertex.glsl ├── Psychedelic-Earth ├── blue-marble.qoi ├── fragment.glsl ├── psychedelic-earth.exe ├── psychedelic-earth.odin └── vertex.glsl ├── README.md ├── Rainbow-Triangle ├── fragment.glsl ├── rainbow-triangle.exe ├── rainbow-triangle.odin └── vertex.glsl ├── Readme-Imgs ├── blinking-pink.png ├── hidden-animal.png ├── moving-character.png ├── psychedelic-earth.png ├── rainbow-triangle.jpg ├── rotating-cube.png └── wavy-words.png ├── Rotating-Cube ├── fragment.glsl ├── rotating-cube.exe ├── rotating-cube.odin └── vertex.glsl └── Wavy-Words ├── fragment.glsl ├── vertex.glsl ├── wavy-words.exe └── wavy-words.odin /Blinking-Pink/blinking-pink.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Blinking-Pink/blinking-pink.exe -------------------------------------------------------------------------------- /Blinking-Pink/blinking.odin: -------------------------------------------------------------------------------- 1 | // An elementary program in Odin which creates a window that oscillates 2 | // between pink and blue. 3 | // 4 | // Created by Benjamin Thompson. Available at: 5 | // https://github.com/bg-thompson/OpenGL-Tutorials-In-Odin 6 | // Last updated: 2024.09.15 7 | // 8 | // To compile and run the program with optimizations, use the command 9 | // 10 | // odin run Blinking-Pink -o:speed 11 | // 12 | // Created for educational purposes. Used verbatim, it is probably 13 | // unsuitable for production code. 14 | 15 | package main 16 | 17 | import "vendor:glfw" 18 | import gl "vendor:OpenGL" 19 | import "core:time" 20 | import "core:math" 21 | 22 | main :: proc() { 23 | // Initialize glfw, specify OpenGL version. 24 | glfw.Init() 25 | defer glfw.Terminate() 26 | glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) 27 | glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) 28 | glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 29 | 30 | // Create render window. 31 | window := glfw.CreateWindow(800, 600, "Blinking", nil, nil) 32 | assert(window != nil) 33 | defer glfw.DestroyWindow(window) 34 | glfw.MakeContextCurrent(window) 35 | 36 | // Enable Vsync. 37 | glfw.SwapInterval(1) 38 | 39 | // Load OpenGL function pointers. 40 | gl.load_up_to(3,3, glfw.gl_set_proc_address) 41 | 42 | // Set normalized device coords to window coords transformation. 43 | w, h := glfw.GetFramebufferSize(window) 44 | gl.Viewport(0,0,w,h) 45 | 46 | // Start blinking timer. 47 | watch : time.Stopwatch 48 | time.stopwatch_start(&watch) 49 | 50 | // Render loop 51 | for !glfw.WindowShouldClose(window) { 52 | glfw.PollEvents() 53 | // Note: glfw.PollEvents will block on Windows during window resize, hence 54 | // strange rendering occurs during resize. To keep this example simple, we 55 | // will not fix this here. A partial solution is found in Rainbow-Triangle 56 | // and subsequent examples. 57 | 58 | // Create oscillating value (osl). 59 | raw_duration := time.stopwatch_duration(watch) 60 | secs := f32(time.duration_seconds(raw_duration)) 61 | osl := (math.sin(3 * secs) + 1) * 0.5 62 | 63 | // Clear screen with color. 64 | gl.ClearColor(0.9 * osl, 0.2, 0.8, 1) // Pink: 0.9, 0.2, 0.8 65 | gl.Clear(gl.COLOR_BUFFER_BIT) 66 | 67 | // Render screen with background color. 68 | glfw.SwapBuffers(window) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Hidden-Animal/main.odin: -------------------------------------------------------------------------------- 1 | // A program which draws a hidden triangulated animal. 2 | // Moving the cursor over a triangle reveals it, in time 3 | // revealing the animal. 4 | 5 | // Inspired by an image in "Vector Elephant set." by Rashevskaya Art at 6 | // https://thehungryjpeg.com/product/3734798-vector-elephant-set-elephant-triangle-geometric-illustration 7 | // Note: the geometry underlying the image is simple geometry and therefore 8 | // public domain under US copywrite law. 9 | 10 | // Created by Benjamin Thompson. Available at: 11 | // https://github.com/bg-thompson/OpenGL-Tutorials-In-Odin 12 | // Last updated: 2024.09.15 13 | // 14 | // To compile and run the program with optimizations, use the command 15 | // 16 | // odin run Hidden-Animal -o:speed 17 | // 18 | // Created for educational purposes. Used verbatim, it is probably 19 | // unsuitable for production code. 20 | 21 | package animal 22 | 23 | import f "core:fmt" 24 | import s "core:strings" 25 | import v "core:strconv" 26 | import "core:time" 27 | import m "core:math" 28 | import mr "core:math/rand" 29 | import "core:os" 30 | import "vendor:glfw" 31 | import gl "vendor:OpenGL" 32 | import "base:runtime" 33 | 34 | cam_pos_wv : [2] f32 35 | mouse_wv : [2] f32 36 | 37 | Triangle :: struct { 38 | a,b,c : Node, 39 | diameter2 : f32, 40 | color : Solid_Color, 41 | hovering : bool, 42 | } 43 | 44 | triangles : [] Triangle 45 | 46 | Solid_Color :: distinct [3] f32 47 | 48 | // Global Colors 49 | color_black :: Solid_Color{0,0,0} 50 | color_white :: Solid_Color{1,1,1} 51 | color_cyan :: Solid_Color{0,1,1} 52 | color_grey :: Solid_Color{0.5,0.5,0.5} 53 | 54 | main :: proc() { 55 | // Initialize window and OpenGL. 56 | glfw.Init() 57 | defer glfw.Terminate() 58 | glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) 59 | glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) 60 | glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 61 | 62 | // Create window for the illustration. 63 | global_window = glfw.CreateWindow(INITIAL_WINDOW_SIZE_W, 64 | INITIAL_WINDOW_SIZE_H, 65 | "Hidden animal", 66 | nil, nil) 67 | assert(global_window != nil) 68 | defer glfw.DestroyWindow(global_window) 69 | glfw.MakeContextCurrent(global_window) 70 | glfw.SetWindowSizeLimits(global_window, MINIMUM_WINDOW_SIZE_W, MINIMUM_WINDOW_SIZE_H, glfw.DONT_CARE, glfw.DONT_CARE) 71 | 72 | // Initialize the window and GPU buffers, and 73 | // initialize textures used to render pieces. 74 | init_window(global_window) 75 | 76 | // Load data, create triangles. 77 | import_nodes() 78 | import_triangles() 79 | 80 | // Main loop. 81 | for !glfw.WindowShouldClose(global_window) { 82 | 83 | // Check for window resize and process keyboard presses. 84 | glfw.PollEvents() 85 | 86 | cam_zom = 1 // pixels-to-world-space-ratio 87 | 88 | process_keyboard_events(global_window) 89 | process_mouse_events(global_window) 90 | 91 | // Keyboard camera movement 92 | dx := cam_zom * CAMERA_SCROLL_SPEED 93 | 94 | if shift_camera.x { cam_pos_wv.x -= dx } 95 | if shift_camera.y { cam_pos_wv.y -= dx } 96 | if shift_camera.z { cam_pos_wv.x += dx } 97 | if shift_camera.w { cam_pos_wv.y += dx } 98 | 99 | // Reset keyboard camera movement 100 | shift_camera = false 101 | 102 | // Change color of triangle if cursor underneath it. 103 | for t,i in triangles { 104 | xdist := mouse_wv.x - t.a.coord.x 105 | ydist := mouse_wv.y - t.a.coord.y 106 | // Don't call inside_triangles for triangles that are far away. 107 | if xdist * xdist + ydist * ydist <= t.diameter2 { 108 | if inside_triangle(mouse_wv, t.a.coord, t.b.coord, t.c.coord) { 109 | if !t.hovering { 110 | triangles[i].hovering = true 111 | current_color := triangles[i].color 112 | triangles[i].color = update_color(current_color) 113 | } 114 | } else { 115 | triangles[i].hovering = false 116 | } 117 | } 118 | } 119 | 120 | // Render screen. 121 | render_screen(global_window) 122 | } 123 | } 124 | 125 | update_color :: proc(color : Solid_Color) -> Solid_Color { 126 | sufficently_different := false 127 | nr : f32 128 | for !sufficently_different { 129 | r := mr.int_max(128) 130 | nr = 0.5 * f32(r) / f32(128) + 0.3 131 | if m.abs(color.r - nr) > 0.2 { 132 | sufficently_different = true 133 | } 134 | } 135 | return Solid_Color{ nr, nr, nr} 136 | } 137 | 138 | process_keyboard_events :: proc(window : glfw.WindowHandle) { 139 | // GetKey returns either glfw.PRESS or glfw.RELEASE. 140 | // ESC 141 | if glfw.GetKey(window, glfw.KEY_ESCAPE) == glfw.PRESS { 142 | glfw.SetWindowShouldClose(window, true) 143 | } 144 | // Right 145 | if glfw.GetKey(window, glfw.KEY_RIGHT) == glfw.PRESS { 146 | shift_camera.x = true 147 | } 148 | // Up 149 | if glfw.GetKey(window, glfw.KEY_UP) == glfw.PRESS { 150 | shift_camera.y = true 151 | } 152 | // Left 153 | if glfw.GetKey(window, glfw.KEY_LEFT) == glfw.PRESS { 154 | shift_camera.z = true 155 | } 156 | // Down 157 | if glfw.GetKey(window, glfw.KEY_DOWN) == glfw.PRESS { 158 | shift_camera.w = true 159 | } 160 | } 161 | 162 | process_mouse_events :: proc( window : glfw.WindowHandle) { 163 | // Get mouse position, normalized to a point in [0,1] x [0,1]. 164 | // Convert the ridiculous standard of setting the origin to be the top left 165 | // instead of the bottom left. 166 | mousexraw, mouseyraw := glfw.GetCursorPos(window) 167 | mouse_px : [2] f32 = {f32(mousexraw), window_h_px - f32(mouseyraw)} 168 | mouse_wv = cam_zom * mouse_px + cam_pos_wv 169 | } 170 | 171 | // If the user blocks .PollEvents() (e.g. due to resizing the window, or right 172 | // clicking on the menu bar, rendering will freeze, so redraw the window. 173 | 174 | window_refresh :: proc "c" ( window : glfw.WindowHandle ) { 175 | context = runtime.default_context() 176 | w, h : i32 177 | w, h = glfw.GetWindowSize(window) 178 | gl.Viewport(0,0,w,h) 179 | render_screen(global_window) 180 | } 181 | 182 | // Import drawing data 183 | NODEDATA :: string(#load(`nodes.txt`)) 184 | TRIANGLES :: string(#load(`triangles.txt`)) 185 | 186 | Node :: struct{ 187 | coord : [2] f32, 188 | index : i32, 189 | } 190 | 191 | nodes : [] Node 192 | 193 | import_nodes :: proc() { 194 | lines := s.split_lines(NODEDATA) 195 | defer delete(lines) 196 | number_nodes := len(lines) / 2 197 | nodes = make([] Node, number_nodes) 198 | for i in 0..=number_nodes-1 { 199 | nodes[i].index = i32(i) 200 | float, ok := v.parse_f32(s.trim_right_space(lines[2*i])) 201 | assert(ok) 202 | nodes[i].coord.x = float 203 | float2, ok2 := v.parse_f32(s.trim_right_space(lines[2*i + 1])) 204 | assert(ok2) 205 | nodes[i].coord.y = -float2 206 | } 207 | } 208 | 209 | import_triangles :: proc() { 210 | lines := s.split_lines(TRIANGLES) 211 | defer delete(lines) 212 | 213 | triangles = make([] Triangle, len(lines)) 214 | for l, i in lines { 215 | if l == "" { continue } 216 | ints := s.fields(l) 217 | int1, ok1 := v.parse_int(ints[0]) 218 | int2, ok2 := v.parse_int(ints[1]) 219 | int3, ok3 := v.parse_int(ints[2]) 220 | assert(ok1 && ok2 && ok3) 221 | na := nodes[int1] 222 | nb := nodes[int2] 223 | nc := nodes[int3] 224 | triangles[i].a = na 225 | triangles[i].b = nb 226 | triangles[i].c = nc 227 | // Compute diameter of triangle, i.e. its longest side length. 228 | l1 := na.coord - nb.coord 229 | l2 := nb.coord - nc.coord 230 | l3 := nc.coord - na.coord 231 | ll :: proc ( vec : [2] f32 ) -> f32 { return vec.x * vec.x + vec.y * vec.y } 232 | diameter2 := max(ll(l1), ll(l2), ll(l3)) 233 | triangles[i].diameter2 = diameter2 234 | triangles[i].color = color_white 235 | triangles[i].hovering = false 236 | } 237 | } 238 | 239 | // Rendering Types 240 | VAO :: distinct u32 // Vertex array object 241 | VBO :: distinct u32 // Vertex buffer object 242 | Shader :: distinct u32 243 | 244 | // Rendering Globals 245 | solid_color_vao : VAO 246 | solid_color_vbo : VBO 247 | global_window : glfw.WindowHandle 248 | window_w_px : f32 249 | window_h_px : f32 250 | 251 | // Actions globals 252 | shift_camera : [4] bool // 0-4: R, U, L, D 253 | 254 | CAMERA_SCROLL_SPEED :: 25 // Feels about right. 255 | 256 | // Rendering Constants 257 | INITIAL_WINDOW_SIZE_W :: 1920 258 | INITIAL_WINDOW_SIZE_H :: 1080 259 | MINIMUM_WINDOW_SIZE_W :: 200 260 | MINIMUM_WINDOW_SIZE_H :: 200 261 | 262 | // Picture constants and globals. 263 | // The camera position is the lower-left coord. 264 | // Setting the camera to the center of the screen leads to code 265 | // that is more complicated. 266 | // 267 | // The zoom is the ratio of 1 pixel to 1 unit it world-space. 268 | 269 | cam_zom : f32 270 | sc_shader : Shader 271 | 272 | // Maximum number of vertices in vertex buffers. 273 | // Note: a large number of grid points doesn't lead to 274 | // an assertion error. 275 | VB_STAGE_SIZE_IN_FLOATS :: (1 << 10) * 6 // About 6000 floats. 276 | 277 | Vertex_Buffer_Stage :: struct { 278 | vb : [VB_STAGE_SIZE_IN_FLOATS] f32, 279 | curr_index : int, 280 | fpv : int, // floats per vertex 281 | } 282 | 283 | // Number of floats in a color vertex set to the GPU. 284 | FLOATS_IN_COLOR_VERTEX :: 5 285 | // | x | y | r | g | b | 286 | // | xy coords (f32 x 2) | rgb color (f32 x 3) | 287 | 288 | init_window :: proc( window : glfw.WindowHandle) { 289 | // Enable V-Sync 290 | glfw.SwapInterval(1) 291 | 292 | // Load OpenGL 3.3 function pointers. 293 | gl.load_up_to(3, 3, glfw.gl_set_proc_address) 294 | w,h := glfw.GetFramebufferSize(window) 295 | gl.Viewport(0,0,w,h) 296 | 297 | // Window_refresh is defined below. 298 | glfw.SetWindowRefreshCallback(window, window_refresh) 299 | 300 | // Setup a Vertex Array Object for solid colors. 301 | gl.GenVertexArrays(1, cast(^u32) &solid_color_vao) 302 | gl.BindVertexArray( u32(solid_color_vao) ) 303 | 304 | // Set up solid_color_vbo. 305 | gl.GenBuffers(1, cast(^u32) &solid_color_vbo) 306 | gl.BindBuffer(gl.ARRAY_BUFFER, u32(solid_color_vbo) ) 307 | 308 | // Create GPU buffer space. Remember to enable! 309 | gl.BufferData(gl.ARRAY_BUFFER, // target 310 | VB_STAGE_SIZE_IN_FLOATS * size_of(f32), // size 311 | nil, // fill with SubData later 312 | gl.DYNAMIC_DRAW) // usage 313 | 314 | // Position attributes for solid_vbo_color. 315 | gl.VertexAttribPointer(0, // index 316 | 2, // size 317 | gl.FLOAT, // type 318 | gl.FALSE, // normalized 319 | FLOATS_IN_COLOR_VERTEX * size_of(f32), // stride 320 | 0 * size_of(f32), // offset 321 | ) 322 | 323 | 324 | 325 | // Color attributes for solid_vbo_color. 326 | gl.VertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, FLOATS_IN_COLOR_VERTEX * size_of(f32), 2 * size_of(f32)) 327 | 328 | // Enable the attributes for vbo_color. 329 | gl.EnableVertexAttribArray(0) 330 | gl.EnableVertexAttribArray(1) 331 | 332 | // Compile vertex and fragment shaders. 333 | shader_comp1_ok : bool 334 | // Load shaders into executable at compile time. 335 | vs1 :: string(#load("sc-vertex-shader.glsl")) 336 | fs1 :: string(#load("sc-fragment-shader.glsl")) 337 | 338 | // sc_shader is a global variable. 339 | sc_shader_u32 : u32 340 | sc_shader_u32, shader_comp1_ok = gl.load_shaders_source(vs1, fs1) 341 | sc_shader = Shader(sc_shader_u32) 342 | 343 | if !shader_comp1_ok { 344 | f.println("ERROR: Loading and/or compilation of shaders failed!") 345 | os.exit(1) 346 | } 347 | 348 | // Set initial camera position. 349 | cam_pos_wv.x = -900 350 | cam_pos_wv.y = -600 351 | return 352 | } 353 | draw_triangle_solid_color :: proc( a,b,c : [2] f32, color : Solid_Color, vas : ^Vertex_Buffer_Stage) { 354 | assert(vas.fpv == 5) 355 | cvn := vas.curr_index 356 | // Triangle Coordinates 357 | vas.vb[(cvn + 0) * 5 + 0] = a.x 358 | vas.vb[(cvn + 0) * 5 + 1] = a.y 359 | vas.vb[(cvn + 1) * 5 + 0] = b.x 360 | vas.vb[(cvn + 1) * 5 + 1] = b.y 361 | vas.vb[(cvn + 2) * 5 + 0] = c.x 362 | vas.vb[(cvn + 2) * 5 + 1] = c.y 363 | // Triangle Colors 364 | for i in 0..=2 { 365 | vas.vb[(cvn + i) * 5 + 2] = color.r 366 | vas.vb[(cvn + i) * 5 + 3] = color.g 367 | vas.vb[(cvn + i) * 5 + 4] = color.b 368 | } 369 | vas.curr_index += 3 370 | } 371 | 372 | render_screen :: proc( window : glfw.WindowHandle) { 373 | // Get window width and height, and tell the GPU about them. 374 | window_w_px_int, window_h_px_int := glfw.GetFramebufferSize(window) 375 | window_w_px = f32(window_w_px_int) 376 | window_h_px = f32(window_h_px_int) 377 | 378 | // Make a buffer to hold the grid squares. 379 | poly_verts1 : Vertex_Buffer_Stage 380 | poly_verts1.curr_index = 0 381 | poly_verts1.fpv = FLOATS_IN_COLOR_VERTEX 382 | 383 | // Draw grid. 384 | // Select grid color based on mouse pressed. 385 | 386 | for trip in triangles { 387 | c1, c2, c3 := trip.a.coord, trip.b.coord, trip.c.coord 388 | draw_triangle_solid_color(c1, c2, c3, trip.color, &poly_verts1) 389 | } 390 | 391 | // Begin rendering. 392 | // Background color. 393 | bgc := color_white 394 | gl.ClearColor(bgc.r, bgc.g, bgc.b, 1) 395 | gl.Clear(gl.COLOR_BUFFER_BIT) 396 | 397 | // Draw solid_color triangles. 398 | gl.UseProgram(u32(sc_shader)) 399 | // Set uniforms in solid color shader. 400 | // Note: this can only be done when the shader is active! 401 | 402 | // Calculate width and hidth of window in world-view. 403 | // Send these and cam_posx, cam_posy to the shaders. 404 | window_w_wv := window_w_px * cam_zom 405 | window_h_wv := window_h_px * cam_zom 406 | cam_posx_wv := cam_pos_wv.x 407 | cam_posy_wv := cam_pos_wv.y 408 | 409 | set_uniform_f32 :: proc( float : f32, str : cstring, shader : Shader) { 410 | gl.Uniform1f(gl.GetUniformLocation(u32(shader), str), float) 411 | } 412 | 413 | set_uniform_f32(window_w_wv, "window_w_wv", sc_shader) 414 | set_uniform_f32(window_h_wv, "window_h_wv", sc_shader) 415 | set_uniform_f32(cam_posx_wv, "cam_posx_wv", sc_shader) 416 | set_uniform_f32(cam_posy_wv, "cam_posy_wv", sc_shader) 417 | 418 | gl.BindVertexArray( u32(solid_color_vao) ) 419 | gl.BindBuffer(gl.ARRAY_BUFFER, u32(solid_color_vbo)) 420 | 421 | gl.BufferSubData(gl.ARRAY_BUFFER, //target 422 | 0, //offset 423 | poly_verts1.curr_index * poly_verts1.fpv * size_of(f32), // size 424 | &poly_verts1.vb) // data pointer 425 | 426 | gl.DrawArrays(gl.TRIANGLES, 0, i32(poly_verts1.curr_index)) 427 | 428 | // Display render. 429 | glfw.SwapBuffers(window) 430 | 431 | free_all(context.temp_allocator) 432 | } 433 | 434 | // Determine if a point is inside a triangle. 435 | // Uses the cross-product formula to calculate the sign of sin(theta). 436 | inside_triangle :: proc( p : [2] $T, a, b, c : [2] T) -> bool { 437 | d1 := a - p 438 | d2 := b - p 439 | d3 := c - p 440 | s1 := int( (d1.x * d2.y - d1.y * d2.x) >= 0) 441 | s2 := int( (d2.x * d3.y - d2.y * d3.x) >= 0) 442 | s3 := int( (d3.x * d1.y - d3.y * d1.x) >= 0) 443 | sign_sum := s1 + s2 + s3 444 | return sign_sum == 0 || sign_sum == 3 445 | } 446 | -------------------------------------------------------------------------------- /Hidden-Animal/nodes.txt: -------------------------------------------------------------------------------- 1 | -322.22974 2 | 447.87057 3 | -335.09863 4 | 422.64084 5 | -305.46637 6 | 422.97946 7 | -307.66763 8 | 281.92987 9 | -363.88425 10 | 228.93044 11 | -329.98264 12 | 121.64817 13 | -309.50836 14 | 52.682278 15 | -339.56122 16 | 52.562546 17 | -351.41473 18 | 70.522415 19 | -399.42746 20 | 80.460205 21 | -385.53848 22 | 83.57325 23 | -399.18796 24 | 69.564552 25 | -464.80136 26 | 76.868233 27 | -392.96188 28 | 52.682278 29 | -371.64951 30 | -5.6274252 31 | -275.74384 32 | -50.527092 33 | -267.87573 34 | 42.501141 35 | -227.40649 36 | -10.498289 37 | -237.05815 38 | -59.603191 39 | -246.03249 40 | -113.27993 41 | -298.35461 42 | -167.8033 43 | -264.31985 44 | -197.60489 45 | -161.70752 46 | -177.45496 47 | -169.15793 48 | -156.45837 49 | -176.439 50 | -68.238884 51 | -183.38141 52 | 3.5558722 53 | -263.64252 54 | 106.16819 55 | -193.54105 56 | 100.91904 57 | -100.24173 58 | 84.324974 59 | -128.68871 60 | -120.89966 61 | -56.893955 62 | -169.49658 63 | -51.644814 64 | -206.91791 65 | 18.625998 66 | -197.94356 67 | 70.609467 68 | -140.88026 69 | 90.59008 70 | -44.533066 71 | 27.092358 72 | 52.15279 73 | -133.5992 74 | 222.32668 75 | -146.63739 76 | 421.45554 77 | -146.63739 78 | 467.8512 79 | -181.01083 80 | 503.07126 81 | -83.816994 82 | 504.59518 83 | -72.133408 84 | 497.48346 85 | -92.960663 86 | 474.28561 87 | -59.264538 88 | 309.86887 89 | 10.328962 90 | 172.71379 91 | -13.038198 92 | 216.73888 93 | 17.271379 94 | 310.88483 95 | 96.68586 96 | 372.35062 97 | 45.887688 98 | 417.561 99 | 83.986313 100 | 456.50626 101 | 120.22234 102 | 478.18015 103 | 123.60889 104 | 519.32666 105 | 161.70752 106 | 504.93387 107 | 198.62086 108 | 459.04617 109 | 120.22234 110 | 418.069 111 | 104.13626 112 | 240.27536 113 | 128.68871 114 | 241.29132 115 | 182.19612 116 | 273.4635 117 | 273.9715 118 | 163.57011 119 | 382.34094 120 | 273.12488 121 | 251.11232 122 | 273.29416 123 | 359.65106 124 | 273.9715 125 | 329.50369 126 | 392.96188 127 | 325.19333 128 | 460.49097 129 | 419.54248 130 | 450.194 131 | 422.17661 132 | 470.30905 133 | 398.70905 134 | 485.87427 135 | 359.91571 136 | 501.20001 137 | 306.51508 138 | 491.8609 139 | 434.49371 140 | 327.30957 141 | 446.51596 142 | 343.22632 143 | 514.92413 144 | 325.6163 145 | 511.19894 146 | 419.25427 147 | 446.85458 148 | 474.28561 149 | 408.24802 150 | 506.28848 151 | 464.12601 152 | 516.44812 153 | 521.18927 154 | 494.77423 155 | 434.49371 156 | 67.730896 157 | 532.70355 158 | 154.76511 159 | 541.84717 160 | 46.903648 161 | 543.03247 162 | 204.20866 163 | 560.64252 164 | 283.45383 165 | 542.01654 166 | 300.21719 167 | 555.05475 168 | 43.855759 169 | 552.17615 170 | 161.03023 171 | 504.25656 172 | -75.858604 173 | 408.41733 174 | -132.41391 175 | 257.88541 176 | -98.040474 177 | 263.30386 178 | -143.75882 179 | 149.00798 180 | 114.12656 181 | -------------------------------------------------------------------------------- /Hidden-Animal/sc-fragment-shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec3 ourColor; 4 | out vec4 FragColor; 5 | 6 | void main() { 7 | FragColor = vec4(ourColor, 1); 8 | } 9 | -------------------------------------------------------------------------------- /Hidden-Animal/sc-vertex-shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec2 aPos; 4 | layout (location = 1) in vec3 aColor; 5 | 6 | out vec3 ourColor; 7 | 8 | uniform float cam_posx_wv; 9 | uniform float cam_posy_wv; 10 | uniform float window_w_wv; 11 | uniform float window_h_wv; 12 | 13 | void main() { 14 | // Convert (x,y) coordinates in pixels to [0,1] x [0,1] coordinates. 15 | float sposx = (aPos.x - cam_posx_wv) / window_w_wv; 16 | float sposy = (aPos.y - cam_posy_wv) / window_h_wv; 17 | // Convert [0,1] x [0,1] into [-1,1] x [-1,1] 18 | gl_Position = vec4(2 * sposx - 1, 2 * sposy - 1, 0, 1); 19 | 20 | // Pass the color on, unmodified. 21 | ourColor = aColor; 22 | } 23 | -------------------------------------------------------------------------------- /Hidden-Animal/triangles.txt: -------------------------------------------------------------------------------- 1 | 0 1 2 2 | 1 2 3 3 | 1 3 4 4 | 3 4 5 5 | 4 5 10 6 | 10 11 9 7 | 9 12 11 8 | 5 10 8 9 | 10 8 11 10 | 11 8 13 11 | 8 13 14 12 | 5 8 7 13 | 8 7 14 14 | 5 7 6 15 | 7 14 15 16 | 14 15 19 17 | 14 19 20 18 | 19 20 23 19 | 20 21 23 20 | 21 23 22 21 | 19 23 24 22 | 15 18 19 23 | 18 19 24 24 | 15 18 17 25 | 18 24 17 26 | 7 6 15 27 | 6 15 17 28 | 6 17 16 29 | 16 26 25 30 | 26 25 27 31 | 16 17 25 32 | 17 25 24 33 | 27 25 28 34 | 28 25 29 35 | 25 24 29 36 | 24 29 23 37 | 23 29 30 38 | 22 23 30 39 | 30 22 31 40 | 31 30 32 41 | 30 32 33 42 | 29 30 33 43 | 29 33 34 44 | 29 34 35 45 | 29 35 28 46 | 35 28 36 47 | 35 36 44 48 | 44 36 45 49 | 45 36 43 50 | 36 43 37 51 | 43 37 42 52 | 37 42 38 53 | 42 41 40 54 | 38 42 40 55 | 38 40 39 56 | 44 45 55 57 | 45 55 46 58 | 55 46 47 59 | 46 47 48 60 | 47 48 49 61 | 47 54 49 62 | 54 53 52 63 | 54 49 50 64 | 54 50 52 65 | 50 52 51 66 | 33 88 87 67 | 33 87 34 68 | 34 87 89 69 | 34 35 89 70 | 35 44 89 71 | 44 89 58 72 | 44 55 58 73 | 55 56 58 74 | 56 57 58 75 | 57 60 58 76 | 60 61 58 77 | 60 61 62 78 | 61 62 64 79 | 62 64 63 80 | 64 63 68 81 | 68 66 64 82 | 64 65 66 83 | 66 67 68 84 | 61 59 58 85 | 89 58 77 86 | 89 77 87 87 | 88 87 86 88 | 86 87 77 89 | 86 85 77 90 | 85 79 77 91 | 85 83 79 92 | 79 83 84 93 | 79 78 84 94 | 79 77 78 95 | 59 58 77 96 | 80 82 81 97 | 81 80 84 98 | 80 84 78 99 | 59 69 77 100 | 69 71 77 101 | 71 78 77 102 | 69 70 71 103 | 71 70 72 104 | 70 72 73 105 | 72 76 75 106 | 72 73 75 107 | 75 73 74 108 | -------------------------------------------------------------------------------- /Moving-Character/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec2 TexCoords; 4 | out vec4 FragColor; 5 | 6 | uniform sampler2D glyph_texture; 7 | 8 | void main() { 9 | // Cyan color is: 0.5, 1, 1 10 | FragColor = vec4(0.5, 1, 1, texture(glyph_texture, TexCoords).r); 11 | } 12 | -------------------------------------------------------------------------------- /Moving-Character/moving-character.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Moving-Character/moving-character.exe -------------------------------------------------------------------------------- /Moving-Character/moving-character.odin: -------------------------------------------------------------------------------- 1 | // An elementary program in Odin which renders a character from a 2 | // font and moves it in a circle. 3 | // 4 | // Created by Benjamin Thompson. Available at: 5 | // https://github.com/bg-thompson/OpenGL-Tutorials-In-Odin 6 | // Last updated: 2024.09.15 7 | // 8 | // To compile and run the program with optimizations, use the command 9 | // 10 | // odin run Moving-Character -o:speed 11 | // 12 | // Created for educational purposes. Used verbatim, it is probably 13 | // unsuitable for production code. 14 | 15 | package main 16 | 17 | import "vendor:glfw" 18 | import gl "vendor:OpenGL" 19 | import tt "vendor:stb/truetype" 20 | import "core:time" 21 | import m "core:math" 22 | import f "core:fmt" 23 | import "core:os" 24 | import "base:runtime" 25 | 26 | // Create alias types for vertex array / buffer objects 27 | VAO :: u32 28 | VBO :: u32 29 | ShaderProgram :: u32 30 | Texture :: u32 31 | 32 | // Global variables. 33 | global_vao : VAO 34 | global_shader : ShaderProgram 35 | watch : time.Stopwatch 36 | 37 | // Constants 38 | WINDOW_H :: 800 39 | WINDOW_W :: 800 40 | FONT :: `C:\Windows\Fonts\arialbd.ttf` 41 | CHARACTER:: '@' 42 | FONTSCALE:: 100 43 | 44 | // Functions to update constants in shader programs. 45 | update_uni_2fv :: proc( program : u32, var_name : cstring, new_value_ptr : [^] f32) { 46 | gl.UniformMatrix2fv(gl.GetUniformLocation(program, var_name), 1, gl.TRUE, new_value_ptr) 47 | } 48 | 49 | update_uni_4fv :: proc( program : u32, var_name : cstring, new_value_ptr : [^] f32) { 50 | gl.UniformMatrix4fv(gl.GetUniformLocation(program, var_name), 1, gl.TRUE, new_value_ptr) 51 | } 52 | 53 | main :: proc() { 54 | //------------------------------------------------------------- 55 | // Use glfw to setup a window to render with OpenGl. 56 | //------------------------------------------------------------- 57 | glfw.Init() 58 | defer glfw.Terminate() 59 | 60 | glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) 61 | glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) 62 | glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 63 | 64 | window := glfw.CreateWindow(WINDOW_W, WINDOW_H, "Moving Character", nil, nil) 65 | assert(window != nil) 66 | defer glfw.DestroyWindow(window) 67 | 68 | glfw.MakeContextCurrent(window) 69 | glfw.SwapInterval(1) 70 | 71 | // Load OpenGL 3.3 function pointers. 72 | gl.load_up_to(3,3, glfw.gl_set_proc_address) 73 | 74 | ww, hh := glfw.GetFramebufferSize(window) 75 | gl.Viewport(0,0,ww,hh) 76 | 77 | // Key press / Window-resize behaviour 78 | glfw.SetKeyCallback(window, callback_key) 79 | glfw.SetWindowRefreshCallback(window, window_refresh) 80 | 81 | //------------------------------------------------------------- 82 | //Set up a rectangle to have the character texture drawn on it. 83 | //------------------------------------------------------------- 84 | 85 | h, w : f32 86 | h = 300 87 | w = 300 88 | 89 | rect_verts : [6 * 4] f32 90 | rect_verts = { // rect coords : vec2, texture coords : vec2 91 | 0, h, 0, 0, 92 | 0, 0, 0, 1, 93 | w, 0, 1, 1, 94 | 0, h, 0, 0, 95 | w, 0, 1, 1, 96 | w, h, 1, 0, 97 | } 98 | 99 | //------------------------------------------------------------- 100 | //Use stb to load a .ttf font and create a bitmap from it. 101 | //------------------------------------------------------------- 102 | 103 | // Load .ttf file into buffer. 104 | ttf_buffer :: [1<<23] u8 // Assumes a .ttf file of under 8MB. 105 | fontdata, succ := os.read_entire_file(FONT) 106 | if !succ { 107 | f.println("ERROR: Couldn't load font at: ", FONT) 108 | os.exit(1) 109 | } 110 | font_ptr : [^] u8 = &fontdata[0] 111 | 112 | // Initialize font. 113 | font : tt.fontinfo 114 | tt.InitFont(info = &font, data = font_ptr, offset = 0) 115 | 116 | // Find glyph of character to render. 117 | char_index := tt.FindGlyphIndex(&font, CHARACTER) 118 | 119 | // Create Bitmap of glyph, and loading width and height. 120 | bitmap_w, bitmap_h, xo, yo : i32 121 | glyph_bitmap := tt.GetGlyphBitmap( 122 | info = &font, 123 | scale_x = 0, 124 | scale_y = tt.ScaleForPixelHeight(&font, FONTSCALE), 125 | glyph = char_index, 126 | width = &bitmap_w, 127 | height = &bitmap_h, 128 | xoff = &xo, 129 | yoff = &yo, 130 | ) 131 | // Memory Leak: the above should be freed with tt.FreeBitmap 132 | 133 | //f.println("bitmap width, height of", CHARACTER, ":", bitmap_w, bitmap_h) // Debug 134 | 135 | //------------------------------------------------------------- 136 | //Tell the GPU about the data, especially the font texture. 137 | //------------------------------------------------------------- 138 | 139 | gl.GenVertexArrays(1, &global_vao) 140 | gl.BindVertexArray(global_vao) 141 | 142 | vbo : VBO 143 | gl.GenBuffers(1, &vbo) 144 | gl.BindBuffer(gl.ARRAY_BUFFER, vbo) 145 | 146 | Glyph_texture : Texture 147 | gl.GenTextures(1, &Glyph_texture) 148 | gl.BindTexture(gl.TEXTURE_2D, Glyph_texture) 149 | 150 | // Describe GPU buffer. 151 | gl.BufferData(gl.ARRAY_BUFFER, size_of(rect_verts), &rect_verts, gl.STATIC_DRAW) 152 | 153 | // Position and color attributes. Don't forget to enable! 154 | gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 4 * size_of(f32), 0 * size_of(f32)) 155 | gl.VertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, 4 * size_of(f32), 2 * size_of(f32)) 156 | 157 | gl.EnableVertexAttribArray(0) 158 | gl.EnableVertexAttribArray(1) 159 | 160 | gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1) 161 | 162 | gl.TexImage2D( 163 | gl.TEXTURE_2D, // texture type 164 | 0, // level of detail number (default = 0) 165 | gl.RED, // texture format 166 | bitmap_w, // width 167 | bitmap_h, // height 168 | 0, // border, must be 0 169 | gl.RED, // pixel data format 170 | gl.UNSIGNED_BYTE, // data type of pixel data 171 | glyph_bitmap, // image data 172 | ) 173 | 174 | // Texture wrapping options. 175 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 176 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 177 | 178 | // Texture filtering options. 179 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 180 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 181 | 182 | //------------------------------------------------------------- 183 | //Compile the vertex and fragment shader. 184 | //------------------------------------------------------------- 185 | 186 | program_ok : bool 187 | vertex_shader := string(#load("vertex.glsl" )) 188 | fragment_shader := string(#load("fragment.glsl")) 189 | 190 | global_shader, program_ok = gl.load_shaders_source(vertex_shader, fragment_shader); 191 | 192 | if !program_ok { 193 | f.println("ERROR: Failed to load and compile shaders."); os.exit(1) 194 | } 195 | 196 | gl.UseProgram(global_shader) 197 | 198 | //------------------------------------------------------------- 199 | //Render the character! 200 | //------------------------------------------------------------- 201 | 202 | // Start rotation timer. 203 | time.stopwatch_start(&watch) 204 | 205 | // Texture blending options. 206 | gl.Enable(gl.BLEND) 207 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 208 | 209 | // Main loop. 210 | for !glfw.WindowShouldClose(window) { 211 | glfw.PollEvents() 212 | // If a key press happens, .PollEvents calls callback_key, defined below. 213 | // Note: glfw.PollEvents blocks on window menu interaction selection or 214 | // window resize. During window_resize, glfw.SetWindowRefreshCallback 215 | // calls window_refresh to redraw the window. 216 | 217 | render_screen(window, global_vao) 218 | } 219 | } 220 | 221 | render_screen :: proc( window : glfw.WindowHandle, vao : VAO) { 222 | // Calculate projection matrix. 223 | render_rect_w , render_rect_h : f32 224 | render_rect_w = WINDOW_W 225 | render_rect_h = WINDOW_H 226 | 227 | proj_mat := [4] f32 { 228 | 1/render_rect_w, 0, 229 | 0, 1/render_rect_h, 230 | } 231 | 232 | proj_mat_ptr : [^] f32 = &proj_mat[0] 233 | 234 | // Calculate translation matrix. 235 | raw_duration := time.stopwatch_duration(watch) 236 | secs := f32(time.duration_seconds(raw_duration)) 237 | 238 | theta := f32(m.PI * secs ) 239 | 240 | radius := f32(0.5) 241 | translation_mat := [16] f32 { 242 | 1, 0, 0, radius * m.cos(theta), 243 | 0, 1, 0, radius * m.sin(theta), 244 | 0, 0, 1, 0, 245 | 0, 0, 0, 1, 246 | } 247 | 248 | trans_mat_ptr : [^] f32 = &translation_mat[0] 249 | 250 | gl.BindVertexArray(vao) 251 | defer gl.BindVertexArray(0) 252 | 253 | // Send matrices to the shader. 254 | update_uni_2fv(global_shader, "projection", proj_mat_ptr) 255 | update_uni_4fv(global_shader, "translation", trans_mat_ptr) 256 | 257 | // Draw commands. 258 | gl.ClearColor(0.1, 0.1, 0.1, 1) 259 | gl.Clear(gl.COLOR_BUFFER_BIT) 260 | 261 | gl.DrawArrays(gl.TRIANGLES, 0, 6) 262 | glfw.SwapBuffers(window) 263 | } 264 | 265 | // Quit the window if the ESC key is pressed. This procedure is called by 266 | // glfw.SetKeyCallback. 267 | callback_key :: proc "c" ( window : glfw.WindowHandle, key, scancode, action, mods : i32 ) { 268 | if action == glfw.PRESS && key == glfw.KEY_ESCAPE { 269 | glfw.SetWindowShouldClose(window, true) 270 | } 271 | } 272 | 273 | // If the window needs to be redrawn (e.g. the user resizes the window), redraw the window. 274 | // This procedure is called by glfw.SetWindowRefreshCallback. 275 | window_refresh :: proc "c" ( window : glfw.WindowHandle ) { 276 | context = runtime.default_context() 277 | w, h : i32 278 | w, h = glfw.GetWindowSize(window) 279 | gl.Viewport(0,0,w,h) 280 | render_screen(window, global_vao) 281 | } 282 | -------------------------------------------------------------------------------- /Moving-Character/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec2 xyPos; 4 | layout (location = 1) in vec2 aTexCoords; 5 | out vec2 TexCoords; 6 | 7 | uniform mat2 projection; 8 | uniform mat4 translation; 9 | 10 | void main() { 11 | gl_Position = translation * vec4(projection * xyPos, 0, 1); 12 | TexCoords = aTexCoords; 13 | } 14 | -------------------------------------------------------------------------------- /Psychedelic-Earth/blue-marble.qoi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Psychedelic-Earth/blue-marble.qoi -------------------------------------------------------------------------------- /Psychedelic-Earth/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec2 TexCoords; 4 | out vec4 FragColor; 5 | 6 | uniform sampler2D earth_texture; 7 | 8 | uniform mat3 psych_mat; 9 | 10 | vec4 NormalColor; 11 | 12 | void main() { 13 | NormalColor = texture(earth_texture, TexCoords); 14 | FragColor = vec4( psych_mat * NormalColor.rgb, NormalColor.a); 15 | } 16 | -------------------------------------------------------------------------------- /Psychedelic-Earth/psychedelic-earth.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Psychedelic-Earth/psychedelic-earth.exe -------------------------------------------------------------------------------- /Psychedelic-Earth/psychedelic-earth.odin: -------------------------------------------------------------------------------- 1 | // An elementary program in Odin which renders "The Blue Marble" taken 2 | // by the Apollo 17 Crew, 1972, animated with mild psychedelic colors. 3 | // 4 | // The photo is in the public domain, and available at: 5 | // https://commons.wikimedia.org/wiki/File:The_Blue_Marble.jpg 6 | // 7 | // The .qoi (Quite OK Image Format) version of the image in this directory 8 | // was converted from the .jpg version above with IrfanView. 9 | // 10 | // Created by Benjamin Thompson. Available at: 11 | // https://github.com/bg-thompson/OpenGL-Tutorials-In-Odin 12 | // Last updated: 2024.09.15 13 | // 14 | // To compile and run the program with optimizations, use the command 15 | // 16 | // odin run Psychedelic-Earth -o:speed 17 | // 18 | // Created for educational purposes. Used verbatim, it is probably 19 | // unsuitable for production code. 20 | 21 | package main 22 | 23 | import "vendor:glfw" 24 | import gl "vendor:OpenGL" 25 | import "core:image" 26 | import q "core:image/qoi" 27 | import "core:time" 28 | import m "core:math" 29 | import f "core:fmt" 30 | import "core:os" 31 | import "base:runtime" 32 | 33 | // Create alias types for vertex array / buffer objects 34 | VAO :: u32 35 | VBO :: u32 36 | ShaderProgram :: u32 37 | Texture :: u32 38 | 39 | // Global variables. 40 | global_vao : VAO 41 | global_shader : ShaderProgram 42 | watch : time.Stopwatch 43 | 44 | // Constants 45 | WINDOW_H :: 800 46 | WINDOW_W :: 800 47 | IMAGELOC :: `blue-marble.qoi` 48 | 49 | // Functions to update constants in shader programs. 50 | update_uni_2fv :: proc( program : u32, var_name : cstring, new_value_ptr : [^] f32) { 51 | gl.UniformMatrix2fv(gl.GetUniformLocation(program, var_name), 1, gl.TRUE, new_value_ptr) 52 | } 53 | 54 | update_uni_3fv :: proc( program : u32, var_name : cstring, new_value_ptr : [^] f32) { 55 | gl.UniformMatrix3fv(gl.GetUniformLocation(program, var_name), 1, gl.TRUE, new_value_ptr) 56 | } 57 | 58 | main :: proc() { 59 | //------------------------------------------------------------- 60 | // Use glfw to setup a window to render with OpenGl. 61 | //------------------------------------------------------------- 62 | glfw.Init() 63 | defer glfw.Terminate() 64 | 65 | glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) 66 | glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) 67 | glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 68 | 69 | window := glfw.CreateWindow(WINDOW_W, WINDOW_H, "Psychedelic Earth", nil, nil) 70 | assert(window != nil) 71 | defer glfw.DestroyWindow(window) 72 | 73 | glfw.MakeContextCurrent(window) 74 | glfw.SwapInterval(1) 75 | 76 | // Load OpenGL 3.3 function pointers. 77 | gl.load_up_to(3,3, glfw.gl_set_proc_address) 78 | 79 | ww, hh := glfw.GetFramebufferSize(window) 80 | gl.Viewport(0,0,ww,hh) 81 | 82 | // Key press / Window-resize behaviour 83 | glfw.SetKeyCallback(window, callback_key) 84 | glfw.SetWindowRefreshCallback(window, window_refresh) 85 | 86 | //------------------------------------------------------------- 87 | //Set up a rectangle to have the image texture drawn on it. 88 | //------------------------------------------------------------- 89 | 90 | w, h : f32 91 | w = WINDOW_W 92 | h = WINDOW_H 93 | 94 | rect_verts : [6 * 4] f32 95 | rect_verts = { // rect coords : vec2, texture coords : vec2 96 | -w, h, 0, 0, 97 | -w, -h, 0, 1, 98 | w, -h, 1, 1, 99 | -w, h, 0, 0, 100 | w, h, 1, 0, 101 | w, -h, 1, 1, 102 | } 103 | 104 | // Load image at compile time 105 | image_file_bytes := #load(IMAGELOC) 106 | 107 | // Load image Odin's core:image library. 108 | image_ptr : ^image.Image 109 | err : image.Error 110 | options := image.Options{.alpha_add_if_missing} 111 | 112 | // image_ptr, err = q.load_from_file(IMAGELOC, options) 113 | image_ptr, err = q.load_from_bytes(image_file_bytes, options) 114 | defer q.destroy(image_ptr) 115 | image_w := i32(image_ptr.width) 116 | image_h := i32(image_ptr.height) 117 | 118 | if err != nil { 119 | f.println("ERROR: Image:", IMAGELOC, "failed to load.") 120 | } 121 | 122 | // Copy bytes from icon buffer into slice. 123 | earth_pixels_u8 := make([]u8, len(image_ptr.pixels.buf)) 124 | for b, i in image_ptr.pixels.buf { 125 | earth_pixels_u8[i] = b 126 | } 127 | 128 | //------------------------------------------------------------- 129 | //Tell the GPU about the image (texture). 130 | //------------------------------------------------------------- 131 | 132 | gl.GenVertexArrays(1, &global_vao) 133 | gl.BindVertexArray(global_vao) 134 | 135 | vbo : VBO 136 | gl.GenBuffers(1, &vbo) 137 | gl.BindBuffer(gl.ARRAY_BUFFER, vbo) 138 | 139 | Earth_texture : Texture 140 | gl.GenTextures(1, &Earth_texture) 141 | gl.BindTexture(gl.TEXTURE_2D, Earth_texture) 142 | 143 | // Describe GPU buffer. 144 | gl.BufferData(gl.ARRAY_BUFFER, size_of(rect_verts), &rect_verts, gl.STATIC_DRAW) 145 | 146 | // Position and color attributes. Don't forget to enable! 147 | gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 4 * size_of(f32), 0 * size_of(f32)) 148 | gl.VertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, 4 * size_of(f32), 2 * size_of(f32)) 149 | 150 | gl.EnableVertexAttribArray(0) 151 | gl.EnableVertexAttribArray(1) 152 | 153 | // Describe texture. 154 | gl.TexImage2D( 155 | gl.TEXTURE_2D, // texture type 156 | 0, // level of detail number (default = 0) 157 | gl.RGBA, // texture format 158 | image_w, // width 159 | image_h, // height 160 | 0, // border, must be 0 161 | gl.RGBA, // pixel data format 162 | gl.UNSIGNED_BYTE, // data type of pixel data 163 | &earth_pixels_u8[0], // image data 164 | ) 165 | 166 | // Texture wrapping options. 167 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 168 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 169 | 170 | // Texture filtering options. 171 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 172 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 173 | 174 | //------------------------------------------------------------- 175 | //Compile the vertex and fragment shader. 176 | //------------------------------------------------------------- 177 | 178 | program_ok : bool 179 | vertex_shader := string(#load("vertex.glsl" )) 180 | fragment_shader := string(#load("fragment.glsl")) 181 | 182 | global_shader, program_ok = gl.load_shaders_source(vertex_shader, fragment_shader); 183 | 184 | if !program_ok { 185 | f.println("ERROR: Failed to load and compile shaders."); os.exit(1) 186 | } 187 | 188 | gl.UseProgram(global_shader) 189 | 190 | //------------------------------------------------------------- 191 | //Render the image! 192 | //------------------------------------------------------------- 193 | 194 | // Start rotation timer. 195 | time.stopwatch_start(&watch) 196 | 197 | // Texture blending options. 198 | gl.Enable(gl.BLEND) 199 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 200 | 201 | // Main loop. 202 | for !glfw.WindowShouldClose(window) { 203 | glfw.PollEvents() 204 | // If a key press happens, .PollEvents calls callback_key, defined below. 205 | // Note: glfw.PollEvents blocks on window menu interaction selection or 206 | // window resize. During window_resize, glfw.SetWindowRefreshCallback 207 | // calls window_refresh to redraw the window. 208 | 209 | render_screen(window, global_vao) 210 | } 211 | } 212 | 213 | render_screen :: proc( window : glfw.WindowHandle, vao : VAO) { 214 | // Calculate projection matrix. 215 | render_rect_w , render_rect_h : f32 216 | render_rect_w = WINDOW_W 217 | render_rect_h = WINDOW_H 218 | 219 | proj_mat := [4] f32 { 220 | 1/render_rect_w, 0, 221 | 0, 1/render_rect_h, 222 | } 223 | 224 | // Calculate translation matrix. 225 | raw_duration := time.stopwatch_duration(watch) 226 | secs := f32(time.duration_seconds(raw_duration)) 227 | 228 | // Small function which oscillates between 0 and 1 with wavelength 229 | // 2*pi/n secs. 230 | osc :: proc( t : f32, n : f32) -> f32 { 231 | return (1 + m.sin(n * t)) / 2 232 | } 233 | 234 | // Calculate fragment shader values to get psychedelic effect. 235 | t1 := osc(secs, 2) 236 | t2 := osc(secs, 2.5) 237 | t3 := osc(secs, 3) 238 | 239 | psych_mat := [9] f32 { 240 | 1 - t1, 0, t1, 241 | 0, 1 - t2, t2, 242 | 0, t3, 1 - t3, 243 | } 244 | 245 | proj_mat_ptr : [^] f32 = &proj_mat[0] 246 | psych_mat_ptr : [^] f32 = &psych_mat[0] 247 | 248 | gl.BindVertexArray(vao) 249 | defer gl.BindVertexArray(0) 250 | 251 | // Send matrices to the shader. 252 | update_uni_2fv(global_shader, "projection", proj_mat_ptr) 253 | update_uni_3fv(global_shader, "psych_mat", psych_mat_ptr) 254 | 255 | // Draw commands. 256 | gl.ClearColor(0.1, 0.1, 0.1, 1) 257 | gl.Clear(gl.COLOR_BUFFER_BIT) 258 | 259 | gl.DrawArrays(gl.TRIANGLES, 0, 6) 260 | glfw.SwapBuffers(window) 261 | } 262 | 263 | // Quit the window if the ESC key is pressed. This procedure is called by 264 | // glfw.SetKeyCallback. 265 | callback_key :: proc "c" ( window : glfw.WindowHandle, key, scancode, action, mods : i32 ) { 266 | if action == glfw.PRESS && key == glfw.KEY_ESCAPE { 267 | glfw.SetWindowShouldClose(window, true) 268 | } 269 | } 270 | 271 | // If the window needs to be redrawn (e.g. the user resizes the window), redraw the window. 272 | // This procedure is called by glfw.SetWindowRefreshCallback. 273 | window_refresh :: proc "c" ( window : glfw.WindowHandle ) { 274 | context = runtime.default_context() 275 | w, h : i32 276 | w, h = glfw.GetWindowSize(window) 277 | gl.Viewport(0,0,w,h) 278 | render_screen(window, global_vao) 279 | } 280 | -------------------------------------------------------------------------------- /Psychedelic-Earth/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec2 xyPos; 4 | layout (location = 1) in vec2 aTexCoords; 5 | out vec2 TexCoords; 6 | 7 | uniform mat2 projection; 8 | 9 | void main() { 10 | gl_Position = vec4(projection * xyPos, 0, 1); 11 | TexCoords = aTexCoords; 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A collection of introductory OpenGL programs in Odin. 2 | 3 | The programs below illustrate OpenGL functions in Odin using the wrappers in `"vendor:OpenGL"`. The programs start very basic and get progressively more complicated. 4 | 5 | The glfw library wrappers for Odin in `"vendor:glfw"` are also illustrated. 6 | 7 | ## Building 8 | 9 | Building the examples is trivial; the standard Odin distribution is the ONLY dependency. 10 | 11 | Simply call `odin run DIRECTORY -o:speed` where DIRECTORY is the name of one of the project's directories to build and run an example. (E.g. `odin run Rainbow-Triangle -o:speed`.) 12 | 13 | ## [Blinking Pink](./Blinking-Pink) 14 | 15 | A window which oscillates between pink and blue. 16 | 17 | An OS window filled with the color pink. 18 | 19 | ## [Rainbow Triangle](./Rainbow-Triangle) 20 | 21 | An RGB triangle which rotates over time. 22 | 23 | A OS window showing a slightly slanted RGB triangle. 24 | 25 | ## [Rotating Cube](./Rotating-Cube) 26 | 27 | A cube with different faces which rotates over time. 28 | 29 | An OS window showing a cube in the middle of rotation. 30 | 31 | ## [Psychedelic Earth](./Psychedelic-Earth) 32 | 33 | An image of the Earth changes color over time. 34 | 35 | An OS window showing a picture of the with purple seas and turqoise land. 36 | 37 | ## [Moving Character](./Moving-Character) 38 | 39 | A character moves in a counter-clockwise circle over time. 40 | 41 | An OS window showing the character '@'. 42 | 43 | ## [Wavy Words](./Wavy-Words) 44 | 45 | The characters of a sentence move in the form of a sine wave over time. 46 | 47 | The sentence: Duty calls, 3 o'clock tea! in the shape of a sine wave. 48 | 49 | ## [Hidden Animal](./Hidden-Animal) 50 | 51 | Triangles which together form an animal are revealed when the mouse cursor passes over them. 52 | 53 | A collection of partially uncovered grey triangles which together form a popular animal. -------------------------------------------------------------------------------- /Rainbow-Triangle/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec3 ourColor; 4 | out vec4 FragColor; 5 | 6 | void main() { 7 | FragColor = vec4(ourColor, 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Rainbow-Triangle/rainbow-triangle.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Rainbow-Triangle/rainbow-triangle.exe -------------------------------------------------------------------------------- /Rainbow-Triangle/rainbow-triangle.odin: -------------------------------------------------------------------------------- 1 | // An elementary program in Odin which creates a rainbow RGB triangle 2 | // which rotates over time. 3 | // 4 | // Created by Benjamin Thompson. Available at: 5 | // https://github.com/bg-thompson/OpenGL-Tutorials-In-Odin 6 | // Last updated: 2024.09.15 7 | // 8 | // To compile and run the program with optimizations, use the command 9 | // 10 | // odin run Rainbow-Triangle -o:speed 11 | // 12 | // Created for educational purposes. Used verbatim, it is probably 13 | // unsuitable for production code. 14 | 15 | package main 16 | 17 | import "vendor:glfw" 18 | import gl "vendor:OpenGL" 19 | import "core:time" 20 | import "core:math" 21 | import "core:fmt" 22 | import "core:os" 23 | import "base:runtime" 24 | 25 | // Create alias types for vertex array / buffer objects 26 | VAO :: u32 27 | VBO :: u32 28 | ShaderProgram :: u32 29 | 30 | // Global variables. 31 | global_vao : VAO 32 | global_shader : ShaderProgram 33 | watch : time.Stopwatch 34 | 35 | main :: proc() { 36 | // Setup window, including priming for OpenGL 3.3. 37 | glfw.Init() 38 | defer glfw.Terminate() 39 | 40 | glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) 41 | glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) 42 | glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 43 | 44 | window := glfw.CreateWindow(800, 800, "Rainbow Triangle", nil, nil) 45 | assert(window != nil) 46 | defer glfw.DestroyWindow(window) 47 | 48 | glfw.MakeContextCurrent(window) 49 | glfw.SwapInterval(1) 50 | 51 | // Load OpenGL 3.3 function pointers. 52 | gl.load_up_to(3,3, glfw.gl_set_proc_address) 53 | 54 | w, h := glfw.GetFramebufferSize(window) 55 | gl.Viewport(0,0,w,h) 56 | 57 | // Key press / Window-resize behaviour 58 | glfw.SetKeyCallback(window, callback_key) 59 | glfw.SetWindowRefreshCallback(window, window_refresh) 60 | 61 | // Create equilateral triangle on unit circle. 62 | 63 | sq := math.sqrt(f32(3)) * 0.5 64 | 65 | vertices : [15] f32 = { 66 | // Coordinates ; Colors 67 | 1.0, 0, 1, 0, 0, 68 | -0.5, sq, 0, 1, 0, 69 | -0.5, -sq, 0, 0, 1, 70 | } 71 | 72 | // Set up vertex array / buffer objects. 73 | gl.GenVertexArrays(1, &global_vao) 74 | gl.BindVertexArray(global_vao) 75 | 76 | vbo : VBO 77 | gl.GenBuffers(1, &vbo) 78 | gl.BindBuffer(gl.ARRAY_BUFFER, vbo) 79 | 80 | // Describe GPU buffer. 81 | gl.BufferData(gl.ARRAY_BUFFER, // target 82 | size_of(vertices), // size of the buffer object's data store 83 | &vertices, // data used for initialization 84 | gl.STATIC_DRAW) // usage 85 | 86 | // Position and color attributes. Don't forget to enable! 87 | gl.VertexAttribPointer(0, // index 88 | 2, // size 89 | gl.FLOAT, // type 90 | gl.FALSE, // normalized 91 | 5 * size_of(f32), // stride 92 | 0) // offset 93 | 94 | gl.VertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 5 * size_of(f32), 2 * size_of(f32)) 95 | 96 | // Enable the vertex position and color attributes defined above. 97 | gl.EnableVertexAttribArray(0) 98 | gl.EnableVertexAttribArray(1) 99 | 100 | // Compile vertex shader and fragment shader. 101 | // Note how much easier this is in Odin than in C++! 102 | 103 | program_ok : bool 104 | vertex_shader := string(#load("vertex.glsl" )) 105 | fragment_shader := string(#load("fragment.glsl")) 106 | 107 | global_shader, program_ok = gl.load_shaders_source(vertex_shader, fragment_shader); 108 | 109 | if !program_ok { 110 | fmt.println("ERROR: Failed to load and compile shaders."); os.exit(1) 111 | } 112 | 113 | gl.UseProgram(global_shader) 114 | 115 | // Start rotation timer. 116 | time.stopwatch_start(&watch) 117 | 118 | for !glfw.WindowShouldClose(window) { 119 | glfw.PollEvents() 120 | // If a key press happens, .PollEvents calls callback_key, defined below. 121 | // Note: glfw.PollEvents blocks on window menu interaction selection or 122 | // window resize. During window_resize, glfw.SetWindowRefreshCallback 123 | // calls window_refresh to redraw the window. 124 | 125 | render_screen(window, global_vao) 126 | } 127 | } 128 | 129 | render_screen :: proc( window : glfw.WindowHandle, vao : VAO) { 130 | // Send theta rotation value to GPU. 131 | raw_duration := time.stopwatch_duration(watch) 132 | secs := f32(time.duration_seconds(raw_duration)) 133 | theta := -secs 134 | gl.Uniform1f(gl.GetUniformLocation(global_shader, "theta"), theta) 135 | 136 | gl.BindVertexArray(vao) 137 | defer gl.BindVertexArray(0) 138 | 139 | // Draw commands. 140 | gl.ClearColor(0.1, 0.1, 0.1, 1) 141 | gl.Clear(gl.COLOR_BUFFER_BIT) 142 | 143 | gl.DrawArrays(gl.TRIANGLES, // Draw triangles. 144 | 0, // Begin drawing at index 0. 145 | 3) // Use 3 indices. 146 | glfw.SwapBuffers(window) 147 | } 148 | 149 | // Quit the window if the ESC key is pressed. This procedure is called by 150 | // glfw.SetKeyCallback. 151 | callback_key :: proc "c" ( window : glfw.WindowHandle, key, scancode, action, mods : i32 ) { 152 | if action == glfw.PRESS && key == glfw.KEY_ESCAPE { 153 | glfw.SetWindowShouldClose(window, true) 154 | } 155 | } 156 | 157 | // If the window needs to be redrawn (e.g. the user resizes the window), redraw the window. 158 | // This procedure is called by glfw.SetWindowRefreshCallback. 159 | window_refresh :: proc "c" ( window : glfw.WindowHandle ) { 160 | context = runtime.default_context() 161 | w, h : i32 162 | w, h = glfw.GetWindowSize(window) 163 | gl.Viewport(0,0,w,h) 164 | render_screen(window, global_vao) 165 | } 166 | -------------------------------------------------------------------------------- /Rainbow-Triangle/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec2 aPos; 3 | layout (location = 1) in vec3 aColor; 4 | 5 | out vec3 ourColor; 6 | 7 | uniform float theta; 8 | 9 | void main() { 10 | 11 | mat2 rotation_mat = mat2( 12 | cos(theta), sin(-theta), 13 | sin(theta), cos( theta) 14 | ); 15 | 16 | gl_Position = vec4(rotation_mat * aPos, 0, 1); 17 | ourColor = aColor; 18 | } 19 | -------------------------------------------------------------------------------- /Readme-Imgs/blinking-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Readme-Imgs/blinking-pink.png -------------------------------------------------------------------------------- /Readme-Imgs/hidden-animal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Readme-Imgs/hidden-animal.png -------------------------------------------------------------------------------- /Readme-Imgs/moving-character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Readme-Imgs/moving-character.png -------------------------------------------------------------------------------- /Readme-Imgs/psychedelic-earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Readme-Imgs/psychedelic-earth.png -------------------------------------------------------------------------------- /Readme-Imgs/rainbow-triangle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Readme-Imgs/rainbow-triangle.jpg -------------------------------------------------------------------------------- /Readme-Imgs/rotating-cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Readme-Imgs/rotating-cube.png -------------------------------------------------------------------------------- /Readme-Imgs/wavy-words.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Readme-Imgs/wavy-words.png -------------------------------------------------------------------------------- /Rotating-Cube/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec3 ourColor; 4 | out vec4 FragColor; 5 | 6 | void main() { 7 | FragColor = vec4(ourColor, 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Rotating-Cube/rotating-cube.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Rotating-Cube/rotating-cube.exe -------------------------------------------------------------------------------- /Rotating-Cube/rotating-cube.odin: -------------------------------------------------------------------------------- 1 | // An elementary program in Odin which creates a colored cube 2 | // which rotates over time. 3 | // 4 | // Created by Benjamin Thompson. Available at: 5 | // https://github.com/bg-thompson/OpenGL-Tutorials-In-Odin 6 | // Last updated: 2024.09.15 7 | // 8 | // To compile and run the program with optimizations, use the command 9 | // 10 | // odin run Rotating-Cube -o:speed 11 | // 12 | // Created for educational purposes. Used verbatim, it is probably 13 | // unsuitable for production code. 14 | 15 | package main 16 | 17 | import "vendor:glfw" 18 | import gl "vendor:OpenGL" 19 | import "core:time" 20 | import m "core:math" 21 | import "core:fmt" 22 | import "core:os" 23 | import "base:runtime" 24 | 25 | // Create alias types for vertex array / buffer objects 26 | VAO :: u32 27 | VBO :: u32 28 | EBO :: u32 29 | ShaderProgram :: u32 30 | 31 | // Global variables. 32 | global_vao : VAO 33 | global_shader : ShaderProgram 34 | watch : time.Stopwatch 35 | 36 | main :: proc() { 37 | // Setup window, including priming for OpenGL 3.3. 38 | glfw.Init() 39 | defer glfw.Terminate() 40 | 41 | glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) 42 | glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) 43 | glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 44 | 45 | window := glfw.CreateWindow(800, 800, "Rotating Cube", nil, nil) 46 | assert(window != nil) 47 | defer glfw.DestroyWindow(window) 48 | 49 | glfw.MakeContextCurrent(window) 50 | glfw.SwapInterval(1) 51 | 52 | // Load OpenGL 3.3 function pointers. 53 | gl.load_up_to(3,3, glfw.gl_set_proc_address) 54 | 55 | w, h := glfw.GetFramebufferSize(window) 56 | gl.Viewport(0,0,w,h) 57 | 58 | // Key press / Window-resize behaviour 59 | glfw.SetKeyCallback(window, callback_key) 60 | glfw.SetWindowRefreshCallback(window, window_refresh) 61 | 62 | // Cube face colors. 63 | c1 := rgbHexToFractions(0xd3_47_3d) // red 64 | c2 := rgbHexToFractions(0xf5_ef_eb) // white 65 | c3 := rgbHexToFractions(0xf6_ad_0f) // orange 66 | c4 := rgbHexToFractions(0x31_6a_96) // blue 67 | c5 := rgbHexToFractions(0x2e_24_3f) // purple 68 | c6 := rgbHexToFractions(0x86_bc_d1) // light blue 69 | 70 | 71 | // Cube vertices and vertex colors. 72 | vertices : [6*4*6] f32 73 | vertices = { 74 | // coords ; colors 75 | -1, -1, -1, c1.r, c1.g, c1.b, 76 | +1, -1, -1, c1.r, c1.g, c1.b, 77 | -1, +1, -1, c1.r, c1.g, c1.b, 78 | +1, +1, -1, c1.r, c1.g, c1.b, 79 | 80 | -1, -1, +1, c2.r, c2.g, c2.b, 81 | +1, -1, +1, c2.r, c2.g, c2.b, 82 | -1, +1, +1, c2.r, c2.g, c2.b, 83 | +1, +1, +1, c2.r, c2.g, c2.b, 84 | 85 | -1, -1, -1, c3.r, c3.g, c3.b, 86 | -1, +1, -1, c3.r, c3.g, c3.b, 87 | -1, -1, +1, c3.r, c3.g, c3.b, 88 | -1, +1, +1, c3.r, c3.g, c3.b, 89 | 90 | +1, -1, -1, c4.r, c4.g, c4.b, 91 | +1, +1, -1, c4.r, c4.g, c4.b, 92 | +1, -1, +1, c4.r, c4.g, c4.b, 93 | +1, +1, +1, c4.r, c4.g, c4.b, 94 | 95 | -1, -1, -1, c5.r, c5.g, c5.b, 96 | +1, -1, -1, c5.r, c5.g, c5.b, 97 | -1, -1, +1, c5.r, c5.g, c5.b, 98 | +1, -1, +1, c5.r, c5.g, c5.b, 99 | 100 | -1, +1, -1, c6.r, c6.g, c6.b, 101 | +1, +1, -1, c6.r, c6.g, c6.b, 102 | -1, +1, +1, c6.r, c6.g, c6.b, 103 | +1, +1, +1, c6.r, c6.g, c6.b, 104 | } 105 | 106 | index_array : [3 * 2 * 6] u32 107 | index_array = { 108 | 0, 1, 2, 1, 2, 3, // Face 1 109 | 4, 5, 6, 5, 6, 7, // Face 2 110 | 8, 9, 10, 9, 10, 11, // Face 3 111 | 12, 13, 14, 13, 14, 15, // Face 4 112 | 16, 17, 18, 17, 18, 19, // Face 5 113 | 20, 21, 22, 21, 22, 23, // Face 6 114 | } 115 | 116 | // Set up vertex array / element array / buffer objects 117 | gl.GenVertexArrays(1, &global_vao) 118 | gl.BindVertexArray(global_vao) 119 | 120 | vbo : VBO 121 | gl.GenBuffers(1, &vbo) 122 | gl.BindBuffer(gl.ARRAY_BUFFER, vbo) 123 | 124 | ebo : EBO 125 | gl.GenBuffers(1, &ebo) 126 | gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo) 127 | 128 | // Describe GPU buffer. 129 | gl.BufferData(gl.ARRAY_BUFFER, // target 130 | size_of(vertices), // size of the buffer object's data store 131 | &vertices, // data used for initialization 132 | gl.STATIC_DRAW) // usage 133 | 134 | gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size_of(index_array), &index_array, gl.STATIC_DRAW) 135 | 136 | // Position and color attributes. Don't forget to enable! 137 | gl.VertexAttribPointer(0, // index 138 | 3, // size 139 | gl.FLOAT, // type 140 | gl.FALSE, // normalized 141 | 6 * size_of(f32), // stride 142 | 0) // offset 143 | 144 | gl.VertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, 6 * size_of(f32), 3 * size_of(f32)) 145 | 146 | // Enable the vertex position and color attributes defined above. 147 | gl.EnableVertexAttribArray(0) 148 | gl.EnableVertexAttribArray(1) 149 | 150 | // Compile vertex shader and fragment shader. 151 | // Note how much easier this is in Odin than in C++! 152 | 153 | program_ok : bool 154 | vertex_shader := string(#load("vertex.glsl" )) 155 | fragment_shader := string(#load("fragment.glsl")) 156 | 157 | global_shader, program_ok = gl.load_shaders_source(vertex_shader, fragment_shader); 158 | 159 | if !program_ok { 160 | fmt.println("ERROR: Failed to load and compile shaders."); os.exit(1) 161 | } 162 | 163 | // Enable shader, and depth-testing during rendering. 164 | gl.UseProgram(global_shader) 165 | gl.Enable(gl.DEPTH_TEST) 166 | 167 | 168 | // Start rotation timer. 169 | time.stopwatch_start(&watch) 170 | 171 | for !glfw.WindowShouldClose(window) { 172 | glfw.PollEvents() 173 | // If a key press happens, .PollEvents calls callback_key, defined below. 174 | // Note: glfw.PollEvents blocks on window menu interaction selection or 175 | // window resize. During window_resize, glfw.SetWindowRefreshCallback 176 | // calls window_refresh to redraw the window. 177 | 178 | render_screen(window, global_vao) 179 | } 180 | } 181 | 182 | render_screen :: proc( window : glfw.WindowHandle, vao : VAO) { 183 | // Send theta rotation value to GPU. 184 | raw_duration := time.stopwatch_duration(watch) 185 | secs := f32(time.duration_seconds(raw_duration)) 186 | theta := f32(-secs) 187 | 188 | // Define matrices for model rotation and camera. 189 | rotation_mat := [9] f32 { 190 | m.cos(theta), m.sin(-theta), 0, 191 | m.sin(theta), m.cos( theta), 0, 192 | 0, 0, 1, 193 | } 194 | 195 | // Note: while the following matrices are constant, Odin 196 | // cannot take pointer address of constants, so put them 197 | // in the stack. It is clearer to have them here 198 | // than as a constant in the vertex shader. 199 | camera_to_x_mat := [9] f32 { 200 | 1, 0, 0, 201 | 0, 0, -1, 202 | 0, 1, 0, 203 | } 204 | 205 | ch :: f32(3) // Cube offset 206 | k :: f32(0.5) // Cube scale 207 | 208 | translation_mat := [16] f32 { 209 | k, 0, 0, 0, 210 | 0, k, 0, 0, 211 | 0, 0, k, ch, 212 | 0, 0, 0, 1, 213 | } 214 | // The math behind calculating a perspective matrix like that 215 | // below can be found in any decent graphics programming textbook. 216 | perspective_mat := [16] f32 { 217 | 2, 0, 0, 0, 218 | 0, 2, 0, 0, 219 | 0, 0, 3, -8, 220 | 0, 0, 1, 0, 221 | }; 222 | 223 | rotation_ptr : [^] f32 = &rotation_mat[0] 224 | camera_to_x_ptr : [^] f32 = &camera_to_x_mat[0] 225 | translation_ptr : [^] f32 = &translation_mat[0] 226 | perspective_ptr : [^] f32 = &perspective_mat[0] 227 | 228 | // Send the matrices above to the vertex shader program. 229 | gl.UniformMatrix3fv(gl.GetUniformLocation(global_shader, "rotation_mat" ), 1, gl.TRUE, rotation_ptr) 230 | gl.UniformMatrix3fv(gl.GetUniformLocation(global_shader, "camera_to_x_mat"), 1, gl.TRUE, camera_to_x_ptr) 231 | gl.UniformMatrix4fv(gl.GetUniformLocation(global_shader, "translation_mat"), 1, gl.TRUE, translation_ptr) 232 | gl.UniformMatrix4fv(gl.GetUniformLocation(global_shader, "perspective_mat"), 1, gl.TRUE, perspective_ptr) 233 | 234 | gl.BindVertexArray(vao) 235 | defer gl.BindVertexArray(0) 236 | 237 | // Draw commands. 238 | gl.ClearColor(0.0, 0.0, 0.0, 1) 239 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 240 | 241 | gl.DrawElements(gl.TRIANGLES, // Draw triangles. 242 | 6 * 6, // Draw 36 vertices. 243 | gl.UNSIGNED_INT, // Data type of the indices. 244 | rawptr(uintptr(0))) // Pointer to indices. (Not needed.) 245 | glfw.SwapBuffers(window) 246 | } 247 | 248 | // A function which simply converts colors specified in hex 249 | // to a triple of floats ranging from 0 to 1. 250 | rgbHexToFractions :: proc( hex_color : int ) -> ( ret : [3] f32 ) { 251 | ret.r = f32( (hex_color & 0x00_FF_00_00) >> 16 ) 252 | ret.g = f32( (hex_color & 0x00_00_FF_00) >> 8 ) 253 | ret.b = f32( (hex_color & 0x00_00_00_FF) >> 0 ) 254 | ret *= 1.0/255 255 | return 256 | } 257 | 258 | // Quit the window if the ESC key is pressed. This procedure is called by 259 | // glfw.SetKeyCallback. 260 | callback_key :: proc "c" ( window : glfw.WindowHandle, key, scancode, action, mods : i32 ) { 261 | if action == glfw.PRESS && key == glfw.KEY_ESCAPE { 262 | glfw.SetWindowShouldClose(window, true) 263 | } 264 | } 265 | 266 | // If the window needs to be redrawn (e.g. the user resizes the window), redraw the window. 267 | // This procedure is called by glfw.SetWindowRefreshCallback. 268 | window_refresh :: proc "c" ( window : glfw.WindowHandle ) { 269 | context = runtime.default_context() 270 | w, h : i32 271 | w, h = glfw.GetWindowSize(window) 272 | gl.Viewport(0,0,w,h) 273 | render_screen(window, global_vao) 274 | } 275 | -------------------------------------------------------------------------------- /Rotating-Cube/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec3 aColor; 4 | 5 | out vec3 ourColor; 6 | 7 | uniform float k; 8 | 9 | float kk = 0.5; 10 | 11 | uniform mat3 rotation_mat; 12 | uniform mat3 camera_to_x_mat; 13 | uniform mat4 translation_mat; 14 | uniform mat4 perspective_mat; 15 | 16 | void main() { 17 | 18 | gl_Position = perspective_mat * translation_mat * vec4(camera_to_x_mat * rotation_mat * aPos, 1); 19 | ourColor = aColor; 20 | } 21 | -------------------------------------------------------------------------------- /Wavy-Words/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | in vec2 TexCoords; 4 | out vec4 FragColor; 5 | 6 | uniform sampler2D glyph_texture; 7 | 8 | void main() { 9 | FragColor = vec4(0.5, 1, 1, texture(glyph_texture, TexCoords).r); 10 | //FragColor = vec4(TexCoords.x,TexCoords.y,1,1); 11 | } 12 | -------------------------------------------------------------------------------- /Wavy-Words/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec2 xyPos; 4 | layout (location = 1) in vec2 aTexCoords; 5 | out vec2 TexCoords; 6 | 7 | uniform mat2 projection; 8 | 9 | void main() { 10 | gl_Position = vec4(projection * xyPos, 0, 1); 11 | TexCoords = aTexCoords; 12 | } 13 | -------------------------------------------------------------------------------- /Wavy-Words/wavy-words.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bg-thompson/OpenGL-Tutorials-In-Odin/c0f17d8a3ba82ce6e067506eff573f8454c6d3d4/Wavy-Words/wavy-words.exe -------------------------------------------------------------------------------- /Wavy-Words/wavy-words.odin: -------------------------------------------------------------------------------- 1 | // A simple program in Odin which renders a sentence and animates 2 | // it like a wave. 3 | // 4 | // Created by Benjamin Thompson. Available at: 5 | // https://github.com/bg-thompson/OpenGL-Tutorials-In-Odin 6 | // Last updated: 2024.09.15 7 | // 8 | // To compile and run the program with optimizations, use the command 9 | // 10 | // odin run Wavy-Words -o:speed 11 | // 12 | // Created for educational purposes. Used verbatim, it is probably 13 | // unsuitable for production code. 14 | 15 | package main 16 | 17 | import "vendor:glfw" 18 | import gl "vendor:OpenGL" 19 | import tt "vendor:stb/truetype" 20 | import "core:time" 21 | import "core:math" 22 | import f "core:fmt" 23 | import "core:os" 24 | import "base:runtime" 25 | 26 | // Create alias types for vertex array / buffer objects 27 | VAO :: u32 28 | VBO :: u32 29 | ShaderProgram :: u32 30 | Texture :: u32 31 | 32 | // Global variables. 33 | global_vao : VAO 34 | global_shader : ShaderProgram 35 | watch : time.Stopwatch 36 | 37 | // Constants 38 | WINDOW_H :: 800 39 | WINDOW_W :: 800 40 | FONT :: `C:\Windows\Fonts\arialbd.ttf` 41 | FONTSCALE :: 125 42 | CHARACTER :: '#' 43 | SENTENCE :: "Duty calls, 3 o'clock tea!" 44 | 45 | // Information needed to render a single letter. 46 | CharacterTexture :: struct { 47 | texID : u32, 48 | width : i32, 49 | height : i32, 50 | bbox_x : f32, 51 | bbox_y : f32, 52 | advance : f32, 53 | bitmap : [^] byte, 54 | } 55 | 56 | rune_to_glyph_texture : map[rune]CharacterTexture 57 | 58 | update_uni_2fv :: proc( program : u32, var_name : cstring, new_value_ptr : [^] f32) { 59 | gl.UniformMatrix2fv(gl.GetUniformLocation(program, var_name), 1, gl.TRUE, new_value_ptr) 60 | } 61 | 62 | main :: proc() { 63 | 64 | //------------------------------------------------------------- 65 | // Use glfw to setup a window to render with OpenGl. 66 | //------------------------------------------------------------- 67 | glfw.Init() 68 | defer glfw.Terminate() 69 | 70 | glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3) 71 | glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3) 72 | glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) 73 | 74 | window := glfw.CreateWindow(WINDOW_W, WINDOW_H, "Wavy Words", nil, nil) 75 | assert(window != nil) 76 | defer glfw.DestroyWindow(window) 77 | 78 | glfw.MakeContextCurrent(window) 79 | glfw.SwapInterval(1) 80 | 81 | // Load OpenGL 3.3 function pointers. 82 | gl.load_up_to(3,3, glfw.gl_set_proc_address) 83 | 84 | ww, hh := glfw.GetFramebufferSize(window) 85 | gl.Viewport(0,0,ww,hh) 86 | 87 | // Key press / Window-resize behaviour 88 | glfw.SetKeyCallback(window, callback_key) 89 | glfw.SetWindowRefreshCallback(window, window_refresh) 90 | 91 | //------------------------------------------------------------- 92 | // Create texture maps for printable ascii characters (32-126) 93 | //------------------------------------------------------------- 94 | 95 | // Load .ttf file 96 | ttf_buffer :: [1<<23] u8 // Assumes file < 8MB 97 | fontdata, succ := os.read_entire_file(FONT) 98 | if !succ { 99 | f.println("ERROR: Couldn't load font file: ", FONT) 100 | os.exit(1) 101 | } 102 | font_ptr : [^] u8 = &fontdata[0] 103 | 104 | // Initialize font for stb_truetype 105 | font : tt.fontinfo 106 | tt.InitFont( 107 | info = &font, 108 | data = font_ptr, 109 | offset = 0, 110 | ) 111 | 112 | // Create rune to character_texture map for printable characters. 113 | rune_to_glyph_texture = make(map[rune] CharacterTexture) 114 | defer delete(rune_to_glyph_texture) 115 | 116 | character_scale : f32 117 | character_scale = tt.ScaleForPixelHeight(&font, FONTSCALE) 118 | 119 | gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1) 120 | 121 | for r in 32..=126 {// The printable ASCII range, per wikipedia. 122 | bm_w, bm_h, xo, yo : i32 123 | glyph_index := tt.FindGlyphIndex(&font, rune(r)) 124 | glyph_bitmap := tt.GetGlyphBitmap( 125 | info = &font, 126 | scale_x = 0, 127 | scale_y = character_scale, 128 | glyph = glyph_index, 129 | width = &bm_w, 130 | height = &bm_h, 131 | xoff = &xo, 132 | yoff = &yo, 133 | ) 134 | // Memory leak: the bitmaps should be freed with tt.FreeBitmap... 135 | // ...but it is unclear what the second arg of FreeBitmap does. 136 | 137 | // Get bbox values. 138 | box1, box2, box3, box4 : i32 139 | tt.GetGlyphBox(&font, glyph_index, &box1, &box2, &box3, &box4) 140 | 141 | // Get advance and l_bearing. 142 | raw_advance, raw_l_bearing : i32 143 | tt.GetGlyphHMetrics(&font, glyph_index, &raw_advance, &raw_l_bearing) 144 | 145 | // Scale to font size. 146 | bbox_x := character_scale * f32(box1) 147 | bbox_y := character_scale * f32(box2) 148 | advance := character_scale * f32(raw_advance) 149 | l_bearing := character_scale * f32(raw_l_bearing) 150 | 151 | // Register glyph texture with GPU. 152 | texture_id : Texture 153 | gl.GenTextures(1, &texture_id) 154 | // Note: textures are so small, we can load all of them instead 155 | // of the ones we need. 156 | gl.BindTexture(gl.TEXTURE_2D, texture_id) 157 | 158 | // Describe textures. 159 | gl.TexImage2D( 160 | gl.TEXTURE_2D, 161 | 0, 162 | gl.RED, 163 | bm_w, 164 | bm_h, 165 | 0, 166 | gl.RED, 167 | gl.UNSIGNED_BYTE, 168 | glyph_bitmap, 169 | ) 170 | 171 | // Wrapping and filtering options. 172 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 173 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 174 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 175 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 176 | 177 | // Save vital texture information for use when rendering the sentence. 178 | ct : CharacterTexture 179 | ct.texID = texture_id 180 | ct.width = bm_w 181 | ct.height = bm_h 182 | ct.bbox_x = bbox_x 183 | ct.bbox_y = bbox_y 184 | ct.advance = advance 185 | ct.bitmap = glyph_bitmap 186 | rune_to_glyph_texture[rune(r)] = ct 187 | } 188 | 189 | //------------------------------------------------------------------ 190 | //Tell the GPU about the data, and give it space to draw characters. 191 | //------------------------------------------------------------------ 192 | 193 | gl.GenVertexArrays(1, &global_vao) 194 | gl.BindVertexArray(global_vao) 195 | 196 | vbo : VBO 197 | gl.GenBuffers(1, &vbo) 198 | gl.BindBuffer(gl.ARRAY_BUFFER, vbo) 199 | 200 | // Allocate space to draw a rectangle with the character glyph on it. 201 | 202 | // The zero value of a multi pointer is nil (per odin-lang.org/docs/overview). 203 | gl.BufferData(gl.ARRAY_BUFFER, 204 | size_of(f32) * 4*6, // Two triangles per letter. 205 | nil, // No vertex data, yet. 206 | gl.DYNAMIC_DRAW) // Expect the data to be updated. 207 | 208 | // Position / texture position attributes. Don't forget to enable! 209 | gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 4 * size_of(f32), 0 * size_of(f32)) 210 | gl.VertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, 4 * size_of(f32), 2 * size_of(f32)) 211 | gl.EnableVertexAttribArray(0) 212 | gl.EnableVertexAttribArray(1) 213 | 214 | //------------------------------------------------------------- 215 | //Compile the vertex and fragment shader. 216 | //------------------------------------------------------------- 217 | 218 | program_ok : bool 219 | vertex_shader := string(#load("vertex.glsl" )) 220 | fragment_shader := string(#load("fragment.glsl")) 221 | 222 | global_shader, program_ok = gl.load_shaders_source(vertex_shader, fragment_shader); 223 | 224 | if !program_ok { 225 | f.println("ERROR: Failed to load and compile shaders."); os.exit(1) 226 | } 227 | 228 | gl.UseProgram(global_shader) 229 | 230 | //------------------------------------------------------------- 231 | //Render a character! 232 | //------------------------------------------------------------- 233 | 234 | // Start rotation timer. 235 | time.stopwatch_start(&watch) 236 | 237 | // Texture blending options. 238 | gl.Enable(gl.BLEND) 239 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 240 | 241 | // Main loop. 242 | for !glfw.WindowShouldClose(window) { 243 | glfw.PollEvents() 244 | // If a key press happens, .PollEvents calls callback_key, defined below. 245 | // Note: glfw.PollEvents blocks on window menu interaction selection or 246 | // window resize. During window_resize, glfw.SetWindowRefreshCallback 247 | // calls window_refresh to redraw the window. 248 | 249 | render_screen(window, global_vao) 250 | } 251 | } 252 | 253 | render_screen :: proc( window : glfw.WindowHandle, vao : VAO) { 254 | // Calculate projection matrix. 255 | render_rect_w , render_rect_h : f32 256 | render_rect_w = WINDOW_W 257 | render_rect_h = WINDOW_H 258 | 259 | proj_mat := [4] f32 { 260 | 1/render_rect_w, 0, 261 | 0, 1/render_rect_h, 262 | } 263 | 264 | proj_mat_ptr : [^] f32 = &proj_mat[0] 265 | 266 | // Draw commands. 267 | gl.BindVertexArray(vao) 268 | defer gl.BindVertexArray(0) 269 | 270 | update_uni_2fv(global_shader, "projection", proj_mat_ptr) 271 | 272 | gl.ClearColor(0.1, 0.1, 0.1, 1) 273 | gl.Clear(gl.COLOR_BUFFER_BIT) 274 | 275 | // Render SENTENCE. 276 | x, y, advance: f32 277 | x = -0.9 * WINDOW_H 278 | y = 0 279 | 280 | // Add wave effect to the text 281 | ha, wl : f32 // Half-amplitude, wavelength 282 | // Call 283 | // 284 | // odin run Wavy-Words -define:ha=200 285 | // 286 | // to change this value at compile time. 287 | ha = #config(ha, 100) 288 | wl = f32(WINDOW_W) * #config(wl, 0.2) 289 | 290 | raw_duration := time.stopwatch_duration(watch) 291 | secs := f32(time.duration_seconds(raw_duration)) 292 | t := 3 * secs 293 | 294 | // Render letters in SENTENCE. 295 | // Note that render_character calls gl.DrawArrays, and in general 296 | // gl.DrawArrays calls should be minimized if possible. In our 297 | // case the sentence is small enough that it's fine to call it 298 | // several times. 299 | for r in SENTENCE { 300 | ywave := ha * math.sin(x/wl + t) + y 301 | advance = render_character(r, x, ywave, vao) 302 | x += advance 303 | } 304 | 305 | // Send buffer to screen. 306 | glfw.SwapBuffers(window) 307 | } 308 | 309 | render_character :: proc( r : rune, xpos , ypos : f32, vao : VAO) -> (advance : f32) { 310 | char_texture : CharacterTexture 311 | char_texture = rune_to_glyph_texture[r] 312 | w := f32(char_texture.width) 313 | h := f32(char_texture.height) 314 | x := xpos + char_texture.bbox_x 315 | y := ypos + char_texture.bbox_y 316 | 317 | character_vertices : [4 * 6] f32 = { 318 | // Position ; Texture Coords 319 | x , y + h, 0, 0, 320 | x , y , 0, 1, 321 | x + w, y , 1, 1, 322 | x , y + h, 0, 0, 323 | x + w, y , 1, 1, 324 | x + w, y + h, 1, 0, 325 | } 326 | 327 | gl.BindVertexArray(vao) 328 | gl.BindTexture(gl.TEXTURE_2D, char_texture.texID) 329 | gl.BufferSubData(gl.ARRAY_BUFFER, // update vertices 330 | 0, // offset 331 | size_of(character_vertices), // size 332 | &character_vertices) // data 333 | // Draw the character! 334 | gl.DrawArrays(gl.TRIANGLES, 0, 6) 335 | advance = char_texture.advance 336 | return 337 | } 338 | 339 | // Quit the window if the ESC key is pressed. This procedure is called by 340 | // glfw.SetKeyCallback. 341 | callback_key :: proc "c" ( window : glfw.WindowHandle, key, scancode, action, mods : i32 ) { 342 | if action == glfw.PRESS && key == glfw.KEY_ESCAPE { 343 | glfw.SetWindowShouldClose(window, true) 344 | } 345 | } 346 | 347 | // If the window needs to be redrawn (e.g. the user resizes the window), redraw the window. 348 | // This procedure is called by glfw.SetWindowRefreshCallback. 349 | window_refresh :: proc "c" ( window : glfw.WindowHandle ) { 350 | context = runtime.default_context() 351 | w, h : i32 352 | w, h = glfw.GetWindowSize(window) 353 | gl.Viewport(0,0,w,h) 354 | render_screen(window, global_vao) 355 | } 356 | --------------------------------------------------------------------------------