diff options
Diffstat (limited to 'src/util/ibl.c')
-rw-r--r-- | src/util/ibl.c | 328 |
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 | |||
11 | typedef 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 | |||
21 | static const CubemapFace faces[6] = { | ||
22 | CubemapFacePosX, // Right. | ||
23 | CubemapFaceNegX, // Left. | ||
24 | CubemapFacePosY, // Up. | ||
25 | CubemapFaceNegY, // Down. | ||
26 | CubemapFacePosZ, // Back. | ||
27 | CubemapFaceNegZ, // Front. | ||
28 | }; | ||
29 | |||
30 | static 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 | |||
39 | IBL* 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 | |||
117 | cleanup: | ||
118 | gfx_destroy_ibl(gfxcore, &ibl); | ||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | void 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 | |||
149 | Texture* 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 | |||
187 | cleanup: | ||
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 | |||
198 | Texture* 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 | |||
248 | cleanup: | ||
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 | |||
259 | Texture* 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 | |||
319 | cleanup: | ||
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 | } | ||