aboutsummaryrefslogtreecommitdiff
path: root/src/util/ibl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/ibl.c')
-rw-r--r--src/util/ibl.c328
1 files changed, 328 insertions, 0 deletions
diff --git a/src/util/ibl.c b/src/util/ibl.c
new file mode 100644
index 0000000..5a79990
--- /dev/null
+++ b/src/util/ibl.c
@@ -0,0 +1,328 @@
1#include <gfx/util/ibl.h>
2
3#include <gfx/core.h>
4#include <gfx/util/geometry.h>
5#include <gfx/util/shader.h>
6#include <math/mat4.h>
7
8#include <assert.h>
9#include <stdlib.h>
10
11typedef struct IBL {
12 Geometry* quad;
13 ShaderProgram* brdf_integration_map_shader;
14 ShaderProgram* irradiance_map_shader;
15 ShaderProgram* prefiltered_environment_map_shader;
16 Texture* brdf_integration_map;
17 FrameBuffer* framebuffer;
18 mat4 rotations[6];
19} IBL;
20
21static const CubemapFace faces[6] = {
22 CubemapFacePosX, // Right.
23 CubemapFaceNegX, // Left.
24 CubemapFacePosY, // Up.
25 CubemapFaceNegY, // Down.
26 CubemapFacePosZ, // Back.
27 CubemapFaceNegZ, // Front.
28};
29
30static const float flips[6] = {
31 -1.0f, // Right.
32 -1.0f, // Left.
33 +1.0f, // Up.
34 +1.0f, // Down.
35 -1.0f, // Back.
36 -1.0f, // Front.
37};
38
39IBL* gfx_make_ibl(GfxCore* gfxcore) {
40 assert(gfxcore);
41
42 IBL* ibl = calloc(1, sizeof(IBL));
43 if (!ibl) {
44 return 0;
45 }
46
47 if (!(ibl->quad = gfx_make_quad_11(gfxcore))) {
48 goto cleanup;
49 }
50
51 // We only need the BRDF integration once since we are caching the map, but
52 // compiling the shader up front may lead to fewer surprises. Not that the
53 // shader is fully compiled up front anyway, since the driver will typically
54 // defer full compilation to the first draw call.
55 if (!(ibl->brdf_integration_map_shader =
56 gfx_make_brdf_integration_map_shader(gfxcore))) {
57 goto cleanup;
58 }
59
60 if (!(ibl->irradiance_map_shader = gfx_make_irradiance_map_shader(gfxcore))) {
61 goto cleanup;
62 }
63
64 if (!(ibl->prefiltered_environment_map_shader =
65 gfx_make_prefiltered_environment_map_shader(gfxcore))) {
66 goto cleanup;
67 }
68
69 // Create an empty framebuffer for now. Will attach the colour buffer later
70 // as we render the faces of the cube.
71 if (!(ibl->framebuffer = gfx_make_framebuffer(
72 gfxcore,
73 &(FrameBufferDesc){
74 .colour =
75 (FrameBufferAttachment){.type = FrameBufferNoAttachment},
76 .depth = (FrameBufferAttachment){
77 .type = FrameBufferNoAttachment}}))) {
78 goto cleanup;
79 }
80
81 // TODO: Debug the camera rotations. Irradiance debug output should appear
82 // just like the input cubemap.
83
84 // Right.
85 ibl->rotations[0] = mat4_lookat(
86 /*position=*/vec3_make(0, 0, 0),
87 /*target=*/vec3_make(1, 0, 0),
88 /*up=*/vec3_make(0, 1, 0));
89 // Left.
90 ibl->rotations[1] = mat4_lookat(
91 /*position=*/vec3_make(0, 0, 0),
92 /*target=*/vec3_make(-1, 0, 0),
93 /*up=*/vec3_make(0, 1, 0));
94 // Up.
95 ibl->rotations[2] = mat4_lookat(
96 /*position=*/vec3_make(0, 0, 0),
97 /*target=*/vec3_make(0, 1, 0),
98 /*up=*/vec3_make(0, 0, 1));
99 // Down.
100 ibl->rotations[3] = mat4_lookat(
101 /*position=*/vec3_make(0, 0, 0),
102 /*target=*/vec3_make(0, -1, 0),
103 /*up=*/vec3_make(0, 0, -1));
104 // Back.
105 ibl->rotations[4] = mat4_lookat(
106 /*position=*/vec3_make(0, 0, 0),
107 /*target=*/vec3_make(0, 0, 1),
108 /*up=*/vec3_make(0, 1, 0));
109 // Front.
110 ibl->rotations[5] = mat4_lookat(
111 /*position=*/vec3_make(0, 0, 0),
112 /*target=*/vec3_make(0, 0, -1),
113 /*up=*/vec3_make(0, 1, 0));
114
115 return ibl;
116
117cleanup:
118 gfx_destroy_ibl(gfxcore, &ibl);
119 return 0;
120}
121
122void gfx_destroy_ibl(GfxCore* gfxcore, IBL** ibl) {
123 if (!ibl) {
124 return;
125 }
126 if ((*ibl)->quad) {
127 gfx_destroy_geometry(gfxcore, &(*ibl)->quad);
128 }
129 if ((*ibl)->brdf_integration_map_shader) {
130 gfx_destroy_shader_program(gfxcore, &(*ibl)->brdf_integration_map_shader);
131 }
132 if ((*ibl)->irradiance_map_shader) {
133 gfx_destroy_shader_program(gfxcore, &(*ibl)->irradiance_map_shader);
134 }
135 if ((*ibl)->prefiltered_environment_map_shader) {
136 gfx_destroy_shader_program(
137 gfxcore, &(*ibl)->prefiltered_environment_map_shader);
138 }
139 if ((*ibl)->brdf_integration_map) {
140 gfx_destroy_texture(gfxcore, &(*ibl)->brdf_integration_map);
141 }
142 if ((*ibl)->framebuffer) {
143 gfx_destroy_framebuffer(gfxcore, &(*ibl)->framebuffer);
144 }
145 free(*ibl);
146 *ibl = 0;
147}
148
149Texture* gfx_make_brdf_integration_map(
150 IBL* ibl, GfxCore* gfxcore, int width, int height) {
151 assert(ibl);
152 assert(gfxcore);
153
154 if (ibl->brdf_integration_map) {
155 return ibl->brdf_integration_map;
156 }
157
158 bool success = false;
159
160 if (!(ibl->brdf_integration_map = gfx_make_texture(
161 gfxcore, &(TextureDesc){
162 .width = width,
163 .height = height,
164 .depth = 1,
165 .dimension = Texture2D,
166 .format = TextureRG16F,
167 .filtering = LinearFiltering,
168 .wrap = ClampToEdge,
169 .mipmaps = false}))) {
170 goto cleanup;
171 }
172
173 gfx_activate_framebuffer(ibl->framebuffer);
174 gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, width, height);
175 gfx_activate_shader_program(ibl->brdf_integration_map_shader);
176 if (!gfx_framebuffer_attach_colour(
177 ibl->framebuffer, &(FrameBufferAttachment){
178 .type = FrameBufferTexture,
179 .texture.texture = ibl->brdf_integration_map,
180 .texture.mip_level = 0})) {
181 goto cleanup;
182 }
183 gfx_render_geometry(ibl->quad);
184
185 success = true;
186
187cleanup:
188 gfx_deactivate_shader_program(ibl->brdf_integration_map_shader);
189 gfx_deactivate_framebuffer(ibl->framebuffer);
190 if (!success && ibl->brdf_integration_map) {
191 gfx_destroy_texture(gfxcore, &ibl->brdf_integration_map);
192 return 0;
193 } else {
194 return ibl->brdf_integration_map;
195 }
196}
197
198Texture* gfx_make_irradiance_map(
199 IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width,
200 int height) {
201 assert(ibl);
202 assert(gfxcore);
203 assert(environment_map);
204
205 bool success = false;
206
207 Texture* irradiance_map = 0;
208
209 // TODO: Could define colour-renderable texture formats separately to make
210 // framebuffer creation less error-prone. Or, at the very least, validate the
211 // choice at runtime.
212 //
213 // Make sure to use a float colour format to avoid [0,1] clamping when the
214 // irradiance values are computed!
215 if (!(irradiance_map = gfx_make_texture(
216 gfxcore, &(TextureDesc){
217 .width = width,
218 .height = height,
219 .depth = 1,
220 .dimension = TextureCubeMap,
221 .format = TextureR11G11B10F,
222 .filtering = LinearFiltering,
223 .mipmaps = false}))) {
224 goto cleanup;
225 }
226
227 gfx_activate_framebuffer(ibl->framebuffer);
228 gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, width, height);
229 gfx_activate_shader_program(ibl->irradiance_map_shader);
230 gfx_set_texture_uniform(ibl->irradiance_map_shader, "Sky", environment_map);
231 for (int i = 0; i < 6; ++i) {
232 if (!gfx_framebuffer_attach_colour(
233 ibl->framebuffer, &(FrameBufferAttachment){
234 .type = FrameBufferCubemapTexture,
235 .cubemap.face = faces[i],
236 .cubemap.texture = irradiance_map})) {
237 goto cleanup;
238 }
239 gfx_set_float_uniform(ibl->irradiance_map_shader, "Flip", flips[i]);
240 gfx_set_mat4_uniform(
241 ibl->irradiance_map_shader, "CameraRotation", &ibl->rotations[i]);
242 gfx_apply_uniforms(ibl->irradiance_map_shader);
243 gfx_render_geometry(ibl->quad);
244 }
245
246 success = true;
247
248cleanup:
249 gfx_deactivate_shader_program(ibl->irradiance_map_shader);
250 gfx_deactivate_framebuffer(ibl->framebuffer);
251 if (!success && irradiance_map) {
252 gfx_destroy_texture(gfxcore, &irradiance_map);
253 return 0;
254 } else {
255 return irradiance_map;
256 }
257}
258
259Texture* gfx_make_prefiltered_environment_map(
260 IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width,
261 int height, int* max_mip_level) {
262 assert(ibl);
263 assert(gfxcore);
264 assert(environment_map);
265 assert(max_mip_level);
266
267 bool success = false;
268
269 Texture* prefiltered_env_map = 0;
270
271 if (!(prefiltered_env_map = gfx_make_texture(
272 gfxcore, &(TextureDesc){
273 .width = width,
274 .height = height,
275 .depth = 1,
276 .dimension = TextureCubeMap,
277 .format = TextureR11G11B10F,
278 .filtering = LinearFiltering,
279 .mipmaps = true}))) {
280 goto cleanup;
281 }
282
283 gfx_activate_framebuffer(ibl->framebuffer);
284 gfx_activate_shader_program(ibl->prefiltered_environment_map_shader);
285 gfx_set_texture_uniform(
286 ibl->prefiltered_environment_map_shader, "Sky", environment_map);
287 const int max_mip = (int)(rlog2(min(width, height)));
288 for (int mip = 0; mip <= max_mip; ++mip) {
289 const int mip_width = width >> mip;
290 const int mip_height = height >> mip;
291 const float roughness = (float)mip / (float)(max_mip);
292 gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, mip_width, mip_height);
293 gfx_set_float_uniform(
294 ibl->prefiltered_environment_map_shader, "Roughness", roughness);
295
296 for (int i = 0; i < 6; ++i) {
297 if (!gfx_framebuffer_attach_colour(
298 ibl->framebuffer, &(FrameBufferAttachment){
299 .type = FrameBufferCubemapTexture,
300 .cubemap.face = faces[i],
301 .cubemap.mip_level = mip,
302 .cubemap.texture = prefiltered_env_map})) {
303 goto cleanup;
304 }
305 gfx_set_float_uniform(
306 ibl->prefiltered_environment_map_shader, "Flip", flips[i]);
307 gfx_set_mat4_uniform(
308 ibl->prefiltered_environment_map_shader, "CameraRotation",
309 &ibl->rotations[i]);
310 gfx_apply_uniforms(ibl->prefiltered_environment_map_shader);
311 gfx_render_geometry(ibl->quad);
312 }
313 }
314
315 *max_mip_level = max_mip;
316
317 success = true;
318
319cleanup:
320 gfx_deactivate_shader_program(ibl->prefiltered_environment_map_shader);
321 gfx_deactivate_framebuffer(ibl->framebuffer);
322 if (!success && prefiltered_env_map) {
323 gfx_destroy_texture(gfxcore, &prefiltered_env_map);
324 return 0;
325 } else {
326 return prefiltered_env_map;
327 }
328}