opengl/learn_opengl/1_getting_started/7_camera

Licence This example has its own licence, rather than Odin's: opengl/learn_opengl/LICENSE
LearnOpenGL.com © 2025 by Joey de Vries is licensed under CC BY-NC 4.0. To view a copy of this license, visit https://creativecommons.org/licenses/by-nc/4.0/ https://learnopengl.com https://x.com/JoeyDeVriez

Code

main.odin ¶
297 linesSource

1package main
2
3import "core:math/linalg/glsl"
4import gl "vendor:OpenGL"
5import "vendor:glfw"
6import "core:fmt"
7import "core:os"
8import stbi "vendor:stb/image"
9import "core:math"
10
11framebuffer_size_callback :: proc "c" (window: glfw.WindowHandle, width: i32, height: i32) {
12	gl.Viewport(0, 0, width, height)
13}
14
15processInput :: proc "c" (window: glfw.WindowHandle) {
16	if glfw.GetKey(window, glfw.KEY_ESCAPE) == glfw.PRESS {
17		glfw.SetWindowShouldClose(window, true)
18	}
19
20	cameraSpeed := f32(deltaTime * 2.5)
21	if glfw.GetKey(window, glfw.KEY_W) == glfw.PRESS {
22		cameraPos += cameraSpeed * cameraFront
23	}
24	if glfw.GetKey(window, glfw.KEY_S) == glfw.PRESS {
25		cameraPos -= cameraSpeed * cameraFront
26	}
27	if glfw.GetKey(window, glfw.KEY_A) == glfw.PRESS {
28		cameraPos -= glsl.normalize(glsl.cross(cameraFront, cameraUp)) * cameraSpeed
29	}
30	if glfw.GetKey(window, glfw.KEY_D) == glfw.PRESS {
31		cameraPos += glsl.normalize(glsl.cross(cameraFront, cameraUp)) * cameraSpeed
32	}
33}
34
35mouse_callback :: proc "c" (window: glfw.WindowHandle, xposIn: f64, yposIn: f64) {
36	xpos := f32(xposIn)
37	ypos := f32(yposIn)
38
39	if firstMouse {
40		lastX = xpos
41		lastY = ypos
42		firstMouse = false
43	}
44
45	xoffset := xpos - lastX
46	yoffset := lastY - ypos
47	lastX = xpos
48	lastY = ypos
49
50	sensitivity: f32 = 0.1
51	xoffset *= sensitivity
52	yoffset *= sensitivity
53
54	yaw += xoffset
55	pitch += yoffset
56
57	if pitch > 89 {
58		pitch = 89
59	}
60	if pitch < -89 {
61		pitch = -89
62	}
63
64	front := glsl.vec3{
65		math.cos(glsl.radians(yaw)) * math.cos(glsl.radians(pitch)),
66		math.sin(glsl.radians(pitch)),
67		math.sin(glsl.radians(yaw)) * glsl.cos(glsl.radians(pitch)),
68	}
69	cameraFront = glsl.normalize(front)
70}
71
72scroll_callback := proc "c" (window: glfw.WindowHandle, xoffset: f64, yoffset: f64) {
73	fov -= f32(yoffset)
74	if fov < 1 {
75		fov = 1
76	}
77	if fov > 45 {
78		fov = 45
79	}
80}
81
82SCR_WIDTH :: 800
83SCR_HEIGHT :: 600
84
85cameraPos := glsl.vec3{0, 0, 3}
86cameraFront := glsl.vec3{0, 0, -1}
87cameraUp := glsl.vec3{0, 1, 0}
88
89firstMouse := true
90yaw: f32 = -90
91pitch: f32 = 0
92lastX: f32 = 800 / 2
93lastY: f32 = 600 / 2
94fov: f32 = 45
95
96deltaTime: f32 = 0
97lastFrame: f32 = 0
98
99main :: proc() {
100	glfw.Init()
101	glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 3)
102	glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 3)
103	glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
104
105	window := glfw.CreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nil, nil)
106	if window == nil {
107		fmt.println("Failed to create GLFW window")
108		glfw.Terminate()
109		os.exit(-1)
110	}
111	glfw.MakeContextCurrent(window)
112
113	gl.load_up_to(3, 3, glfw.gl_set_proc_address)
114
115	gl.Viewport(0, 0, SCR_WIDTH, SCR_HEIGHT)
116
117	glfw.SetFramebufferSizeCallback(window, framebuffer_size_callback)
118	glfw.SetCursorPosCallback(window, mouse_callback)
119	glfw.SetScrollCallback(window, scroll_callback)
120
121	glfw.SetInputMode(window, glfw.CURSOR, glfw.CURSOR_DISABLED)
122
123	vertices := [?]f32 {
124        -0.5, -0.5, -0.5,  0.0, 0.0,
125         0.5, -0.5, -0.5,  1.0, 0.0,
126         0.5,  0.5, -0.5,  1.0, 1.0,
127         0.5,  0.5, -0.5,  1.0, 1.0,
128        -0.5,  0.5, -0.5,  0.0, 1.0,
129        -0.5, -0.5, -0.5,  0.0, 0.0,
130
131        -0.5, -0.5,  0.5,  0.0, 0.0,
132         0.5, -0.5,  0.5,  1.0, 0.0,
133         0.5,  0.5,  0.5,  1.0, 1.0,
134         0.5,  0.5,  0.5,  1.0, 1.0,
135        -0.5,  0.5,  0.5,  0.0, 1.0,
136        -0.5, -0.5,  0.5,  0.0, 0.0,
137
138        -0.5,  0.5,  0.5,  1.0, 0.0,
139        -0.5,  0.5, -0.5,  1.0, 1.0,
140        -0.5, -0.5, -0.5,  0.0, 1.0,
141        -0.5, -0.5, -0.5,  0.0, 1.0,
142        -0.5, -0.5,  0.5,  0.0, 0.0,
143        -0.5,  0.5,  0.5,  1.0, 0.0,
144
145         0.5,  0.5,  0.5,  1.0, 0.0,
146         0.5,  0.5, -0.5,  1.0, 1.0,
147         0.5, -0.5, -0.5,  0.0, 1.0,
148         0.5, -0.5, -0.5,  0.0, 1.0,
149         0.5, -0.5,  0.5,  0.0, 0.0,
150         0.5,  0.5,  0.5,  1.0, 0.0,
151
152        -0.5, -0.5, -0.5,  0.0, 1.0,
153         0.5, -0.5, -0.5,  1.0, 1.0,
154         0.5, -0.5,  0.5,  1.0, 0.0,
155         0.5, -0.5,  0.5,  1.0, 0.0,
156        -0.5, -0.5,  0.5,  0.0, 0.0,
157        -0.5, -0.5, -0.5,  0.0, 1.0,
158
159        -0.5,  0.5, -0.5,  0.0, 1.0,
160         0.5,  0.5, -0.5,  1.0, 1.0,
161         0.5,  0.5,  0.5,  1.0, 0.0,
162         0.5,  0.5,  0.5,  1.0, 0.0,
163        -0.5,  0.5,  0.5,  0.0, 0.0,
164        -0.5,  0.5, -0.5,  0.0, 1.0,
165	}
166
167	cubePositions := [?]glsl.vec3{
168		{0.0, 0.0, 0.0},
169		{2.0, 5.0, -15.0},
170		{-1.5, -2.2, -2.5},
171		{-3.8, -2.0, -12.3},
172		{2.4, -0.4, -3.5},
173		{-1.7, 3.0, -7.5},
174		{1.3, -2.0, -2.5},
175		{1.5, 2.0, -2.5},
176		{1.5, 0.2, -1.5},
177		{-1.3, 1.0, -1.5},
178	}
179
180	gl.Enable(gl.DEPTH_TEST)
181
182	// The shader loading they create can be replaced with just this
183	shaderProgram, loaded_ok := gl.load_shaders_file("./res/vertex.vs", "./res/fragment.fs")
184	if !loaded_ok {
185		os.exit(-1)
186	}
187
188	VBO, VAO: u32
189	gl.GenVertexArrays(1, &VAO)
190	gl.GenBuffers(1, &VBO)
191
192	gl.BindVertexArray(VAO)
193
194	gl.BindBuffer(gl.ARRAY_BUFFER, VBO)
195	gl.BufferData(gl.ARRAY_BUFFER, size_of(vertices), raw_data(&vertices), gl.STATIC_DRAW)
196
197	gl.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 5 * size_of(f32), 0)
198	gl.EnableVertexAttribArray(0)
199
200	gl.VertexAttribPointer(1, 2, gl.FLOAT, gl.FALSE, 5 * size_of(f32), 3 * size_of(f32))
201	gl.EnableVertexAttribArray(1)
202
203	texture1, texture2: u32
204	gl.GenTextures(1, &texture1)
205	gl.BindTexture(gl.TEXTURE_2D, texture1)
206
207	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
208	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
209
210	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
211	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
212
213	width, height, nrChannels: i32
214	stbi.set_flip_vertically_on_load(1)
215
216	data := stbi.load("../res/container.jpg", &width, &height, &nrChannels, 0)
217	if data == nil {
218		fmt.println("Failed to load texture")
219		os.exit(-1)
220	}
221
222	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_BYTE, data)
223	gl.GenerateMipmap(gl.TEXTURE_2D)
224
225	stbi.image_free(data)
226
227	gl.GenTextures(1, &texture2)
228	gl.BindTexture(gl.TEXTURE_2D, texture2)
229
230	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
231	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
232
233	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
234	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
235
236	data = stbi.load("../res/awesomeface.png", &width, &height, &nrChannels, 0)
237	if data == nil {
238		fmt.println("Failed to load texture")
239		os.exit(-1)
240	}
241
242	gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data)
243	gl.GenerateMipmap(gl.TEXTURE_2D)
244
245	stbi.image_free(data)
246
247	gl.UseProgram(shaderProgram)
248	shader_set_int(shaderProgram, "texture1", 0)
249	shader_set_int(shaderProgram, "texture2", 1)
250
251	for !glfw.WindowShouldClose(window) {
252		currentFrame := f32(glfw.GetTime())
253		deltaTime = currentFrame - lastFrame
254		lastFrame = currentFrame
255
256		processInput(window)
257
258		gl.ClearColor(0.2, 0.3, 0.3, 1.0)
259		gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
260
261		gl.ActiveTexture(gl.TEXTURE0)
262		gl.BindTexture(gl.TEXTURE_2D, texture1)
263		gl.ActiveTexture(gl.TEXTURE1)
264		gl.BindTexture(gl.TEXTURE_2D, texture2)
265
266		gl.UseProgram(shaderProgram)
267
268		view: glsl.mat4 = 1
269		view *= glsl.mat4LookAt(cameraPos, cameraPos + cameraFront, cameraUp)
270
271		projection: glsl.mat4 = 1
272		projection *= glsl.mat4Perspective(glsl.radians_f32(45), f32(f32(SCR_WIDTH) / f32(SCR_HEIGHT)), 0.1, 100)
273
274		shader_set_mat4(shaderProgram, "projection", projection)
275		shader_set_mat4(shaderProgram, "view", view)
276
277		gl.BindVertexArray(VAO)
278		for position, i in cubePositions {
279			model: glsl.mat4 = 1
280			model *= glsl.mat4Translate(position)
281			angle := 20.0 * f32(i)
282			model *= glsl.mat4Rotate({1.0, 0.3, 0.5}, glsl.radians(angle))
283			shader_set_mat4(shaderProgram, "model", model)
284
285			gl.DrawArrays(gl.TRIANGLES, 0, 36)
286		}
287
288		glfw.SwapBuffers(window)
289		glfw.PollEvents()
290	}
291
292	gl.DeleteVertexArrays(1, &VAO)
293	gl.DeleteBuffers(1, &VBO)
294	gl.DeleteProgram(shaderProgram)
295
296	glfw.Terminate()
297}

