summaryrefslogtreecommitdiff
path: root/pbr
diff options
context:
space:
mode:
Diffstat (limited to 'pbr')
-rw-r--r--pbr/1.1.lighting/default.vert2
-rw-r--r--pbr/1.1.lighting/lighting.c128
-rw-r--r--pbr/1.1.lighting/occlusion.frag14
-rw-r--r--pbr/1.1.lighting/ssao.frag45
-rw-r--r--pbr/1.1.lighting/ssao.vert14
-rw-r--r--pbr/ibl_irradiance/background.frag17
-rw-r--r--pbr/ibl_irradiance/background.vert19
-rwxr-xr-xpbr/ibl_irradiance/build.sh5
-rw-r--r--pbr/ibl_irradiance/convolution.frag33
-rw-r--r--pbr/ibl_irradiance/convolution.vert17
-rw-r--r--pbr/ibl_irradiance/cubemap.frag25
-rw-r--r--pbr/ibl_irradiance/cubemap.vert17
-rw-r--r--pbr/ibl_irradiance/hdr.frag18
-rw-r--r--pbr/ibl_irradiance/hdr.vert17
-rwxr-xr-xpbr/ibl_irradiance/ibl_irradiancebin0 -> 402760 bytes
-rw-r--r--pbr/ibl_irradiance/ibl_irradiance.c228
-rw-r--r--pbr/ibl_irradiance/pbr.frag114
-rw-r--r--pbr/ibl_irradiance/pbr.vert21
-rw-r--r--pbr/ibl_irradiance_conversion/background.frag17
-rw-r--r--pbr/ibl_irradiance_conversion/background.vert19
-rwxr-xr-xpbr/ibl_irradiance_conversion/build.sh5
-rw-r--r--pbr/ibl_irradiance_conversion/cubemap.frag25
-rw-r--r--pbr/ibl_irradiance_conversion/cubemap.vert17
-rw-r--r--pbr/ibl_irradiance_conversion/hdr.frag18
-rw-r--r--pbr/ibl_irradiance_conversion/hdr.vert17
-rwxr-xr-xpbr/ibl_irradiance_conversion/ibl_irradiance_conversionbin0 -> 402568 bytes
-rw-r--r--pbr/ibl_irradiance_conversion/ibl_irradiance_conversion.c193
-rw-r--r--pbr/ibl_irradiance_conversion/pbr.frag105
-rw-r--r--pbr/ibl_irradiance_conversion/pbr.vert21
-rw-r--r--pbr/ibl_specular/background.frag17
-rw-r--r--pbr/ibl_specular/background.vert19
-rw-r--r--pbr/ibl_specular/brdf.frag98
-rw-r--r--pbr/ibl_specular/brdf.vert (renamed from pbr/1.1.lighting/gbuffer.vert)8
-rwxr-xr-xpbr/ibl_specular/build.sh5
-rw-r--r--pbr/ibl_specular/convolution.frag33
-rw-r--r--pbr/ibl_specular/convolution.vert17
-rw-r--r--pbr/ibl_specular/cubemap.frag25
-rw-r--r--pbr/ibl_specular/cubemap.vert17
-rw-r--r--pbr/ibl_specular/hdr.frag18
-rw-r--r--pbr/ibl_specular/hdr.vert17
-rwxr-xr-xpbr/ibl_specular/ibl_specularbin0 -> 407864 bytes
-rw-r--r--pbr/ibl_specular/ibl_specular.c307
-rw-r--r--pbr/ibl_specular/pbr.frag131
-rw-r--r--pbr/ibl_specular/pbr.vert21
-rw-r--r--pbr/ibl_specular/prefilter.frag91
-rw-r--r--pbr/ibl_specular/prefilter.vert17
-rwxr-xr-xpbr/lighting/build.sh (renamed from pbr/1.1.lighting/build.sh)0
-rw-r--r--pbr/lighting/default.frag15
-rw-r--r--pbr/lighting/default.vert17
-rw-r--r--pbr/lighting/hdr.frag18
-rw-r--r--pbr/lighting/hdr.vert17
-rwxr-xr-xpbr/lighting/lightingbin0 -> 401936 bytes
-rw-r--r--pbr/lighting/lighting.c157
-rw-r--r--pbr/lighting/pbr.frag105
-rw-r--r--pbr/lighting/pbr.vert21
-rwxr-xr-xpbr/textured/build.sh5
-rw-r--r--pbr/textured/hdr.frag18
-rw-r--r--pbr/textured/hdr.vert17
-rw-r--r--pbr/textured/pbr.frag126
-rw-r--r--pbr/textured/pbr.vert24
-rwxr-xr-xpbr/textured/texturedbin0 -> 397880 bytes
-rw-r--r--pbr/textured/textured.c151
62 files changed, 2498 insertions, 205 deletions
diff --git a/pbr/1.1.lighting/default.vert b/pbr/1.1.lighting/default.vert
deleted file mode 100644
index d83afbf..0000000
--- a/pbr/1.1.lighting/default.vert
+++ /dev/null
@@ -1,2 +0,0 @@
-#version 330 core
-layout(location = 0)
diff --git a/pbr/1.1.lighting/lighting.c b/pbr/1.1.lighting/lighting.c
deleted file mode 100644
index 87621ba..0000000
--- a/pbr/1.1.lighting/lighting.c
+++ /dev/null
@@ -1,128 +0,0 @@
-#include "GL/glew.h"
-#include "GLFW/glfw3.h"
-#include "pwyazh.h"
-#include "common.h"
-
-typedef struct {
- Arena *arena;
- Input input;
- Camera camera;
- Mesh *cube;
- GLFWwindow *window;
-} state_t;
-
-static S32 width = 1600;
-static S32 height = 800;
-
-void die(const char *fmt, ...)
-{
- fprintf(stderr, "error: ");
- va_list args;
- va_start(args, fmt);
- vprintf(fmt, args);
- va_end(args);
- fprintf(stderr, "\n");
- exit(1);
-}
-
-void init_glfw(state_t *state) {
- glfwSetErrorCallback(error_callback);
- if (glfwInit() == GLFW_FALSE)
- die("failed to initialize glfw");
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
- glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
- glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
- state->window = glfwCreateWindow(width, height, "prb lighting", 0, 0);
- if (!state->window)
- die("failed to create window");
- glfwSetInputMode(state->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
- glfwMakeContextCurrent(state->window);
-}
-
-void init_gl(void)
-{
- if (glewInit() != GLEW_OK)
- die("failed to initialize glew");
- glEnable(GL_DEPTH_TEST);
-}
-
-state_t init_state(void)
-{
- state_t state = {
- .arena = arena_alloc(Kilobytes(256)),
- .input = input_init(),
- .camera = {
- .pos = v3f(0.0f, 2.0f, 5.0f),
- .fovx = 90.0f,
- .near = 0.1f,
- .far = 100.0f,
- .yaw = 0.0f,
- .pitch = 0.0f
- },
- };
- return state;
-}
-
-int main(void)
-{
- state_t state = init_state();
-
- init_glfw(&state);
- init_gl();
-
- // meshes
- Mesh *cube = mesh_load_obj(state.arena, "../../data/models/cube.obj");
-
- // shaders
- U32 shader = load_shader("default.vert", "default.frag");
-
- F64 last_time = glfwGetTime();
- while (!glfwWindowShouldClose(state.window)) {
- F64 time = glfwGetTime();
- F32 dt = time - last_time;
- last_time = time;
-
- input_update_last_state(&state.input);
-
- glfwPollEvents();
- process_glfw_keyboard(state.window, &state.input);
- process_glfw_mouse_pos(state.window, &state.input);
-
- if (key_first_press(state.input.exit))
- glfwSetWindowShouldClose(state.window, GLFW_TRUE);
-
- F32 speed = 2.0f;
- V3F dv = get_dv_camera_first_person(&state.input, &state.camera, speed, dt);
- V3F camera_dp = v3f_add(camera_dp, dv);
- camera_dp = v3f_scalef(camera_dp, 0.8f);
- state.camera.pos = v3f_add(state.camera.pos, camera_dp);
-
- F32 sensitivity = 0.1f;
- state.input.mouse_offset = v2f_scalef(state.input.mouse_offset, sensitivity);
- state.camera.yaw += state.input.mouse_offset.x;
- state.camera.pitch += state.input.mouse_offset.y;
- if (state.camera.pitch > 89.0f)
- state.camera.pitch = 89.0f;
- if (state.camera.pitch < -89.0f)
- state.camera.pitch = -89.0f;
-
- // update
-
- // render
- F32 ar = (F32)width / (F32)height;
- MAT4 projection = camera_persp(state.camera, ar);
- MAT4 view = get_view_matrix(&state.camera);
-
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- glUseProgram(shader);
- mesh_draw(cube);
- glUseProgram(0);
-
- glfwSwapBuffers(state.window);
- }
-
- return 0;
-}
diff --git a/pbr/1.1.lighting/occlusion.frag b/pbr/1.1.lighting/occlusion.frag
deleted file mode 100644
index 41a436b..0000000
--- a/pbr/1.1.lighting/occlusion.frag
+++ /dev/null
@@ -1,14 +0,0 @@
-#version 330 core
-
-in vert_t {
- vec2 tex_coords;
-} vert;
-
-out vec4 frag_color;
-
-uniform sampler2D colorbuffer;
-
-void main()
-{
- frag_color = vec4(vec3(texture(colorbuffer, vert.tex_coords).r), 1.0);
-}
diff --git a/pbr/1.1.lighting/ssao.frag b/pbr/1.1.lighting/ssao.frag
deleted file mode 100644
index dd03754..0000000
--- a/pbr/1.1.lighting/ssao.frag
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 330 core
-
-in vert_t {
- vec2 tex_coords;
-} vert;
-
-out float frag_color;
-
-uniform sampler2D positions;
-uniform sampler2D normals;
-uniform sampler2D noise;
-
-uniform vec3 samples[64];
-uniform mat4 projection;
-
-const vec2 noise_scale = vec2(1600.0 / 4.0, 900.0 / 4.0);
-
-void main()
-{
- vec3 position = texture(positions, vert.tex_coords).rgb;
- vec3 normal = texture(normals, vert.tex_coords).rgb;
- vec3 random = texture(noise, vert.tex_coords * noise_scale).rgb;
-
- vec3 tangent = normalize(random - normal * dot(random, normal));
- vec3 bitangent = cross(normal, tangent);
- mat3 TBN = mat3(tangent, bitangent, normal);
-
- int nsamples = 64;
- float radius = 0.5;
- float bias = 0.025;
- float occlusion = 0.0;
- for (int i = 0; i < nsamples; i++) {
- vec3 sample = TBN * samples[i];
- sample = position + sample * radius;
- vec4 offset = vec4(sample, 1.0);
- offset = projection * offset;
- offset.xyz /= offset.w;
- offset.xyz = offset.xyz * 0.5 + 0.5;
- float depth = texture(positions, offset.xy).z;
- float range_check = smoothstep(0.0, 1.0, radius / abs(position.z - depth));
- occlusion += (depth >= sample.z + bias ? 1.0 : 0.0) * range_check;
- }
- occlusion = 1.0 - (occlusion / nsamples);
- frag_color = occlusion;
-}
diff --git a/pbr/1.1.lighting/ssao.vert b/pbr/1.1.lighting/ssao.vert
deleted file mode 100644
index eed3be3..0000000
--- a/pbr/1.1.lighting/ssao.vert
+++ /dev/null
@@ -1,14 +0,0 @@
-#version 330 core
-
-layout(location = 0) in vec3 position;
-layout(location = 2) in vec2 tex_coords;
-
-out vert_t {
- vec2 tex_coords;
-} vert;
-
-void main()
-{
- vert.tex_coords = tex_coords;
- gl_Position = vec4(position, 1.0);
-}
diff --git a/pbr/ibl_irradiance/background.frag b/pbr/ibl_irradiance/background.frag
new file mode 100644
index 0000000..e3891eb
--- /dev/null
+++ b/pbr/ibl_irradiance/background.frag
@@ -0,0 +1,17 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform samplerCube cubemap;
+
+void main(void)
+{
+ vec3 color = texture(cubemap, vert.position).rgb;
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / 2.2));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_irradiance/background.vert b/pbr/ibl_irradiance/background.vert
new file mode 100644
index 0000000..25b9778
--- /dev/null
+++ b/pbr/ibl_irradiance/background.vert
@@ -0,0 +1,19 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ vert.position = position;
+
+ mat4 rotate_view = mat4(mat3(view));
+ vec4 clip_position = projection * rotate_view * vec4(position, 1.0);
+ gl_Position = clip_position.xyww;
+}
diff --git a/pbr/ibl_irradiance/build.sh b/pbr/ibl_irradiance/build.sh
new file mode 100755
index 0000000..fdecd76
--- /dev/null
+++ b/pbr/ibl_irradiance/build.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+. ../../config
+TARGET='ibl_irradiance'
+set -x
+gcc -o $TARGET $CFLAGS $INCLUDE $LFLAGS $TARGET.c $LIBS
diff --git a/pbr/ibl_irradiance/convolution.frag b/pbr/ibl_irradiance/convolution.frag
new file mode 100644
index 0000000..69ce8af
--- /dev/null
+++ b/pbr/ibl_irradiance/convolution.frag
@@ -0,0 +1,33 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform samplerCube cubemap;
+
+const float PI = 3.14159265359;
+
+void main(void)
+{
+ vec3 normal = normalize(vert.position);
+ vec3 up = vec3(0.0, 1.0, 0.0);
+ vec3 right = cross(up, normal);
+ up = cross(normal, right);
+
+ vec3 irradiance = vec3(0.0);
+ float delta = 0.025;
+ int nsamples = 0;
+ for (float phi = 0.0; phi < 2.0 * PI; phi += delta) {
+ for (float theta = 0.0; theta < 0.5 * PI; theta += delta) {
+ vec3 tangent = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
+ vec3 v = tangent.x * right + tangent.y * up + tangent.z * normal;
+ irradiance += texture(cubemap, v).rgb * cos(theta) * sin(theta);
+ nsamples++;
+ }
+ }
+ irradiance *= PI / nsamples;
+ frag_color = vec4(irradiance, 1.0);
+}
diff --git a/pbr/ibl_irradiance/convolution.vert b/pbr/ibl_irradiance/convolution.vert
new file mode 100644
index 0000000..8f8862e
--- /dev/null
+++ b/pbr/ibl_irradiance/convolution.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ gl_Position = projection * view * vec4(position, 1.0);
+
+ vert.position = position;
+}
diff --git a/pbr/ibl_irradiance/cubemap.frag b/pbr/ibl_irradiance/cubemap.frag
new file mode 100644
index 0000000..349347a
--- /dev/null
+++ b/pbr/ibl_irradiance/cubemap.frag
@@ -0,0 +1,25 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D map;
+
+const vec2 inv_atan = vec2(0.1591, 0.3183);
+vec2 sample_spherical_map(vec3 v)
+{
+ vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
+ uv *= inv_atan;
+ uv += 0.5;
+ return uv;
+}
+
+void main(void)
+{
+ vec2 uv = sample_spherical_map(normalize(vert.position));
+ vec3 color = texture(map, uv).rgb;
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_irradiance/cubemap.vert b/pbr/ibl_irradiance/cubemap.vert
new file mode 100644
index 0000000..8f8862e
--- /dev/null
+++ b/pbr/ibl_irradiance/cubemap.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ gl_Position = projection * view * vec4(position, 1.0);
+
+ vert.position = position;
+}
diff --git a/pbr/ibl_irradiance/hdr.frag b/pbr/ibl_irradiance/hdr.frag
new file mode 100644
index 0000000..01650ae
--- /dev/null
+++ b/pbr/ibl_irradiance/hdr.frag
@@ -0,0 +1,18 @@
+#version 330 core
+
+in vert_t {
+ vec2 tex_coords;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D colorbuffer;
+
+void main(void)
+{
+ const float gamma = 2.2;
+ vec3 color = vec3(texture(colorbuffer, vert.tex_coords));
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / gamma));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_irradiance/hdr.vert b/pbr/ibl_irradiance/hdr.vert
new file mode 100644
index 0000000..1b953d7
--- /dev/null
+++ b/pbr/ibl_irradiance/hdr.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 2) in vec2 tex_coords;
+
+out vert_t {
+ vec2 tex_coords;
+} vert;
+
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = model * vec4(position, 1.0);
+
+ vert.tex_coords = tex_coords;
+}
diff --git a/pbr/ibl_irradiance/ibl_irradiance b/pbr/ibl_irradiance/ibl_irradiance
new file mode 100755
index 0000000..aee47cb
--- /dev/null
+++ b/pbr/ibl_irradiance/ibl_irradiance
Binary files differ
diff --git a/pbr/ibl_irradiance/ibl_irradiance.c b/pbr/ibl_irradiance/ibl_irradiance.c
new file mode 100644
index 0000000..921c60d
--- /dev/null
+++ b/pbr/ibl_irradiance/ibl_irradiance.c
@@ -0,0 +1,228 @@
+#include "GL/glew.h"
+#include "GLFW/glfw3.h"
+#include "common.h"
+
+int main(void)
+{
+ state_t state = init_state(1600, 800);
+
+ init_glfw(&state);
+ init_gl();
+ glDepthFunc(GL_LEQUAL);
+
+ /* meshes */
+ Mesh *cube = mesh_load_obj(state.arena, "../../data/models/cube.obj");
+ Mesh *sphere = mesh_load_obj(state.arena, "../../data/models/sphere.obj");
+ Mesh *quad = mesh_gen_quad(state.arena);
+
+ /* shaders */
+ add_shader("pbr.vert", "pbr.frag", 0);
+ add_shader("hdr.vert", "hdr.frag", 0);
+ add_shader("cubemap.vert", "cubemap.frag", 0);
+ add_shader("background.vert", "background.frag", 0);
+ add_shader("convolution.vert", "convolution.frag", 0);
+
+ light_t lights[4] = {
+ {{-10.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{-5.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 5.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 10.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ };
+
+ /* textures */
+ U32 hdr_texture = load_hdr_texture("../../data/textures/loigerwiesen.hdr");;
+
+ /* framebuffers */
+ U32 hdr_fbo, hdr_colorbuffer, hdr_rbo;
+ glGenFramebuffers(1, &hdr_fbo);
+ glGenTextures(1, &hdr_colorbuffer);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.width, state.height, 0, GL_RGBA, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glGenRenderbuffers(1, &hdr_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, hdr_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, state.width, state.height);
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hdr_colorbuffer, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, hdr_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 capture_fbo, capture_rbo;
+ glGenFramebuffers(1, &capture_fbo);
+ glGenRenderbuffers(1, &capture_rbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, capture_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 cubemap;
+ glGenTextures(1, &cubemap);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ for (U32 i = 0; i < 6; i++)
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 512, 512, 0, GL_RGB, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ MAT4 capture_projection = perspective(90.0f, 1.0f, 0.1f, 10.0f);
+ V3F capture_origin = {0};
+ MAT4 capture_views[6] = {
+ look_at(capture_origin, v3f( 1.0f, 0.0f, 0.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f(-1.0f, 0.0f, 0.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f( 0.0f, 1.0f, 0.0f), v3f(0.0f, 0.0f, 1.0f)),
+ look_at(capture_origin, v3f( 0.0f, -1.0f, 0.0f), v3f(0.0f, 0.0f, -1.0f)),
+ look_at(capture_origin, v3f( 0.0f, 0.0f, 1.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f( 0.0f, 0.0f, -1.0f), v3f(0.0f, -1.0f, 0.0f)),
+ };
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ U32 shader = find_shader("cubemap");
+ if (!shader) die("waht");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", capture_projection);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, hdr_texture);
+
+ glViewport(0, 0, 512, 512);
+ for (U32 i = 0; i < 6; i++) {
+ shader_set_mat4fv(shader, "view", capture_views[i]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mesh_draw(cube);
+ }
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 irradiance_map;
+ glGenTextures(1, &irradiance_map);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, irradiance_map);
+ for (U32 i = 0; i < 6; i++)
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, GL_RGB, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ shader = find_shader("convolution");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", capture_projection);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ glViewport(0, 0, 32, 32);
+ for (U32 i = 0; i < 6; i++) {
+ shader_set_mat4fv(shader, "view", capture_views[i]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradiance_map, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mesh_draw(cube);
+ }
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glViewport(0, 0, state.width, state.height);
+
+ F32 time = 0;
+
+ while (!glfwWindowShouldClose(state.window)) {
+ handle_glfw_events(state.window, &state.input);
+ F32 dt = lock_framerate(60);
+ fps_info(dt, 10);
+
+ /* update */
+ update_camera_first_person(&state.camera, &state.input, dt, 2.0f);
+ F32 limit = 4.0f;
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++)
+ lights[i].position.y = sinf(time + i) * limit;
+ time += dt;
+
+ /* render */
+ F32 ar = (F32)state.width / (F32)state.height;
+ MAT4 projection = camera_persp(state.camera, ar);
+ MAT4 view = get_view_matrix(&state.camera);
+
+ glClearColor(0.16f, 0.16f, 0.16f, 1.0f);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("pbr");
+ glUseProgram(shader);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, irradiance_map);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ shader_set_3fv(shader, "camera", state.camera.pos);
+ shader_set_3fv(shader, "color", v3f(0.04, 0.04, 0.04));
+ shader_set_1f(shader, "ao", 1.0f);
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ char str[512];
+ snprintf(str, 512, "lights[%d].position", i);
+ shader_set_3fv(shader, str, lights[i].position);
+ snprintf(str, 512, "lights[%d].color", i);
+ shader_set_3fv(shader, str, lights[i].color);
+ }
+ S32 rows = 7;
+ S32 cols = 7;
+ F32 offset = 3.0f;
+ for (S32 row = 0; row < rows; row++) {
+ shader_set_1f(shader, "metallic", (F32)row / (F32)rows);
+ for (S32 col = 0; col < cols; col++) {
+ shader_set_1f(shader, "roughness", clamp(0.05f, (F32)col / (F32)cols, 1.0f));
+ V3F pos = {(col - cols / 2.0f) * offset, (row - rows / 2.0f) * offset, 0.0f};
+ MAT4 model = mat4_make_translate(pos);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(sphere);
+ }
+ }
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ MAT4 model = mat4_make_scale(v3f(0.5f, 0.5f, 0.5f));
+ model = mat4_translate(model, lights[i].position);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(sphere);
+ }
+ glUseProgram(0);
+
+ shader = find_shader("background");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ mesh_draw(cube);
+ glUseProgram(0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("hdr");
+ if (!shader) die("!");
+ glUseProgram(shader);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ shader_set_mat4fv(shader, "model", mat4_identity());
+ mesh_draw(quad);
+ glUseProgram(0);
+
+ glfwSwapBuffers(state.window);
+ }
+
+ return 0;
+}
diff --git a/pbr/ibl_irradiance/pbr.frag b/pbr/ibl_irradiance/pbr.frag
new file mode 100644
index 0000000..88cf01d
--- /dev/null
+++ b/pbr/ibl_irradiance/pbr.frag
@@ -0,0 +1,114 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+out vec4 frag_color;
+
+uniform vec3 camera;
+
+uniform vec3 color;
+uniform float metallic;
+uniform float roughness;
+uniform float ao;
+
+uniform samplerCube irradiance_map;
+
+struct light_t {
+ vec3 position;
+ vec3 color;
+};
+
+const int light_count = 4;
+uniform light_t lights[light_count];
+
+const float PI = 3.14159265358;
+
+vec3 fresnel_shlick(float costheta, vec3 f0)
+{
+ return f0 + (1.0 - f0) * pow(1.0 - costheta, 5.0);
+}
+
+float distribution_ggx(vec3 n, vec3 h, float roughness)
+{
+ float a = roughness * roughness;
+ float a2 = a * a;
+ float ndoth = max(dot(n, h), 0.0);
+ float ndoth2 = ndoth * ndoth;
+
+ float num = a2;
+ float denom = (ndoth2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+
+ return num / denom;
+}
+
+float geometry_shlick_ggx(float ndotv, float roughness)
+{
+ float r = roughness + 1.0;
+ float k = r * r / 8.0;
+
+ float num = ndotv;
+ float denom = ndotv * (1.0 - k) + k;
+
+ return num / denom;
+}
+
+float geometry_smith(vec3 n, vec3 v, vec3 l, float roughness)
+{
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+ float ggx2 = geometry_shlick_ggx(ndotv, roughness);
+ float ggx1 = geometry_shlick_ggx(ndotl, roughness);
+
+ return ggx1 * ggx2;
+}
+
+void main(void)
+{
+ vec3 n = vert.normal;
+ vec3 v = normalize(camera - vert.position);
+
+ vec3 f0 = vec3(0.04);
+ f0 = mix(f0, color, metallic);
+
+ vec3 lo = vec3(0.0);
+ for (int i = 0; i < light_count; i++) {
+ vec3 l = normalize(lights[i].position - vert.position);
+ vec3 h = normalize(v + l);
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+
+ float distance = length(lights[i].position - vert.position);
+ float attenuation = 1.0 / (distance * distance);
+
+ vec3 radiance = lights[i].color * attenuation;
+
+ vec3 f = fresnel_shlick(max(dot(h, v), 0.0), f0);
+
+ float ndf = distribution_ggx(n, h, roughness);
+ float g = geometry_smith(n, v, l, roughness);
+
+ vec3 numerator = ndf * g * f;
+ float denominator = 4.0 * ndotv * ndotl;
+ vec3 specular = numerator / max(denominator, 0.001);
+
+ vec3 ks = f;
+ vec3 kd = vec3(1.0) - ks;
+
+ kd *= 1.0 - metallic;
+
+ lo += (kd * color / PI + specular) * radiance * ndotl;
+ }
+
+ vec3 ks = fresnel_shlick(max(dot(n, v), 0.0), f0);
+ vec3 kd = 1.0 - ks;
+ kd *= 1.0 - metallic;
+ vec3 irradiance = texture(irradiance_map, n).rgb;
+ vec3 diffuse = irradiance * color;
+ vec3 ambient = kd * diffuse * ao;
+
+ frag_color = vec4(ambient + lo, 1.0);
+}
diff --git a/pbr/ibl_irradiance/pbr.vert b/pbr/ibl_irradiance/pbr.vert
new file mode 100644
index 0000000..e6e3800
--- /dev/null
+++ b/pbr/ibl_irradiance/pbr.vert
@@ -0,0 +1,21 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+
+out vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = projection * view * model * vec4(position, 1.0);
+
+ vert.position = vec3(model * vec4(position, 1.0));
+ vert.normal = normalize(mat3(transpose(inverse(model))) * normal);
+}
diff --git a/pbr/ibl_irradiance_conversion/background.frag b/pbr/ibl_irradiance_conversion/background.frag
new file mode 100644
index 0000000..e3891eb
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/background.frag
@@ -0,0 +1,17 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform samplerCube cubemap;
+
+void main(void)
+{
+ vec3 color = texture(cubemap, vert.position).rgb;
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / 2.2));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_irradiance_conversion/background.vert b/pbr/ibl_irradiance_conversion/background.vert
new file mode 100644
index 0000000..25b9778
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/background.vert
@@ -0,0 +1,19 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ vert.position = position;
+
+ mat4 rotate_view = mat4(mat3(view));
+ vec4 clip_position = projection * rotate_view * vec4(position, 1.0);
+ gl_Position = clip_position.xyww;
+}
diff --git a/pbr/ibl_irradiance_conversion/build.sh b/pbr/ibl_irradiance_conversion/build.sh
new file mode 100755
index 0000000..834030a
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/build.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+. ../../config
+TARGET='ibl_irradiance_conversion'
+set -x
+gcc -o $TARGET $CFLAGS $INCLUDE $LFLAGS $TARGET.c $LIBS
diff --git a/pbr/ibl_irradiance_conversion/cubemap.frag b/pbr/ibl_irradiance_conversion/cubemap.frag
new file mode 100644
index 0000000..349347a
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/cubemap.frag
@@ -0,0 +1,25 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D map;
+
+const vec2 inv_atan = vec2(0.1591, 0.3183);
+vec2 sample_spherical_map(vec3 v)
+{
+ vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
+ uv *= inv_atan;
+ uv += 0.5;
+ return uv;
+}
+
+void main(void)
+{
+ vec2 uv = sample_spherical_map(normalize(vert.position));
+ vec3 color = texture(map, uv).rgb;
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_irradiance_conversion/cubemap.vert b/pbr/ibl_irradiance_conversion/cubemap.vert
new file mode 100644
index 0000000..8f8862e
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/cubemap.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ gl_Position = projection * view * vec4(position, 1.0);
+
+ vert.position = position;
+}
diff --git a/pbr/ibl_irradiance_conversion/hdr.frag b/pbr/ibl_irradiance_conversion/hdr.frag
new file mode 100644
index 0000000..01650ae
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/hdr.frag
@@ -0,0 +1,18 @@
+#version 330 core
+
+in vert_t {
+ vec2 tex_coords;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D colorbuffer;
+
+void main(void)
+{
+ const float gamma = 2.2;
+ vec3 color = vec3(texture(colorbuffer, vert.tex_coords));
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / gamma));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_irradiance_conversion/hdr.vert b/pbr/ibl_irradiance_conversion/hdr.vert
new file mode 100644
index 0000000..1b953d7
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/hdr.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 2) in vec2 tex_coords;
+
+out vert_t {
+ vec2 tex_coords;
+} vert;
+
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = model * vec4(position, 1.0);
+
+ vert.tex_coords = tex_coords;
+}
diff --git a/pbr/ibl_irradiance_conversion/ibl_irradiance_conversion b/pbr/ibl_irradiance_conversion/ibl_irradiance_conversion
new file mode 100755
index 0000000..cc43012
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/ibl_irradiance_conversion
Binary files differ
diff --git a/pbr/ibl_irradiance_conversion/ibl_irradiance_conversion.c b/pbr/ibl_irradiance_conversion/ibl_irradiance_conversion.c
new file mode 100644
index 0000000..0ffbe7c
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/ibl_irradiance_conversion.c
@@ -0,0 +1,193 @@
+#include "GL/glew.h"
+#include "GLFW/glfw3.h"
+#include "common.h"
+
+int main(void)
+{
+ state_t state = init_state(1600, 800);
+
+ init_glfw(&state);
+ init_gl();
+ glDepthFunc(GL_LEQUAL);
+
+ /* meshes */
+ Mesh *cube = mesh_load_obj(state.arena, "../../data/models/cube.obj");
+ Mesh *quad = mesh_gen_quad(state.arena);
+
+ /* shaders */
+ add_shader("pbr.vert", "pbr.frag", 0);
+ add_shader("hdr.vert", "hdr.frag", 0);
+ add_shader("cubemap.vert", "cubemap.frag", 0);
+ add_shader("background.vert", "background.frag", 0);
+
+ light_t lights[4] = {
+ {{-10.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{-5.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 5.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 10.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ };
+
+ /* textures */
+ U32 hdr_texture = load_hdr_texture("../../data/textures/loigerwiesen.hdr");
+
+ /* framebuffers */
+ U32 hdr_fbo, hdr_colorbuffer, hdr_rbo;
+ glGenFramebuffers(1, &hdr_fbo);
+ glGenTextures(1, &hdr_colorbuffer);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.width, state.height, 0, GL_RGBA, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glGenRenderbuffers(1, &hdr_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, hdr_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, state.width, state.height);
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hdr_colorbuffer, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, hdr_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 capture_fbo, capture_rbo;
+ glGenFramebuffers(1, &capture_fbo);
+ glGenRenderbuffers(1, &capture_rbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, capture_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 cubemap;
+ glGenTextures(1, &cubemap);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ for (U32 i = 0; i < 6; i++)
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 512, 512, 0, GL_RGB, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ MAT4 capture_projection = perspective(90.0f, 1.0f, 0.1f, 10.0f);
+ V3F capture_origin = {0};
+ MAT4 capture_views[6] = {
+ look_at(capture_origin, v3f( 1.0f, 0.0f, 0.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f(-1.0f, 0.0f, 0.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f( 0.0f, 1.0f, 0.0f), v3f(0.0f, 0.0f, 1.0f)),
+ look_at(capture_origin, v3f( 0.0f, -1.0f, 0.0f), v3f(0.0f, 0.0f, -1.0f)),
+ look_at(capture_origin, v3f( 0.0f, 0.0f, 1.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f( 0.0f, 0.0f, -1.0f), v3f(0.0f, -1.0f, 0.0f)),
+ };
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ U32 shader = find_shader("cubemap");
+ if (!shader) die("waht");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", capture_projection);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, hdr_texture);
+
+ glViewport(0, 0, 512, 512);
+ for (U32 i = 0; i < 6; i++) {
+ shader_set_mat4fv(shader, "view", capture_views[i]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mesh_draw(cube);
+ }
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glViewport(0, 0, state.width, state.height);
+
+ F32 time = 0;
+
+ while (!glfwWindowShouldClose(state.window)) {
+ handle_glfw_events(state.window, &state.input);
+ F32 dt = lock_framerate(60);
+ fps_info(dt, 10);
+
+ /* update */
+ update_camera_first_person(&state.camera, &state.input, dt, 2.0f);
+ F32 limit = 4.0f;
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++)
+ lights[i].position.y = sinf(time + i) * limit;
+ time += dt;
+
+ /* render */
+ F32 ar = (F32)state.width / (F32)state.height;
+ MAT4 projection = camera_persp(state.camera, ar);
+ MAT4 view = get_view_matrix(&state.camera);
+
+ glClearColor(0.16f, 0.16f, 0.16f, 1.0f);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("pbr");
+ if (!shader) die("wha");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ shader_set_3fv(shader, "camera", state.camera.pos);
+ shader_set_3fv(shader, "color", v3f(0.04, 0.04, 0.04));
+ shader_set_1f(shader, "ao", 1.0f);
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ char str[512];
+ snprintf(str, 512, "lights[%d].position", i);
+ shader_set_3fv(shader, str, lights[i].position);
+ snprintf(str, 512, "lights[%d].color", i);
+ shader_set_3fv(shader, str, lights[i].color);
+ }
+ S32 rows = 7;
+ S32 cols = 7;
+ F32 offset = 3.0f;
+ for (S32 row = 0; row < rows; row++) {
+ shader_set_1f(shader, "metallic", (F32)row / (F32)rows);
+ for (S32 col = 0; col < cols; col++) {
+ shader_set_1f(shader, "roughness", clamp(0.05f, (F32)col / (F32)cols, 1.0f));
+ V3F pos = {(col - cols / 2.0f) * offset, (row - rows / 2.0f) * offset, 0.0f};
+ MAT4 model = mat4_make_translate(pos);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(cube);
+ }
+ }
+
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ MAT4 model = mat4_make_scale(v3f(0.5f, 0.5f, 0.5f));
+ model = mat4_translate(model, lights[i].position);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(cube);
+ }
+
+ glUseProgram(0);
+
+ shader = find_shader("background");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ mesh_draw(cube);
+ glUseProgram(0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("hdr");
+ if (!shader) die("failed to find hdr shader!");
+ glUseProgram(shader);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ shader_set_mat4fv(shader, "model", mat4_identity());
+ mesh_draw(quad);
+ glUseProgram(0);
+
+ glfwSwapBuffers(state.window);
+ }
+
+ return 0;
+}
diff --git a/pbr/ibl_irradiance_conversion/pbr.frag b/pbr/ibl_irradiance_conversion/pbr.frag
new file mode 100644
index 0000000..db9edea
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/pbr.frag
@@ -0,0 +1,105 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+out vec4 frag_color;
+
+uniform vec3 camera;
+
+uniform vec3 color;
+uniform float metallic;
+uniform float roughness;
+uniform float ao;
+
+struct light_t {
+ vec3 position;
+ vec3 color;
+};
+
+const int light_count = 4;
+uniform light_t lights[light_count];
+
+const float PI = 3.14159265358;
+
+vec3 fresnel_shlick(float costheta, vec3 f0)
+{
+ return f0 + (1.0 - f0) * pow(1.0 - costheta, 5.0);
+}
+
+float distribution_ggx(vec3 n, vec3 h, float roughness)
+{
+ float a = roughness * roughness;
+ float a2 = a * a;
+ float ndoth = max(dot(n, h), 0.0);
+ float ndoth2 = ndoth * ndoth;
+
+ float num = a2;
+ float denom = (ndoth2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+
+ return num / denom;
+}
+
+float geometry_shlick_ggx(float ndotv, float roughness)
+{
+ float r = roughness + 1.0;
+ float k = r * r / 8.0;
+
+ float num = ndotv;
+ float denom = ndotv * (1.0 - k) + k;
+
+ return num / denom;
+}
+
+float geometry_smith(vec3 n, vec3 v, vec3 l, float roughness)
+{
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+ float ggx2 = geometry_shlick_ggx(ndotv, roughness);
+ float ggx1 = geometry_shlick_ggx(ndotl, roughness);
+
+ return ggx1 * ggx2;
+}
+
+void main(void)
+{
+ vec3 n = vert.normal;
+ vec3 v = normalize(camera - vert.position);
+
+ vec3 lo = vec3(0.0);
+ for (int i = 0; i < light_count; i++) {
+ vec3 l = normalize(lights[i].position - vert.position);
+ vec3 h = normalize(v + l);
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+
+ float distance = length(lights[i].position - vert.position);
+ float attenuation = 1.0 / (distance * distance);
+
+ vec3 radiance = lights[i].color * attenuation;
+
+ vec3 f0 = vec3(0.04);
+ f0 = mix(f0, color, metallic);
+ vec3 f = fresnel_shlick(max(dot(h, v), 0.0), f0);
+
+ float ndf = distribution_ggx(n, h, roughness);
+ float g = geometry_smith(n, v, l, roughness);
+
+ vec3 numerator = ndf * g * f;
+ float denominator = 4.0 * ndotv * ndotl;
+ vec3 specular = numerator / max(denominator, 0.001);
+
+ vec3 ks = f;
+ vec3 kd = vec3(1.0) - ks;
+
+ kd *= 1.0 - metallic;
+
+ lo += (kd * color / PI + specular) * radiance * ndotl;
+ }
+
+ vec3 ambient = vec3(0.03) * color * ao;
+ frag_color = vec4(ambient + lo, 1.0);
+}
diff --git a/pbr/ibl_irradiance_conversion/pbr.vert b/pbr/ibl_irradiance_conversion/pbr.vert
new file mode 100644
index 0000000..e6e3800
--- /dev/null
+++ b/pbr/ibl_irradiance_conversion/pbr.vert
@@ -0,0 +1,21 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+
+out vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = projection * view * model * vec4(position, 1.0);
+
+ vert.position = vec3(model * vec4(position, 1.0));
+ vert.normal = normalize(mat3(transpose(inverse(model))) * normal);
+}
diff --git a/pbr/ibl_specular/background.frag b/pbr/ibl_specular/background.frag
new file mode 100644
index 0000000..e3891eb
--- /dev/null
+++ b/pbr/ibl_specular/background.frag
@@ -0,0 +1,17 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform samplerCube cubemap;
+
+void main(void)
+{
+ vec3 color = texture(cubemap, vert.position).rgb;
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / 2.2));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_specular/background.vert b/pbr/ibl_specular/background.vert
new file mode 100644
index 0000000..25b9778
--- /dev/null
+++ b/pbr/ibl_specular/background.vert
@@ -0,0 +1,19 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ vert.position = position;
+
+ mat4 rotate_view = mat4(mat3(view));
+ vec4 clip_position = projection * rotate_view * vec4(position, 1.0);
+ gl_Position = clip_position.xyww;
+}
diff --git a/pbr/ibl_specular/brdf.frag b/pbr/ibl_specular/brdf.frag
new file mode 100644
index 0000000..6e7cf5b
--- /dev/null
+++ b/pbr/ibl_specular/brdf.frag
@@ -0,0 +1,98 @@
+#version 330 core
+
+in vert_t {
+ vec2 tex_coords;
+} vert;
+
+out vec2 frag_color;
+
+const float PI = 3.14159265359;
+
+float radical_inverse_vdc(uint bits)
+{
+ bits = (bits << 16u) | (bits >> 16u);
+ bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+ bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+ bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+ bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+ return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+vec2 hammersley(uint i, uint n)
+{
+ return vec2(float(i) / float(n), radical_inverse_vdc(i));
+}
+
+vec3 importance_sample_ggx(vec2 xi, vec3 n, float roughness)
+{
+ float a = roughness * roughness;
+
+ float phi = 2.0 * PI * xi.x;
+ float cos_theta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
+ float sin_theta = sqrt(1.0 - cos_theta * cos_theta);
+
+ vec3 h;
+ h.x = cos(phi) * sin_theta;
+ h.y = sin(phi) * sin_theta;
+ h.z = cos_theta;
+
+ vec3 up = abs(n.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+ vec3 tangent = normalize(cross(up, n));
+ vec3 bitangent = cross(n, tangent);
+
+ vec3 samplevec = tangent * h.x + bitangent * h.y + n * h.z;
+ return normalize(samplevec);
+}
+
+float geometry_schlick_ggx(float ndotv, float roughness)
+{
+ float a = roughness;
+ float k = (a * a) / 2.0;
+ float nom = ndotv;
+ float denom = ndotv * (1.0 - k) + k;
+ return nom / denom;
+}
+
+float geometry_smith(vec3 n, vec3 v, vec3 l, float roughness)
+{
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+ float ggx1 = geometry_schlick_ggx(ndotl, roughness);
+ float ggx2 = geometry_schlick_ggx(ndotv, roughness);
+ return ggx1 * ggx2;
+}
+
+vec2 integrate_brdf(float ndotv, float roughness)
+{
+ vec3 v;
+ v.x = sqrt(1.0 - ndotv * ndotv);
+ v.y = 0.0;
+ v.z = ndotv;
+ float a = 0.0;
+ float b = 0.0;
+ vec3 n = vec3(0.0, 0.0, 1.0);
+ uint sample_count = 1024u;
+ for (uint i = 0u; i < sample_count; i++) {
+ vec2 xi = hammersley(i, sample_count);
+ vec3 h = importance_sample_ggx(xi, n, roughness);
+ vec3 l = normalize(2.0 * dot(v, h) * h - v);
+ float ndotl = max(l.z, 0.0);
+ float ndoth = max(h.z, 0.0);
+ float vdoth = max(dot(v, h), 0.0);
+ if (ndotl > 0.0) {
+ float g = geometry_smith(n, v, l, roughness);
+ float g_vis = (g * vdoth) / (ndoth * ndotv);
+ float fc = pow(1.0 - vdoth, 5.0);
+ a += (1.0 - fc) * g_vis;
+ b += fc * g_vis;
+ }
+ }
+ a /= float(sample_count);
+ b /= float(sample_count);
+ return vec2(a, b);
+}
+
+void main()
+{
+ frag_color = integrate_brdf(vert.tex_coords.x, vert.tex_coords.y);
+}
diff --git a/pbr/1.1.lighting/gbuffer.vert b/pbr/ibl_specular/brdf.vert
index c53439c..fd86ecc 100644
--- a/pbr/1.1.lighting/gbuffer.vert
+++ b/pbr/ibl_specular/brdf.vert
@@ -1,9 +1,13 @@
#version 330 core
-
layout(location = 0) in vec3 position;
-layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 tex_coords;
out vert_t {
vec2 tex_coords;
} vert;
+
+void main()
+{
+ vert.tex_coords = tex_coords;
+ gl_Position = vec4(position, 1.0);
+}
diff --git a/pbr/ibl_specular/build.sh b/pbr/ibl_specular/build.sh
new file mode 100755
index 0000000..5ed5193
--- /dev/null
+++ b/pbr/ibl_specular/build.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+. ../../config
+TARGET='ibl_specular'
+set -x
+gcc -o $TARGET $CFLAGS $INCLUDE $LFLAGS $TARGET.c $LIBS
diff --git a/pbr/ibl_specular/convolution.frag b/pbr/ibl_specular/convolution.frag
new file mode 100644
index 0000000..69ce8af
--- /dev/null
+++ b/pbr/ibl_specular/convolution.frag
@@ -0,0 +1,33 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform samplerCube cubemap;
+
+const float PI = 3.14159265359;
+
+void main(void)
+{
+ vec3 normal = normalize(vert.position);
+ vec3 up = vec3(0.0, 1.0, 0.0);
+ vec3 right = cross(up, normal);
+ up = cross(normal, right);
+
+ vec3 irradiance = vec3(0.0);
+ float delta = 0.025;
+ int nsamples = 0;
+ for (float phi = 0.0; phi < 2.0 * PI; phi += delta) {
+ for (float theta = 0.0; theta < 0.5 * PI; theta += delta) {
+ vec3 tangent = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
+ vec3 v = tangent.x * right + tangent.y * up + tangent.z * normal;
+ irradiance += texture(cubemap, v).rgb * cos(theta) * sin(theta);
+ nsamples++;
+ }
+ }
+ irradiance *= PI / nsamples;
+ frag_color = vec4(irradiance, 1.0);
+}
diff --git a/pbr/ibl_specular/convolution.vert b/pbr/ibl_specular/convolution.vert
new file mode 100644
index 0000000..8f8862e
--- /dev/null
+++ b/pbr/ibl_specular/convolution.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ gl_Position = projection * view * vec4(position, 1.0);
+
+ vert.position = position;
+}
diff --git a/pbr/ibl_specular/cubemap.frag b/pbr/ibl_specular/cubemap.frag
new file mode 100644
index 0000000..349347a
--- /dev/null
+++ b/pbr/ibl_specular/cubemap.frag
@@ -0,0 +1,25 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D map;
+
+const vec2 inv_atan = vec2(0.1591, 0.3183);
+vec2 sample_spherical_map(vec3 v)
+{
+ vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
+ uv *= inv_atan;
+ uv += 0.5;
+ return uv;
+}
+
+void main(void)
+{
+ vec2 uv = sample_spherical_map(normalize(vert.position));
+ vec3 color = texture(map, uv).rgb;
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_specular/cubemap.vert b/pbr/ibl_specular/cubemap.vert
new file mode 100644
index 0000000..8f8862e
--- /dev/null
+++ b/pbr/ibl_specular/cubemap.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ gl_Position = projection * view * vec4(position, 1.0);
+
+ vert.position = position;
+}
diff --git a/pbr/ibl_specular/hdr.frag b/pbr/ibl_specular/hdr.frag
new file mode 100644
index 0000000..01650ae
--- /dev/null
+++ b/pbr/ibl_specular/hdr.frag
@@ -0,0 +1,18 @@
+#version 330 core
+
+in vert_t {
+ vec2 tex_coords;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D colorbuffer;
+
+void main(void)
+{
+ const float gamma = 2.2;
+ vec3 color = vec3(texture(colorbuffer, vert.tex_coords));
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / gamma));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/ibl_specular/hdr.vert b/pbr/ibl_specular/hdr.vert
new file mode 100644
index 0000000..1b953d7
--- /dev/null
+++ b/pbr/ibl_specular/hdr.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 2) in vec2 tex_coords;
+
+out vert_t {
+ vec2 tex_coords;
+} vert;
+
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = model * vec4(position, 1.0);
+
+ vert.tex_coords = tex_coords;
+}
diff --git a/pbr/ibl_specular/ibl_specular b/pbr/ibl_specular/ibl_specular
new file mode 100755
index 0000000..e4466f5
--- /dev/null
+++ b/pbr/ibl_specular/ibl_specular
Binary files differ
diff --git a/pbr/ibl_specular/ibl_specular.c b/pbr/ibl_specular/ibl_specular.c
new file mode 100644
index 0000000..5692505
--- /dev/null
+++ b/pbr/ibl_specular/ibl_specular.c
@@ -0,0 +1,307 @@
+#include "GL/glew.h"
+#include "GLFW/glfw3.h"
+#include "common.h"
+
+int main(void)
+{
+ state_t state = init_state(1600, 800);
+
+ init_glfw(&state);
+ init_gl();
+ glDepthFunc(GL_LEQUAL);
+ glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+
+ /* meshes */
+ Mesh *cube = mesh_load_obj(state.arena, "../../data/models/cube.obj");
+ Mesh *sphere = mesh_load_obj(state.arena, "../../data/models/sphere.obj");
+ Mesh *quad = mesh_gen_quad(state.arena);
+
+ /* shaders */
+ add_shader("pbr.vert", "pbr.frag", 0);
+ add_shader("hdr.vert", "hdr.frag", 0);
+ add_shader("cubemap.vert", "cubemap.frag", 0);
+ add_shader("background.vert", "background.frag", 0);
+ add_shader("convolution.vert", "convolution.frag", 0);
+ add_shader("prefilter.vert", "prefilter.frag", 0);
+ add_shader("brdf.vert", "brdf.frag", 0);
+
+ U32 shader = find_shader("pbr");
+ glUseProgram(shader);
+ shader_set_1i(shader, "irradiance_map", 0);
+ shader_set_1i(shader, "prefilter_map", 1);
+ shader_set_1i(shader, "brdf_lut", 2);
+ glUseProgram(0);
+
+ light_t lights[4] = {
+ {{-10.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{-5.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 5.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 10.0f, 0.0f, 20.0f}, {300.0f, 300.0f, 300.0f}},
+ };
+
+ /* textures */
+ U32 textures[] = {
+ load_hdr_texture("../../data/textures/loigerwiesen.hdr"),
+ load_hdr_texture("../../data/textures/wells.hdr")
+ };
+ U32 hdr_texture = 1;
+
+ /* framebuffers */
+ U32 hdr_fbo, hdr_colorbuffer, hdr_rbo;
+ glGenFramebuffers(1, &hdr_fbo);
+ glGenTextures(1, &hdr_colorbuffer);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.width, state.height, 0, GL_RGBA, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glGenRenderbuffers(1, &hdr_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, hdr_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, state.width, state.height);
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hdr_colorbuffer, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, hdr_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 capture_fbo, capture_rbo;
+ glGenFramebuffers(1, &capture_fbo);
+ glGenRenderbuffers(1, &capture_rbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, capture_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 cubemap;
+ glGenTextures(1, &cubemap);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ for (U32 i = 0; i < 6; i++)
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 512, 512, 0, GL_RGB, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ MAT4 capture_projection = perspective(90.0f, 1.0f, 0.1f, 10.0f);
+ V3F capture_origin = {0};
+ MAT4 capture_views[6] = {
+ look_at(capture_origin, v3f( 1.0f, 0.0f, 0.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f(-1.0f, 0.0f, 0.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f( 0.0f, 1.0f, 0.0f), v3f(0.0f, 0.0f, 1.0f)),
+ look_at(capture_origin, v3f( 0.0f, -1.0f, 0.0f), v3f(0.0f, 0.0f, -1.0f)),
+ look_at(capture_origin, v3f( 0.0f, 0.0f, 1.0f), v3f(0.0f, -1.0f, 0.0f)),
+ look_at(capture_origin, v3f( 0.0f, 0.0f, -1.0f), v3f(0.0f, -1.0f, 0.0f)),
+ };
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ shader = find_shader("cubemap");
+ if (!shader) die("waht");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", capture_projection);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, textures[hdr_texture]);
+
+ glViewport(0, 0, 512, 512);
+ for (U32 i = 0; i < 6; i++) {
+ shader_set_mat4fv(shader, "view", capture_views[i]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mesh_draw(cube);
+ }
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+
+ U32 irradiance_map;
+ glGenTextures(1, &irradiance_map);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, irradiance_map);
+ for (U32 i = 0; i < 6; i++)
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, GL_RGB, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ shader = find_shader("convolution");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", capture_projection);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ glViewport(0, 0, 32, 32);
+ for (U32 i = 0; i < 6; i++) {
+ shader_set_mat4fv(shader, "view", capture_views[i]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradiance_map, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mesh_draw(cube);
+ }
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 prefilter_map;
+ glGenTextures(1, &prefilter_map);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, prefilter_map);
+ for (U32 i = 0; i < 6; i++)
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 128, 128, 0, GL_RGB, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+
+ shader = find_shader("prefilter");
+ glUseProgram(shader);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ shader_set_mat4fv(shader, "projection", capture_projection);
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ U32 mipmap_levels = 5;
+ for (U32 mip = 0; mip < mipmap_levels; mip++) {
+ U32 width = 128 * pow(0.5, mip);
+ U32 height = width;
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
+ glViewport(0, 0, width, height);
+ float roughness = (float)mip / (float)(mipmap_levels - 1);
+ shader_set_1f(shader, "roughness", roughness);
+ for (U32 i = 0; i < 6; i++) {
+ shader_set_mat4fv(shader, "view", capture_views[i]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilter_map, mip);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mesh_draw(cube);
+ }
+ }
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ U32 brdf_lut_texture;
+ glGenTextures(1, &brdf_lut_texture);
+ glBindTexture(GL_TEXTURE_2D, brdf_lut_texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdf_lut_texture, 0);
+ glViewport(0, 0, 512, 512);
+ shader = find_shader("brdf");
+ assert(shader);
+ glUseProgram(shader);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mesh_draw(quad);
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ glViewport(0, 0, state.width, state.height);
+
+ F32 time = 0;
+
+ while (!glfwWindowShouldClose(state.window)) {
+ handle_glfw_events(state.window, &state.input);
+ F32 dt = lock_framerate(60);
+ fps_info(dt, 10);
+
+ /* update */
+ update_camera_first_person(&state.camera, &state.input, dt, 2.0f);
+ F32 limit = 4.0f;
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++)
+ lights[i].position.y = sinf(time + i) * limit;
+ time += dt;
+
+ /* render */
+ F32 ar = (F32)state.width / (F32)state.height;
+ MAT4 projection = camera_persp(state.camera, ar);
+ MAT4 view = get_view_matrix(&state.camera);
+
+ glClearColor(0.16f, 0.16f, 0.16f, 1.0f);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("pbr");
+ glUseProgram(shader);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, irradiance_map);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, prefilter_map);
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, brdf_lut_texture);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ shader_set_3fv(shader, "camera", state.camera.pos);
+ shader_set_3fv(shader, "color", v3f(0.04, 0.04, 0.04));
+ shader_set_1f(shader, "ao", 1.0f);
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ char str[512];
+ snprintf(str, 512, "lights[%d].position", i);
+ shader_set_3fv(shader, str, lights[i].position);
+ snprintf(str, 512, "lights[%d].color", i);
+ shader_set_3fv(shader, str, lights[i].color);
+ }
+ S32 rows = 7;
+ S32 cols = 7;
+ F32 offset = 3.0f;
+ for (S32 row = 0; row < rows; row++) {
+ shader_set_1f(shader, "metallic", (F32)row / (F32)rows);
+ for (S32 col = 0; col < cols; col++) {
+ shader_set_1f(shader, "roughness", clamp(0.05f, (F32)col / (F32)cols, 1.0f));
+ V3F pos = {(col - cols / 2.0f) * offset, (row - rows / 2.0f) * offset, 0.0f};
+ MAT4 model = mat4_make_translate(pos);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(cube);
+ }
+ }
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ MAT4 model = mat4_make_scale(v3f(0.5f, 0.5f, 0.5f));
+ model = mat4_translate(model, lights[i].position);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(sphere);
+ }
+ glUseProgram(0);
+
+ shader = find_shader("background");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
+ mesh_draw(cube);
+ glUseProgram(0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("hdr");
+ if (!shader) die("!");
+ glUseProgram(shader);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ shader_set_mat4fv(shader, "model", mat4_identity());
+ mesh_draw(quad);
+ glUseProgram(0);
+
+ glfwSwapBuffers(state.window);
+ }
+
+ return 0;
+}
diff --git a/pbr/ibl_specular/pbr.frag b/pbr/ibl_specular/pbr.frag
new file mode 100644
index 0000000..3ed2018
--- /dev/null
+++ b/pbr/ibl_specular/pbr.frag
@@ -0,0 +1,131 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+out vec4 frag_color;
+
+uniform vec3 camera;
+
+uniform vec3 color;
+uniform float metallic;
+uniform float roughness;
+uniform float ao;
+
+uniform samplerCube irradiance_map;
+uniform samplerCube prefilter_map;
+uniform sampler2D brdf_lut;
+
+struct light_t {
+ vec3 position;
+ vec3 color;
+};
+
+const int light_count = 4;
+uniform light_t lights[light_count];
+
+const float PI = 3.14159265358;
+
+vec3 fresnel_schlick(float costheta, vec3 f0)
+{
+ return f0 + (1.0 - f0) * pow(1.0 - costheta, 5.0);
+}
+
+vec3 fresnel_schlick_roughness(float costheta, vec3 f0, float roughness)
+{
+ return f0 + (max(vec3(1.0 - roughness), f0) - f0) * pow(clamp(1.0 - costheta, 0.0, 1.0), 5.0);
+}
+
+float distribution_ggx(vec3 n, vec3 h, float roughness)
+{
+ float a = roughness * roughness;
+ float a2 = a * a;
+ float ndoth = max(dot(n, h), 0.0);
+ float ndoth2 = ndoth * ndoth;
+
+ float num = a2;
+ float denom = (ndoth2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+
+ return num / denom;
+}
+
+float geometry_schlick_ggx(float ndotv, float roughness)
+{
+ float r = roughness + 1.0;
+ float k = r * r / 8.0;
+
+ float num = ndotv;
+ float denom = ndotv * (1.0 - k) + k;
+
+ return num / denom;
+}
+
+float geometry_smith(vec3 n, vec3 v, vec3 l, float roughness)
+{
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+ float ggx2 = geometry_schlick_ggx(ndotv, roughness);
+ float ggx1 = geometry_schlick_ggx(ndotl, roughness);
+
+ return ggx1 * ggx2;
+}
+
+void main(void)
+{
+ vec3 n = vert.normal;
+ vec3 v = normalize(camera - vert.position);
+ vec3 r = reflect(-v, n);
+
+ vec3 f0 = vec3(0.04);
+ f0 = mix(f0, color, metallic);
+
+ vec3 lo = vec3(0.0);
+ for (int i = 0; i < light_count; i++) {
+ vec3 l = normalize(lights[i].position - vert.position);
+ vec3 h = normalize(v + l);
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+
+ float distance = length(lights[i].position - vert.position);
+ float attenuation = 1.0 / (distance * distance);
+
+ vec3 radiance = lights[i].color * attenuation;
+
+ vec3 f = fresnel_schlick(max(dot(h, v), 0.0), f0);
+
+ float ndf = distribution_ggx(n, h, roughness);
+ float g = geometry_smith(n, v, l, roughness);
+
+ vec3 numerator = ndf * g * f;
+ float denominator = 4.0 * ndotv * ndotl;
+ vec3 specular = numerator / max(denominator, 0.001);
+
+ vec3 ks = f;
+ vec3 kd = vec3(1.0) - ks;
+
+ kd *= 1.0 - metallic;
+
+ lo += (kd * color / PI + specular) * radiance * ndotl;
+ }
+
+ vec3 f = fresnel_schlick_roughness(max(dot(n, v), 0.0), f0, roughness);
+
+ vec3 ks = f;
+ vec3 kd = 1.0 - ks;
+ kd *= 1.0 - metallic;
+
+ vec3 irradiance = texture(irradiance_map, n).rgb;
+ vec3 diffuse = irradiance * color;
+
+ float max_reflection_lod = 4.0;
+ vec3 prefiltered = textureLod(prefilter_map, r, roughness * max_reflection_lod).rgb;
+ vec2 brdf = texture(brdf_lut, vec2(max(dot(n, v), 0.0), roughness)).rg;
+ vec3 specular = prefiltered * (f * brdf.x + brdf.y);
+
+ vec3 ambient = (kd * diffuse + specular)* ao;
+
+ frag_color = vec4(ambient + lo, 1.0);
+}
diff --git a/pbr/ibl_specular/pbr.vert b/pbr/ibl_specular/pbr.vert
new file mode 100644
index 0000000..e6e3800
--- /dev/null
+++ b/pbr/ibl_specular/pbr.vert
@@ -0,0 +1,21 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+
+out vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = projection * view * model * vec4(position, 1.0);
+
+ vert.position = vec3(model * vec4(position, 1.0));
+ vert.normal = normalize(mat3(transpose(inverse(model))) * normal);
+}
diff --git a/pbr/ibl_specular/prefilter.frag b/pbr/ibl_specular/prefilter.frag
new file mode 100644
index 0000000..5359c58
--- /dev/null
+++ b/pbr/ibl_specular/prefilter.frag
@@ -0,0 +1,91 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+} vert;
+
+out vec4 frag_color;
+
+uniform samplerCube cubemap;
+uniform float roughness;
+
+const float PI = 3.14159265359;
+
+float distribution_ggx(vec3 n, vec3 h, float roughness)
+{
+ float a = roughness * roughness;
+ float a2 = a * a;
+ float ndoth = max(dot(n, h), 0.0);
+ float ndoth2 = ndoth * ndoth;
+ float nom = a2;
+ float denom = (ndoth2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+ return nom / denom;
+}
+
+float radical_inverse_vdc(uint bits)
+{
+ bits = (bits << 16u) | (bits >> 16u);
+ bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+ bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+ bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+ bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+ return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+vec2 hammersley(uint i, uint n)
+{
+ return vec2(float(i) / float(n), radical_inverse_vdc(i));
+}
+
+vec3 importance_sample_ggx(vec2 xi, vec3 n, float roughness)
+{
+ float a = roughness * roughness;
+
+ float phi = 2.0 * PI * xi.x;
+ float cos_theta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
+ float sin_theta = sqrt(1.0 - cos_theta * cos_theta);
+
+ vec3 h;
+ h.x = cos(phi) * sin_theta;
+ h.y = sin(phi) * sin_theta;
+ h.z = cos_theta;
+
+ vec3 up = abs(n.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+ vec3 tangent = normalize(cross(up, n));
+ vec3 bitangent = cross(n, tangent);
+
+ vec3 samplevec = tangent * h.x + bitangent * h.y + n * h.z;
+ return normalize(samplevec);
+}
+
+void main(void)
+{
+ vec3 n = normalize(vert.position);
+ vec3 r = n;
+ vec3 v = r;
+
+ uint count = 1024u;
+ float total = 0.0;
+ vec3 prefiltered = vec3(0.0);
+ for (uint i = 0u; i < count; i++) {
+ vec2 xi = hammersley(i, count);
+ vec3 h = importance_sample_ggx(xi, n, roughness);
+ vec3 l = normalize(2.0 * dot(v, h) * h - v);
+ float ndotl = max(dot(n, l), 0.0);
+ if (ndotl > 0.0) {
+ float d = distribution_ggx(n, h, roughness);
+ float ndoth = max(dot(n, h), 0.0);
+ float hdotv = max(dot(h, v), 0.0);
+ float pdf = d * ndoth / (4.0 * hdotv) + 0.0001;
+ float resolution = 512.0;
+ float sa_texel = 4.0 * PI / (6.0 * resolution * resolution);
+ float sa_sample = 1.0 / (float(count) * pdf + 0.0001);
+ float mip_level = roughness == 0.0 ? 0.0 : 0.5 * log2(sa_sample / sa_texel);
+ prefiltered += textureLod(cubemap, l, mip_level).rgb * ndotl;
+ total += ndotl;
+ }
+ }
+ prefiltered /= total;
+ frag_color = vec4(prefiltered, 1.0);
+}
diff --git a/pbr/ibl_specular/prefilter.vert b/pbr/ibl_specular/prefilter.vert
new file mode 100644
index 0000000..8f8862e
--- /dev/null
+++ b/pbr/ibl_specular/prefilter.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+
+out vert_t {
+ vec3 position;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+
+void main(void)
+{
+ gl_Position = projection * view * vec4(position, 1.0);
+
+ vert.position = position;
+}
diff --git a/pbr/1.1.lighting/build.sh b/pbr/lighting/build.sh
index 2cd71b7..2cd71b7 100755
--- a/pbr/1.1.lighting/build.sh
+++ b/pbr/lighting/build.sh
diff --git a/pbr/lighting/default.frag b/pbr/lighting/default.frag
new file mode 100644
index 0000000..9351a1b
--- /dev/null
+++ b/pbr/lighting/default.frag
@@ -0,0 +1,15 @@
+#version 330 core
+
+in vert_t {
+ vec2 tex_coords;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D colorbuffer;
+
+void main(void)
+{
+ vec3 color = vec3(texture(colorbuffer, vert.tex_coords));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/lighting/default.vert b/pbr/lighting/default.vert
new file mode 100644
index 0000000..1b953d7
--- /dev/null
+++ b/pbr/lighting/default.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 2) in vec2 tex_coords;
+
+out vert_t {
+ vec2 tex_coords;
+} vert;
+
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = model * vec4(position, 1.0);
+
+ vert.tex_coords = tex_coords;
+}
diff --git a/pbr/lighting/hdr.frag b/pbr/lighting/hdr.frag
new file mode 100644
index 0000000..01650ae
--- /dev/null
+++ b/pbr/lighting/hdr.frag
@@ -0,0 +1,18 @@
+#version 330 core
+
+in vert_t {
+ vec2 tex_coords;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D colorbuffer;
+
+void main(void)
+{
+ const float gamma = 2.2;
+ vec3 color = vec3(texture(colorbuffer, vert.tex_coords));
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / gamma));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/lighting/hdr.vert b/pbr/lighting/hdr.vert
new file mode 100644
index 0000000..1b953d7
--- /dev/null
+++ b/pbr/lighting/hdr.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 2) in vec2 tex_coords;
+
+out vert_t {
+ vec2 tex_coords;
+} vert;
+
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = model * vec4(position, 1.0);
+
+ vert.tex_coords = tex_coords;
+}
diff --git a/pbr/lighting/lighting b/pbr/lighting/lighting
new file mode 100755
index 0000000..a68bd2b
--- /dev/null
+++ b/pbr/lighting/lighting
Binary files differ
diff --git a/pbr/lighting/lighting.c b/pbr/lighting/lighting.c
new file mode 100644
index 0000000..d2a941d
--- /dev/null
+++ b/pbr/lighting/lighting.c
@@ -0,0 +1,157 @@
+#include "GL/glew.h"
+#include "GLFW/glfw3.h"
+#include "common.h"
+
+int main(void)
+{
+ state_t state = init_state(1600, 800);
+
+ init_glfw(&state);
+ init_gl();
+
+ /* meshes */
+ Mesh *sphere = mesh_load_obj(state.arena, "../../data/models/sphere.obj");
+ Mesh *quad = mesh_gen_quad(state.arena);
+
+ /* shaders */
+ add_shader("default.vert", "default.frag", 0);
+ add_shader("pbr.vert", "pbr.frag", 0);
+ add_shader("hdr.vert", "hdr.frag", 0);
+
+ light_t lights[4] = {
+ {{-6.0f, 0.0f, 7.0f}, {300.0f, 300.0f, 300.0f}},
+ {{-4.0f, 0.0f, 6.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 1.0f, 0.0f, 5.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 3.0f, 0.0f, 8.0f}, {300.0f, 300.0f, 300.0f}},
+ };
+
+ /* framebuffers */
+ U32 hdr_fbo, hdr_colorbuffer, hdr_rbo;
+ glGenFramebuffers(1, &hdr_fbo);
+ glGenTextures(1, &hdr_colorbuffer);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.width, state.height, 0, GL_RGBA, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glGenRenderbuffers(1, &hdr_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, hdr_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, state.width, state.height);
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hdr_colorbuffer, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, hdr_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ F32 time = 0;
+
+ S32 show_gamma = 0;
+
+ while (!glfwWindowShouldClose(state.window)) {
+ handle_glfw_events(state.window, &state.input);
+ F32 dt = lock_framerate(60);
+ fps_info(dt, 10);
+
+ /* update */
+ update_camera_first_person(&state.camera, &state.input, dt, 2.0f);
+ F32 limit = 4.0f;
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++)
+ lights[i].position.y = sinf(time + i) * limit;
+ time += dt;
+
+ if (key_first_press(state.input.jump))
+ show_gamma = 1 - show_gamma;
+
+ /* render */
+ F32 ar = 0;
+ if (show_gamma) {
+ ar = (F32)state.width / 2.0f / (F32)state.height;
+ } else {
+ ar = (F32)state.width / (F32)state.height;
+ }
+ MAT4 projection = camera_persp(state.camera, ar);
+ MAT4 view = get_view_matrix(&state.camera);
+
+ glClearColor(0.16f, 0.16f, 0.16f, 1.0f);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ U32 shader = find_shader("pbr");
+ if (!shader) die("wha");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ shader_set_3fv(shader, "camera", state.camera.pos);
+ shader_set_3fv(shader, "color", v3f(0.04, 0.04, 0.04));
+ shader_set_1f(shader, "ao", 1.0f);
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ char str[512];
+ snprintf(str, 512, "lights[%d].position", i);
+ shader_set_3fv(shader, str, lights[i].position);
+ snprintf(str, 512, "lights[%d].color", i);
+ shader_set_3fv(shader, str, lights[i].color);
+ }
+ S32 rows = 7;
+ S32 cols = 7;
+ F32 offset = 3.0f;
+ for (S32 row = 0; row < rows; row++) {
+ shader_set_1f(shader, "metallic", (F32)row / (F32)rows);
+ for (S32 col = 0; col < cols; col++) {
+ shader_set_1f(shader, "roughness", clamp(0.05f, (F32)col / (F32)cols, 1.0f));
+ V3F pos = {(col - cols / 2.0f) * offset, (row - rows / 2.0f) * offset, 0.0f};
+ MAT4 model = mat4_make_translate(pos);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(sphere);
+ }
+ }
+
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ MAT4 model = mat4_make_scale(v3f(0.5f, 0.5f, 0.5f));
+ model = mat4_translate(model, lights[i].position);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(sphere);
+ }
+
+ glUseProgram(0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ if (show_gamma) {
+ shader = find_shader("default");
+ if (!shader) die("!");
+ glUseProgram(shader);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ MAT4 model = mat4_make_scale(v3f(0.5f, 1.0f, 1.0f));
+ model = mat4_translate(model, v3f(-0.5f, 0.0f, 0.0f));
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(quad);
+ glUseProgram(0);
+
+ shader = find_shader("hdr");
+ if (!shader) die("!");
+ glUseProgram(shader);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ model = mat4_make_scale(v3f(0.5f, 1.0f, 1.0f));
+ model = mat4_translate(model, v3f(0.5f, 0.0f, 0.0f));
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(quad);
+ glUseProgram(0);
+ } else {
+ shader = find_shader("hdr");
+ if (!shader) die("!");
+ glUseProgram(shader);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ shader_set_mat4fv(shader, "model", mat4_identity());
+ mesh_draw(quad);
+ glUseProgram(0);
+ }
+
+ glfwSwapBuffers(state.window);
+ }
+
+ return 0;
+}
diff --git a/pbr/lighting/pbr.frag b/pbr/lighting/pbr.frag
new file mode 100644
index 0000000..db9edea
--- /dev/null
+++ b/pbr/lighting/pbr.frag
@@ -0,0 +1,105 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+out vec4 frag_color;
+
+uniform vec3 camera;
+
+uniform vec3 color;
+uniform float metallic;
+uniform float roughness;
+uniform float ao;
+
+struct light_t {
+ vec3 position;
+ vec3 color;
+};
+
+const int light_count = 4;
+uniform light_t lights[light_count];
+
+const float PI = 3.14159265358;
+
+vec3 fresnel_shlick(float costheta, vec3 f0)
+{
+ return f0 + (1.0 - f0) * pow(1.0 - costheta, 5.0);
+}
+
+float distribution_ggx(vec3 n, vec3 h, float roughness)
+{
+ float a = roughness * roughness;
+ float a2 = a * a;
+ float ndoth = max(dot(n, h), 0.0);
+ float ndoth2 = ndoth * ndoth;
+
+ float num = a2;
+ float denom = (ndoth2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+
+ return num / denom;
+}
+
+float geometry_shlick_ggx(float ndotv, float roughness)
+{
+ float r = roughness + 1.0;
+ float k = r * r / 8.0;
+
+ float num = ndotv;
+ float denom = ndotv * (1.0 - k) + k;
+
+ return num / denom;
+}
+
+float geometry_smith(vec3 n, vec3 v, vec3 l, float roughness)
+{
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+ float ggx2 = geometry_shlick_ggx(ndotv, roughness);
+ float ggx1 = geometry_shlick_ggx(ndotl, roughness);
+
+ return ggx1 * ggx2;
+}
+
+void main(void)
+{
+ vec3 n = vert.normal;
+ vec3 v = normalize(camera - vert.position);
+
+ vec3 lo = vec3(0.0);
+ for (int i = 0; i < light_count; i++) {
+ vec3 l = normalize(lights[i].position - vert.position);
+ vec3 h = normalize(v + l);
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+
+ float distance = length(lights[i].position - vert.position);
+ float attenuation = 1.0 / (distance * distance);
+
+ vec3 radiance = lights[i].color * attenuation;
+
+ vec3 f0 = vec3(0.04);
+ f0 = mix(f0, color, metallic);
+ vec3 f = fresnel_shlick(max(dot(h, v), 0.0), f0);
+
+ float ndf = distribution_ggx(n, h, roughness);
+ float g = geometry_smith(n, v, l, roughness);
+
+ vec3 numerator = ndf * g * f;
+ float denominator = 4.0 * ndotv * ndotl;
+ vec3 specular = numerator / max(denominator, 0.001);
+
+ vec3 ks = f;
+ vec3 kd = vec3(1.0) - ks;
+
+ kd *= 1.0 - metallic;
+
+ lo += (kd * color / PI + specular) * radiance * ndotl;
+ }
+
+ vec3 ambient = vec3(0.03) * color * ao;
+ frag_color = vec4(ambient + lo, 1.0);
+}
diff --git a/pbr/lighting/pbr.vert b/pbr/lighting/pbr.vert
new file mode 100644
index 0000000..e6e3800
--- /dev/null
+++ b/pbr/lighting/pbr.vert
@@ -0,0 +1,21 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+
+out vert_t {
+ vec3 position;
+ vec3 normal;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = projection * view * model * vec4(position, 1.0);
+
+ vert.position = vec3(model * vec4(position, 1.0));
+ vert.normal = normalize(mat3(transpose(inverse(model))) * normal);
+}
diff --git a/pbr/textured/build.sh b/pbr/textured/build.sh
new file mode 100755
index 0000000..5c5427f
--- /dev/null
+++ b/pbr/textured/build.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+. ../../config
+TARGET='textured'
+set -x
+gcc -o $TARGET $CFLAGS $INCLUDE $LFLAGS $TARGET.c $LIBS
diff --git a/pbr/textured/hdr.frag b/pbr/textured/hdr.frag
new file mode 100644
index 0000000..01650ae
--- /dev/null
+++ b/pbr/textured/hdr.frag
@@ -0,0 +1,18 @@
+#version 330 core
+
+in vert_t {
+ vec2 tex_coords;
+} vert;
+
+out vec4 frag_color;
+
+uniform sampler2D colorbuffer;
+
+void main(void)
+{
+ const float gamma = 2.2;
+ vec3 color = vec3(texture(colorbuffer, vert.tex_coords));
+ color /= color + vec3(1.0);
+ color = pow(color, vec3(1.0 / gamma));
+ frag_color = vec4(color, 1.0);
+}
diff --git a/pbr/textured/hdr.vert b/pbr/textured/hdr.vert
new file mode 100644
index 0000000..1b953d7
--- /dev/null
+++ b/pbr/textured/hdr.vert
@@ -0,0 +1,17 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 2) in vec2 tex_coords;
+
+out vert_t {
+ vec2 tex_coords;
+} vert;
+
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = model * vec4(position, 1.0);
+
+ vert.tex_coords = tex_coords;
+}
diff --git a/pbr/textured/pbr.frag b/pbr/textured/pbr.frag
new file mode 100644
index 0000000..f0aaeca
--- /dev/null
+++ b/pbr/textured/pbr.frag
@@ -0,0 +1,126 @@
+#version 330 core
+
+in vert_t {
+ vec3 position;
+ vec3 normal;
+ vec2 tex_coords;
+} vert;
+
+out vec4 frag_color;
+
+uniform vec3 camera;
+
+uniform sampler2D color_map;
+uniform sampler2D normal_map;
+uniform sampler2D metallic_map;
+uniform sampler2D roughness_map;
+uniform sampler2D ao_map;
+
+struct light_t {
+ vec3 position;
+ vec3 color;
+};
+
+const int light_count = 4;
+uniform light_t lights[light_count];
+
+const float PI = 3.14159265358;
+
+vec3 get_normal_from_map()
+{
+ vec3 tangent = texture(normal_map, vert.tex_coords).xyz * 2.0 - 1.0;
+ vec3 q1 = dFdx(vert.position);
+ vec3 q2 = dFdy(vert.position);
+ vec2 st1 = dFdx(vert.tex_coords);
+ vec2 st2 = dFdy(vert.tex_coords);
+ vec3 n = normalize(vert.normal);
+ vec3 t = normalize(q1*st2.t - q2*st1.t);
+ vec3 b = -normalize(cross(n, t));
+ mat3 tbn = mat3(t, b, n);
+ return normalize(tbn * tangent);
+}
+
+vec3 fresnel_shlick(float costheta, vec3 f0)
+{
+ return f0 + (1.0 - f0) * pow(1.0 - costheta, 5.0);
+}
+
+float distribution_ggx(vec3 n, vec3 h, float roughness)
+{
+ float a = roughness * roughness;
+ float a2 = a * a;
+ float ndoth = max(dot(n, h), 0.0);
+ float ndoth2 = ndoth * ndoth;
+
+ float num = a2;
+ float denom = (ndoth2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+
+ return num / denom;
+}
+
+float geometry_shlick_ggx(float ndotv, float roughness)
+{
+ float r = roughness + 1.0;
+ float k = r * r / 8.0;
+
+ float num = ndotv;
+ float denom = ndotv * (1.0 - k) + k;
+
+ return num / denom;
+}
+
+float geometry_smith(vec3 n, vec3 v, vec3 l, float roughness)
+{
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+ float ggx2 = geometry_shlick_ggx(ndotv, roughness);
+ float ggx1 = geometry_shlick_ggx(ndotl, roughness);
+
+ return ggx1 * ggx2;
+}
+
+void main(void)
+{
+ vec3 color = pow(texture(color_map, vert.tex_coords).rgb, vec3(2.2));
+ float metallic = texture(metallic_map, vert.tex_coords).r;
+ float roughness = texture(roughness_map, vert.tex_coords).r;
+ float ao = texture(ao_map, vert.tex_coords).r;
+
+ vec3 n = get_normal_from_map();
+ vec3 v = normalize(camera - vert.position);
+
+ vec3 lo = vec3(0.0);
+ for (int i = 0; i < light_count; i++) {
+ vec3 l = normalize(lights[i].position - vert.position);
+ vec3 h = normalize(v + l);
+ float ndotv = max(dot(n, v), 0.0);
+ float ndotl = max(dot(n, l), 0.0);
+
+ float distance = length(lights[i].position - vert.position);
+ float attenuation = 1.0 / (distance * distance);
+
+ vec3 radiance = lights[i].color * attenuation;
+
+ vec3 f0 = vec3(0.04);
+ f0 = mix(f0, color, metallic);
+ vec3 f = fresnel_shlick(max(dot(h, v), 0.0), f0);
+
+ float ndf = distribution_ggx(n, h, roughness);
+ float g = geometry_smith(n, v, l, roughness);
+
+ vec3 numerator = ndf * g * f;
+ float denominator = 4.0 * ndotv * ndotl;
+ vec3 specular = numerator / max(denominator, 0.001);
+
+ vec3 ks = f;
+ vec3 kd = vec3(1.0) - ks;
+
+ kd *= 1.0 - metallic;
+
+ lo += (kd * color / PI + specular) * radiance * ndotl;
+ }
+
+ vec3 ambient = vec3(0.03) * color * ao;
+ frag_color = vec4(ambient + lo, 1.0);
+}
diff --git a/pbr/textured/pbr.vert b/pbr/textured/pbr.vert
new file mode 100644
index 0000000..0ff1bdb
--- /dev/null
+++ b/pbr/textured/pbr.vert
@@ -0,0 +1,24 @@
+#version 330 core
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+layout(location = 2) in vec2 tex_coords;
+
+out vert_t {
+ vec3 position;
+ vec3 normal;
+ vec2 tex_coords;
+} vert;
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform mat4 model;
+
+void main(void)
+{
+ gl_Position = projection * view * model * vec4(position, 1.0);
+
+ vert.position = vec3(model * vec4(position, 1.0));
+ vert.normal = normalize(mat3(transpose(inverse(model))) * normal);
+ vert.tex_coords = tex_coords;
+}
diff --git a/pbr/textured/textured b/pbr/textured/textured
new file mode 100755
index 0000000..e339406
--- /dev/null
+++ b/pbr/textured/textured
Binary files differ
diff --git a/pbr/textured/textured.c b/pbr/textured/textured.c
new file mode 100644
index 0000000..e8ed096
--- /dev/null
+++ b/pbr/textured/textured.c
@@ -0,0 +1,151 @@
+#include "GL/glew.h"
+#include "GLFW/glfw3.h"
+#include "common.h"
+
+int main(void)
+{
+ state_t state = init_state(1600, 800);
+
+ init_glfw(&state);
+ init_gl();
+
+ /* meshes */
+ Mesh *sphere = mesh_load_obj(state.arena, "../../data/models/sphere.obj");
+ Mesh *cube = mesh_load_obj(state.arena, "../../data/models/cube.obj");
+ Mesh *quad = mesh_gen_quad(state.arena);
+
+ /* shaders */
+ add_shader("pbr.vert", "pbr.frag", 0);
+ add_shader("hdr.vert", "hdr.frag", 0);
+
+ U32 shader = find_shader("pbr");
+ if (!shader)
+ die("not again");
+ glUseProgram(shader);
+ shader_set_1i(shader, "color_map", 0);
+ shader_set_1i(shader, "normal_map", 1);
+ shader_set_1i(shader, "metallic_map", 2);
+ shader_set_1i(shader, "roughness_map", 3);
+ shader_set_1i(shader, "ao_map", 4);
+ glUseProgram(0);
+
+ light_t lights[4] = {
+ {{-6.0f, 0.0f, 7.0f}, {300.0f, 300.0f, 300.0f}},
+ {{-4.0f, 0.0f, 6.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 1.0f, 0.0f, 5.0f}, {300.0f, 300.0f, 300.0f}},
+ {{ 3.0f, 0.0f, 8.0f}, {300.0f, 300.0f, 300.0f}},
+ };
+
+ /* textures */
+ U32 color_map = load_texture("../../data/textures/pbr/albedo.png");
+ U32 normal_map = load_texture("../../data/textures/pbr/normal.png");
+ U32 metallic_map = load_texture("../../data/textures/pbr/metallic.png");
+ U32 roughness_map = load_texture("../../data/textures/pbr/roughness.png");
+ U32 ao_map = load_texture("../../data/textures/pbr/ao.png");
+
+ /* framebuffers */
+ U32 hdr_fbo, hdr_colorbuffer, hdr_rbo;
+ glGenFramebuffers(1, &hdr_fbo);
+ glGenTextures(1, &hdr_colorbuffer);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, state.width, state.height, 0, GL_RGBA, GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glGenRenderbuffers(1, &hdr_rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, hdr_rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, state.width, state.height);
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hdr_colorbuffer, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, hdr_rbo);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ die("failed to create framebuffer");
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ F32 time = 0;
+
+ while (!glfwWindowShouldClose(state.window)) {
+ handle_glfw_events(state.window, &state.input);
+ F32 dt = lock_framerate(60);
+ fps_info(dt, 10);
+
+ /* update */
+ update_camera_first_person(&state.camera, &state.input, dt, 2.0f);
+ F32 limit = 4.0f;
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++)
+ lights[i].position.y = sinf(2.0f * time + i) * limit;
+ time += dt;
+
+ /* render */
+ F32 ar = (F32)state.width / (F32)state.height;
+ MAT4 projection = camera_persp(state.camera, ar);
+ MAT4 view = get_view_matrix(&state.camera);
+
+ glClearColor(0.16f, 0.16f, 0.16f, 1.0f);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("pbr");
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, color_map);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, normal_map);
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, metallic_map);
+ glActiveTexture(GL_TEXTURE3);
+ glBindTexture(GL_TEXTURE_2D, roughness_map);
+ glActiveTexture(GL_TEXTURE4);
+ glBindTexture(GL_TEXTURE_2D, ao_map);
+
+ if (!shader) die("wha");
+ glUseProgram(shader);
+ shader_set_mat4fv(shader, "projection", projection);
+ shader_set_mat4fv(shader, "view", view);
+ shader_set_3fv(shader, "camera", state.camera.pos);
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ char str[512];
+ snprintf(str, 512, "lights[%d].position", i);
+ shader_set_3fv(shader, str, lights[i].position);
+ snprintf(str, 512, "lights[%d].color", i);
+ shader_set_3fv(shader, str, lights[i].color);
+ }
+ S32 rows = 7;
+ S32 cols = 7;
+ F32 offset = 3.0f;
+ for (S32 row = 0; row < rows; row++) {
+ for (S32 col = 0; col < cols; col++) {
+ V3F pos = {(col - cols / 2.0f) * offset, (row - rows / 2.0f) * offset, 0.0f};
+ MAT4 model = mat4_make_translate(pos);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(cube);
+ }
+ }
+
+ for (U32 i = 0; i < (sizeof(lights) / sizeof(lights[0])); i++) {
+ MAT4 model = mat4_make_scale(v3f(0.5f, 0.5f, 0.5f));
+ model = mat4_translate(model, lights[i].position);
+ shader_set_mat4fv(shader, "model", model);
+ mesh_draw(sphere);
+ }
+
+ glUseProgram(0);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ shader = find_shader("hdr");
+ if (!shader) die("!");
+ glUseProgram(shader);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, hdr_colorbuffer);
+ shader_set_mat4fv(shader, "model", mat4_identity());
+ mesh_draw(quad);
+ glUseProgram(0);
+
+ glfwSwapBuffers(state.window);
+ }
+
+ return 0;
+}