shader.odin ¶
73 linesSource

1package main
2
3import gl "vendor:OpenGL"
4import "core:math/linalg/glsl"
5
6shader_set_bool :: proc(id: u32, name: cstring, value: bool) {
7	gl.Uniform1i(gl.GetUniformLocation(id, name), i32(value))
8}
9
10shader_set_int :: proc(id: u32, name: cstring, value: i32) {
11	gl.Uniform1i(gl.GetUniformLocation(id, name), value)
12}
13
14shader_set_float :: proc(id: u32, name: cstring, value: f32) {
15	gl.Uniform1f(gl.GetUniformLocation(id, name), value)
16}
17
18shader_set_vec2_v :: proc(id: u32, name: cstring, value: glsl.vec2) {
19	lc := value
20	gl.Uniform2fv(gl.GetUniformLocation(id, name), 1, raw_data(&lc))
21}
22
23shader_set_vec2_f :: proc(id: u32, name: cstring, x, y: f32) {
24	gl.Uniform2f(gl.GetUniformLocation(id, name), x, y)
25}
26
27shader_set_vec2 :: proc {
28	shader_set_vec2_v,
29	shader_set_vec2_f,
30}
31
32shader_set_vec3_v :: proc(id: u32, name: cstring, value: glsl.vec3) {
33	lc := value
34	gl.Uniform3fv(gl.GetUniformLocation(id, name), 1, raw_data(&lc))
35}
36
37shader_set_vec3_f :: proc(id: u32, name: cstring, x, y, z: f32) {
38	gl.Uniform3f(gl.GetUniformLocation(id, name), x, y, z)
39}
40
41shader_set_vec3 :: proc {
42	shader_set_vec3_v,
43	shader_set_vec3_f,
44}
45
46shader_set_vec4_v :: proc(id: u32, name: cstring, value: glsl.vec4) {
47	lc := value
48	gl.Uniform4fv(gl.GetUniformLocation(id, name), 1, raw_data(&lc))
49}
50
51shader_set_vec4_f :: proc(id: u32, name: cstring, x, y, z, w: f32) {
52	gl.Uniform4f(gl.GetUniformLocation(id, name), x, y, z, w)
53}
54
55shader_set_vec4 :: proc {
56	shader_set_vec4_v,
57	shader_set_vec4_f,
58}
59
60shader_set_mat2 :: proc(id: u32, name: cstring, mat: glsl.mat2) {
61	lc := mat
62	gl.UniformMatrix2fv(gl.GetUniformLocation(id, name), 1, gl.FALSE, raw_data(&lc))
63}
64
65shader_set_mat3 :: proc(id: u32, name: cstring, mat: glsl.mat3) {
66	lc := mat
67	gl.UniformMatrix3fv(gl.GetUniformLocation(id, name), 1, gl.FALSE, raw_data(&lc))
68}
69
70shader_set_mat4 :: proc(id: u32, name: cstring, mat: glsl.mat4) {
71	lc := mat
72	gl.UniformMatrix4fv(gl.GetUniformLocation(id, name), 1, gl.FALSE, raw_data(&lc))
73}

Shaders

res/fragment.fs ¶
14 linesSource

#version 330 core
out vec4 FragColor;

in vec2 TexCoord;

// texture samplers
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
	// linearly interpolate between both textures (80% container, 20% awesomeface)
	FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

res/vertex.vs ¶
15 linesSource

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(aPos, 1.0f);
	TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

Declarations Used 107