├── .gitmodules
├── README.md
├── example
├── CMakeLists.txt
├── example.c
└── gazebo.obj
├── example_images
├── debug_interpolation.png
├── example.png
├── gazebo_light.png
├── gazebo_shadows_reflections.png
└── gazebo_skybox.png
└── lightmapper.h
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "example/glfw"]
2 | path = example/glfw
3 | url = https://github.com/glfw/glfw.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lightmapper
2 | lightmapper.h is a C/C++ single-file library for lightmap baking by using your existing OpenGL renderer.
3 |
4 | To paste the implementation into your project, insert the following lines:
5 | ```
6 | #define LIGHTMAPPER_IMPLEMENTATION
7 | #include "lightmapper.h"
8 | ```
9 |
10 | While the simple example application demonstrates the baking of ambient occlusion only, the library can also be used to precompute global illumination for static scene geometry by doing multiple bounces.
11 |
12 | Any light shapes and emissive surfaces are supported as long as you draw them with their corresponding emissive light colors.
13 |
14 | You may want to use [thekla_atlas](https://github.com/Thekla/thekla_atlas) / [xatlas](https://github.com/jpcy/xatlas) or [uvatlas](https://github.com/microsoft/UVAtlas) to prepare your geometry for lightmapping.
15 |
16 | # Example application
17 | 
18 | The provided [example application](https://github.com/ands/lightmapper/blob/master/example/example.c) should build on Windows/Linux/MacOSX.
19 |
20 | Linux dependencies for glfw: xorg-dev libgl1-mesa-dev
21 | ```
22 | git clone --recursive https://github.com/ands/lightmapper.git
23 | cd lightmapper/example
24 | cmake .
25 | make
26 | ./example
27 | ```
28 |
29 | # Example usage
30 | ```
31 | lm_context *ctx = lmCreate(
32 | 64, // hemicube rendering resolution/quality
33 | 0.001f, 100.0f, // zNear, zFar
34 | 1.0f, 1.0f, 1.0f, // sky/clear color
35 | 2, 0.01f // hierarchical selective interpolation for speedup (passes, threshold)
36 | 0.0f); // modifier for camera-to-surface distance for hemisphere rendering.
37 | // tweak this to trade-off between interpolated vertex normal quality and other artifacts (see declaration).
38 | if (!ctx)
39 | {
40 | printf("Could not initialize lightmapper.\n");
41 | exit(-1);
42 | }
43 |
44 | for (int b = 0; b < bounces; b++)
45 | {
46 | // render all geometry to their lightmaps
47 | for (int i = 0; i < meshes; i++)
48 | {
49 | memset(mesh[i].lightmap, 0, sizeof(float) * mesh[i].lightmapWidth * mesh[i].lightmapHeight * 3); // clear lightmap to black
50 | lmSetTargetLightmap(ctx, mesh[i].lightmap, mesh[i].lightmapWidth, mesh[i].lightmapHeight, 3);
51 |
52 | lmSetGeometry(ctx, mesh[i].modelMatrix,
53 | LM_FLOAT, (uint8_t*)mesh[i].vertices + positionOffset, vertexSize,
54 | LM_NONE , NULL, 0, // optional vertex normals for smooth surfaces
55 | LM_FLOAT, (uint8_t*)mesh[i].vertices + lightmapUVOffset, vertexSize,
56 | mesh[i].indexCount, LM_UNSIGNED_SHORT, mesh[i].indices);
57 |
58 | int vp[4];
59 | mat4 view, proj;
60 | while (lmBegin(&ctx, vp, &view[0][0], &proj[0][0]))
61 | {
62 | // don't glClear on your own here!
63 | glViewport(vp[0], vp[1], vp[2], vp[3]);
64 | drawScene(view, proj);
65 | printf("\r%6.2f%%", lmProgress(ctx) * 100.0f); // don't actually call printf that often ;) it's slow.
66 | lmEnd(&ctx);
67 | }
68 | }
69 |
70 | // postprocess and upload all lightmaps to the GPU now to use them for indirect lighting during the next bounce.
71 | for (int i = 0; i < meshes; i++)
72 | {
73 | // you can also call other lmImage* here to postprocess the lightmap.
74 | lmImageDilate(mesh[i].lightmap, temp, mesh[i].lightmapWidth, mesh[i].lightmapHeight, 3);
75 | lmImageDilate(temp, mesh[i].lightmap, mesh[i].lightmapWidth, mesh[i].lightmapHeight, 3);
76 |
77 | glBindTexture(GL_TEXTURE_2D, mesh[i].lightmapHandle);
78 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, mesh[i].lightmapWidth, mesh[i].lightmapHeight, 0, GL_RGB, GL_FLOAT, data);
79 | }
80 | }
81 |
82 | lmDestroy(&ctx);
83 |
84 | // gamma correct and save lightmaps to disk
85 | for (int i = 0; i < meshes; i++)
86 | {
87 | lmImagePower(mesh[i].lightmap, mesh[i].lightmapWidth, mesh[i].lightmapHeight, 3, 1.0f / 2.2f);
88 | lmImageSaveTGAf(mesh[i].lightmapFilename, mesh[i].lightmap, mesh[i].lightmapWidth, mesh[i].lightmapHeight, 3);
89 | }
90 | ```
91 |
92 | # Quality improvement
93 | To improve the lightmapping quality on closed meshes it is recommended to disable backface culling and to write `(gl_FrontFacing ? 1.0 : 0.0)` into the alpha channel during scene rendering to mark valid and invalid geometry (look at [example.c](https://github.com/ands/lightmapper/blob/master/example/example.c) for more details). The lightmapper will use this information to discard lightmap texel results with too many invalid samples. These texels can then be filled in by calls to `lmImageDilate` during postprocessing.
94 |
95 | # Selective hierarchical interpolation for speedup
96 | The lightmapper can do several passes traversing the geometry to render to the lightmap.
97 | If interpolation passes are turned on, only every (1 << interpolationPasses) texel gets rendered in the first pass.
98 | Consecutive passes either render or interpolate the values in between based on the interpolationThreshold value.
99 | If the neighbours deviate too much or don't exist yet, the texel gets also rendered, otherwise it is simply interpolated between the neighbours.
100 | If interpolationPasses or interpolationThreshold are set too high, lighting variations can be missed.
101 | If they are very low, very many lightmap texels get rendered, which is very expensive compared to interpolating them.
102 | On the example above this technique gives about a ~3.3x speedup without degrading the quality.
103 | Larger lightmaps can even get a ~10x speedup depending on the scene.
104 | This image shows which texels get rendered (red) and which get interpolated (green) in the above example:
105 |
106 | 
107 |
108 | This technique is also described by Hugo Elias over [here](http://web.archive.org/web/20160311085440/http://freespace.virgin.net/hugo.elias/radiosity/radiosity.htm).
109 |
110 | # Example media
111 | The following video shows several lighting effects. The static/stationary and indirect lighting were precomputed with lightmapper.h:
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | 
120 | 
121 | 
122 |
123 | # Thanks
124 | - To Ignacio Castaño and Jonathan Blow for blogging about their lightmapping system for The Witness.
125 | - To Hugo Elias who documented his radiosity light mapper and acceleration technique a long time ago.
126 | - To Sean Barrett for his library design philosophy.
127 | - To Nyamyam Ltd. for letting me use their engine and assets during my master's thesis for which I started my work on this library.
128 | - To Apoorva Joshi for trying to find a graphics driver performance anomaly.
129 | - To Teh_Bucket for this public domain gazebo model on OpenGameArt.org.
130 | - To everybody else that I forgot :)
131 |
--------------------------------------------------------------------------------
/example/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required (VERSION 2.8)
2 |
3 | project(example)
4 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build the GLFW example programs")
5 | add_subdirectory(glfw)
6 | include_directories(${PROJECT_SOURCE_DIR})
7 | include_directories("glfw/deps") # for glad
8 | include_directories("glfw/include")
9 | add_executable(${PROJECT_NAME} example.c glfw/deps/glad.c)
10 | add_definitions( "-D _CRT_SECURE_NO_WARNINGS -std=c99" )
11 | target_link_libraries(${PROJECT_NAME} glfw ${GLFW_LIBRARIES})
12 |
--------------------------------------------------------------------------------
/example/example.c:
--------------------------------------------------------------------------------
1 | #define _USE_MATH_DEFINES
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "glad/glad.h"
7 | #include "GLFW/glfw3.h"
8 |
9 | #define LIGHTMAPPER_IMPLEMENTATION
10 | #define LM_DEBUG_INTERPOLATION
11 | #include "../lightmapper.h"
12 |
13 | #ifndef M_PI // even with _USE_MATH_DEFINES not always available
14 | #define M_PI 3.14159265358979323846
15 | #endif
16 |
17 | typedef struct {
18 | float p[3];
19 | float t[2];
20 | } vertex_t;
21 |
22 | typedef struct
23 | {
24 | GLuint program;
25 | GLint u_lightmap;
26 | GLint u_projection;
27 | GLint u_view;
28 |
29 | GLuint lightmap;
30 | int w, h;
31 |
32 | GLuint vao, vbo, ibo;
33 | vertex_t *vertices;
34 | unsigned short *indices;
35 | unsigned int vertexCount, indexCount;
36 | } scene_t;
37 |
38 | static int initScene(scene_t *scene);
39 | static void drawScene(scene_t *scene, float *view, float *projection);
40 | static void destroyScene(scene_t *scene);
41 |
42 | static int bake(scene_t *scene)
43 | {
44 | lm_context *ctx = lmCreate(
45 | 64, // hemisphere resolution (power of two, max=512)
46 | 0.001f, 100.0f, // zNear, zFar of hemisphere cameras
47 | 1.0f, 1.0f, 1.0f, // background color (white for ambient occlusion)
48 | 2, 0.01f, // lightmap interpolation threshold (small differences are interpolated rather than sampled)
49 | // check debug_interpolation.tga for an overview of sampled (red) vs interpolated (green) pixels.
50 | 0.0f); // modifier for camera-to-surface distance for hemisphere rendering.
51 | // tweak this to trade-off between interpolated normals quality and other artifacts (see declaration).
52 |
53 | if (!ctx)
54 | {
55 | fprintf(stderr, "Error: Could not initialize lightmapper.\n");
56 | return 0;
57 | }
58 |
59 | int w = scene->w, h = scene->h;
60 | float *data = calloc(w * h * 4, sizeof(float));
61 | lmSetTargetLightmap(ctx, data, w, h, 4);
62 |
63 | lmSetGeometry(ctx, NULL, // no transformation in this example
64 | LM_FLOAT, (unsigned char*)scene->vertices + offsetof(vertex_t, p), sizeof(vertex_t),
65 | LM_NONE , NULL , 0 , // no interpolated normals in this example
66 | LM_FLOAT, (unsigned char*)scene->vertices + offsetof(vertex_t, t), sizeof(vertex_t),
67 | scene->indexCount, LM_UNSIGNED_SHORT, scene->indices);
68 |
69 | int vp[4];
70 | float view[16], projection[16];
71 | double lastUpdateTime = 0.0;
72 | while (lmBegin(ctx, vp, view, projection))
73 | {
74 | // render to lightmapper framebuffer
75 | glViewport(vp[0], vp[1], vp[2], vp[3]);
76 | drawScene(scene, view, projection);
77 |
78 | // display progress every second (printf is expensive)
79 | double time = glfwGetTime();
80 | if (time - lastUpdateTime > 1.0)
81 | {
82 | lastUpdateTime = time;
83 | printf("\r%6.2f%%", lmProgress(ctx) * 100.0f);
84 | fflush(stdout);
85 | }
86 |
87 | lmEnd(ctx);
88 | }
89 | printf("\rFinished baking %d triangles.\n", scene->indexCount / 3);
90 |
91 | lmDestroy(ctx);
92 |
93 | // postprocess texture
94 | float *temp = calloc(w * h * 4, sizeof(float));
95 | for (int i = 0; i < 16; i++)
96 | {
97 | lmImageDilate(data, temp, w, h, 4);
98 | lmImageDilate(temp, data, w, h, 4);
99 | }
100 | lmImageSmooth(data, temp, w, h, 4);
101 | lmImageDilate(temp, data, w, h, 4);
102 | lmImagePower(data, w, h, 4, 1.0f / 2.2f, 0x7); // gamma correct color channels
103 | free(temp);
104 |
105 | // save result to a file
106 | if (lmImageSaveTGAf("result.tga", data, w, h, 4, 1.0f))
107 | printf("Saved result.tga\n");
108 |
109 | // upload result
110 | glBindTexture(GL_TEXTURE_2D, scene->lightmap);
111 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_FLOAT, data);
112 | free(data);
113 |
114 | return 1;
115 | }
116 |
117 | static void error_callback(int error, const char *description)
118 | {
119 | fprintf(stderr, "Error: %s\n", description);
120 | }
121 |
122 | static void fpsCameraViewMatrix(GLFWwindow *window, float *view);
123 | static void perspectiveMatrix(float *out, float fovy, float aspect, float zNear, float zFar);
124 |
125 | static void mainLoop(GLFWwindow *window, scene_t *scene)
126 | {
127 | glfwPollEvents();
128 | if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)
129 | bake(scene);
130 |
131 | int w, h;
132 | glfwGetFramebufferSize(window, &w, &h);
133 | glViewport(0, 0, w, h);
134 |
135 | // camera for glfw window
136 | float view[16], projection[16];
137 | fpsCameraViewMatrix(window, view);
138 | perspectiveMatrix(projection, 45.0f, (float)w / (float)h, 0.01f, 100.0f);
139 |
140 | // draw to screen with a blueish sky
141 | glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
142 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
143 | drawScene(scene, view, projection);
144 |
145 | glfwSwapBuffers(window);
146 | }
147 |
148 | int main(int argc, char* argv[])
149 | {
150 | glfwSetErrorCallback(error_callback);
151 |
152 | if (!glfwInit())
153 | {
154 | fprintf(stderr, "Could not initialize GLFW.\n");
155 | return EXIT_FAILURE;
156 | }
157 |
158 | glfwWindowHint(GLFW_RED_BITS, 8);
159 | glfwWindowHint(GLFW_GREEN_BITS, 8);
160 | glfwWindowHint(GLFW_BLUE_BITS, 8);
161 | glfwWindowHint(GLFW_ALPHA_BITS, 8);
162 | glfwWindowHint(GLFW_DEPTH_BITS, 32);
163 | glfwWindowHint(GLFW_STENCIL_BITS, GLFW_DONT_CARE);
164 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
165 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
166 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
167 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
168 | glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
169 | glfwWindowHint(GLFW_SAMPLES, 4);
170 |
171 | GLFWwindow *window = glfwCreateWindow(1024, 768, "Lightmapping Example", NULL, NULL);
172 | if (!window)
173 | {
174 | fprintf(stderr, "Could not create window.\n");
175 | glfwTerminate();
176 | return EXIT_FAILURE;
177 | }
178 |
179 | glfwMakeContextCurrent(window);
180 | gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
181 | glfwSwapInterval(1);
182 |
183 | scene_t scene = {0};
184 | if (!initScene(&scene))
185 | {
186 | fprintf(stderr, "Could not initialize scene.\n");
187 | glfwDestroyWindow(window);
188 | glfwTerminate();
189 | return EXIT_FAILURE;
190 | }
191 |
192 | printf("Ambient Occlusion Baking Example.\n");
193 | printf("Use your mouse and the W, A, S, D, E, Q keys to navigate.\n");
194 | printf("Press SPACE to start baking one light bounce!\n");
195 | printf("This will take a few seconds and bake a lightmap illuminated by:\n");
196 | printf("1. The mesh itself (initially black)\n");
197 | printf("2. A white sky (1.0f, 1.0f, 1.0f)\n");
198 |
199 | while (!glfwWindowShouldClose(window))
200 | {
201 | mainLoop(window, &scene);
202 | }
203 |
204 | destroyScene(&scene);
205 | glfwDestroyWindow(window);
206 | glfwTerminate();
207 | return EXIT_SUCCESS;
208 | }
209 |
210 | // helpers ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
211 | static int loadSimpleObjFile(const char *filename, vertex_t **vertices, unsigned int *vertexCount, unsigned short **indices, unsigned int *indexCount);
212 | static GLuint loadProgram(const char *vp, const char *fp, const char **attributes, int attributeCount);
213 |
214 | static int initScene(scene_t *scene)
215 | {
216 | // load mesh
217 | if (!loadSimpleObjFile("gazebo.obj", &scene->vertices, &scene->vertexCount, &scene->indices, &scene->indexCount))
218 | {
219 | fprintf(stderr, "Error loading obj file\n");
220 | return 0;
221 | }
222 |
223 | glGenVertexArrays(1, &scene->vao);
224 | glBindVertexArray(scene->vao);
225 |
226 | glGenBuffers(1, &scene->vbo);
227 | glBindBuffer(GL_ARRAY_BUFFER, scene->vbo);
228 | glBufferData(GL_ARRAY_BUFFER, scene->vertexCount * sizeof(vertex_t), scene->vertices, GL_STATIC_DRAW);
229 |
230 | glGenBuffers(1, &scene->ibo);
231 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, scene->ibo);
232 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, scene->indexCount * sizeof(unsigned short), scene->indices, GL_STATIC_DRAW);
233 |
234 | glEnableVertexAttribArray(0);
235 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, p));
236 | glEnableVertexAttribArray(1);
237 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, t));
238 |
239 | // create lightmap texture
240 | scene->w = 654;
241 | scene->h = 654;
242 | glGenTextures(1, &scene->lightmap);
243 | glBindTexture(GL_TEXTURE_2D, scene->lightmap);
244 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
245 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
246 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
247 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
248 | unsigned char emissive[] = { 0, 0, 0, 255 };
249 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, emissive);
250 |
251 | // load shader
252 | const char *vp =
253 | "#version 150 core\n"
254 | "in vec3 a_position;\n"
255 | "in vec2 a_texcoord;\n"
256 | "uniform mat4 u_view;\n"
257 | "uniform mat4 u_projection;\n"
258 | "out vec2 v_texcoord;\n"
259 |
260 | "void main()\n"
261 | "{\n"
262 | "gl_Position = u_projection * (u_view * vec4(a_position, 1.0));\n"
263 | "v_texcoord = a_texcoord;\n"
264 | "}\n";
265 |
266 | const char *fp =
267 | "#version 150 core\n"
268 | "in vec2 v_texcoord;\n"
269 | "uniform sampler2D u_lightmap;\n"
270 | "out vec4 o_color;\n"
271 |
272 | "void main()\n"
273 | "{\n"
274 | "o_color = vec4(texture(u_lightmap, v_texcoord).rgb, gl_FrontFacing ? 1.0 : 0.0);\n"
275 | "}\n";
276 |
277 | const char *attribs[] =
278 | {
279 | "a_position",
280 | "a_texcoord"
281 | };
282 |
283 | scene->program = loadProgram(vp, fp, attribs, 2);
284 | if (!scene->program)
285 | {
286 | fprintf(stderr, "Error loading shader\n");
287 | return 0;
288 | }
289 | scene->u_view = glGetUniformLocation(scene->program, "u_view");
290 | scene->u_projection = glGetUniformLocation(scene->program, "u_projection");
291 | scene->u_lightmap = glGetUniformLocation(scene->program, "u_lightmap");
292 |
293 | return 1;
294 | }
295 |
296 | static void drawScene(scene_t *scene, float *view, float *projection)
297 | {
298 | glEnable(GL_DEPTH_TEST);
299 |
300 | glUseProgram(scene->program);
301 | glUniform1i(scene->u_lightmap, 0);
302 | glUniformMatrix4fv(scene->u_projection, 1, GL_FALSE, projection);
303 | glUniformMatrix4fv(scene->u_view, 1, GL_FALSE, view);
304 |
305 | glBindTexture(GL_TEXTURE_2D, scene->lightmap);
306 |
307 | glBindVertexArray(scene->vao);
308 | glDrawElements(GL_TRIANGLES, scene->indexCount, GL_UNSIGNED_SHORT, 0);
309 | }
310 |
311 | static void destroyScene(scene_t *scene)
312 | {
313 | free(scene->vertices);
314 | free(scene->indices);
315 | glDeleteVertexArrays(1, &scene->vao);
316 | glDeleteBuffers(1, &scene->vbo);
317 | glDeleteBuffers(1, &scene->ibo);
318 | glDeleteTextures(1, &scene->lightmap);
319 | glDeleteProgram(scene->program);
320 | }
321 |
322 | static int loadSimpleObjFile(const char *filename, vertex_t **vertices, unsigned int *vertexCount, unsigned short **indices, unsigned int *indexCount)
323 | {
324 | FILE *file = fopen(filename, "rt");
325 | if (!file)
326 | return 0;
327 | char line[1024];
328 |
329 | // first pass
330 | unsigned int np = 0, nn = 0, nt = 0, nf = 0;
331 | while (!feof(file))
332 | {
333 | fgets(line, 1024, file);
334 | if (line[0] == '#') continue;
335 | if (line[0] == 'v')
336 | {
337 | if (line[1] == ' ') { np++; continue; }
338 | if (line[1] == 'n') { nn++; continue; }
339 | if (line[1] == 't') { nt++; continue; }
340 | assert(!"unknown vertex attribute");
341 | }
342 | if (line[0] == 'f') { nf++; continue; }
343 | assert(!"unknown identifier");
344 | }
345 | assert(np && np == nn && np == nt && nf); // only supports obj files without separately indexed vertex attributes
346 |
347 | // allocate memory
348 | *vertexCount = np;
349 | *vertices = calloc(np, sizeof(vertex_t));
350 | *indexCount = nf * 3;
351 | *indices = calloc(nf * 3, sizeof(unsigned short));
352 |
353 | // second pass
354 | fseek(file, 0, SEEK_SET);
355 | unsigned int cp = 0, cn = 0, ct = 0, cf = 0;
356 | while (!feof(file))
357 | {
358 | fgets(line, 1024, file);
359 | if (line[0] == '#') continue;
360 | if (line[0] == 'v')
361 | {
362 | if (line[1] == ' ') { float *p = (*vertices)[cp++].p; char *e1, *e2; p[0] = (float)strtod(line + 2, &e1); p[1] = (float)strtod(e1, &e2); p[2] = (float)strtod(e2, 0); continue; }
363 | if (line[1] == 'n') { /*float *n = (*vertices)[cn++].n; char *e1, *e2; n[0] = (float)strtod(line + 3, &e1); n[1] = (float)strtod(e1, &e2); n[2] = (float)strtod(e2, 0);*/ continue; } // no normals needed
364 | if (line[1] == 't') { float *t = (*vertices)[ct++].t; char *e1; t[0] = (float)strtod(line + 3, &e1); t[1] = (float)strtod(e1, 0); continue; }
365 | assert(!"unknown vertex attribute");
366 | }
367 | if (line[0] == 'f')
368 | {
369 | unsigned short *tri = (*indices) + cf;
370 | cf += 3;
371 | char *e1, *e2, *e3 = line + 1;
372 | for (int i = 0; i < 3; i++)
373 | {
374 | unsigned long pi = strtoul(e3 + 1, &e1, 10);
375 | assert(e1[0] == '/');
376 | unsigned long ti = strtoul(e1 + 1, &e2, 10);
377 | assert(e2[0] == '/');
378 | unsigned long ni = strtoul(e2 + 1, &e3, 10);
379 | assert(pi == ti && pi == ni);
380 | tri[i] = (unsigned short)(pi - 1);
381 | }
382 | continue;
383 | }
384 | assert(!"unknown identifier");
385 | }
386 |
387 | fclose(file);
388 | return 1;
389 | }
390 |
391 | static GLuint loadShader(GLenum type, const char *source)
392 | {
393 | GLuint shader = glCreateShader(type);
394 | if (shader == 0)
395 | {
396 | fprintf(stderr, "Could not create shader!\n");
397 | return 0;
398 | }
399 | glShaderSource(shader, 1, &source, NULL);
400 | glCompileShader(shader);
401 | GLint compiled;
402 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
403 | if (!compiled)
404 | {
405 | fprintf(stderr, "Could not compile shader!\n");
406 | GLint infoLen = 0;
407 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
408 | if (infoLen)
409 | {
410 | char* infoLog = (char*)malloc(infoLen);
411 | glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
412 | fprintf(stderr, "%s\n", infoLog);
413 | free(infoLog);
414 | }
415 | glDeleteShader(shader);
416 | return 0;
417 | }
418 | return shader;
419 | }
420 | static GLuint loadProgram(const char *vp, const char *fp, const char **attributes, int attributeCount)
421 | {
422 | GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vp);
423 | if (!vertexShader)
424 | return 0;
425 | GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fp);
426 | if (!fragmentShader)
427 | {
428 | glDeleteShader(vertexShader);
429 | return 0;
430 | }
431 |
432 | GLuint program = glCreateProgram();
433 | if (program == 0)
434 | {
435 | fprintf(stderr, "Could not create program!\n");
436 | return 0;
437 | }
438 | glAttachShader(program, vertexShader);
439 | glAttachShader(program, fragmentShader);
440 |
441 | for (int i = 0; i < attributeCount; i++)
442 | glBindAttribLocation(program, i, attributes[i]);
443 |
444 | glLinkProgram(program);
445 | glDeleteShader(vertexShader);
446 | glDeleteShader(fragmentShader);
447 | GLint linked;
448 | glGetProgramiv(program, GL_LINK_STATUS, &linked);
449 | if (!linked)
450 | {
451 | fprintf(stderr, "Could not link program!\n");
452 | GLint infoLen = 0;
453 | glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
454 | if (infoLen)
455 | {
456 | char* infoLog = (char*)malloc(sizeof(char) * infoLen);
457 | glGetProgramInfoLog(program, infoLen, NULL, infoLog);
458 | fprintf(stderr, "%s\n", infoLog);
459 | free(infoLog);
460 | }
461 | glDeleteProgram(program);
462 | return 0;
463 | }
464 | return program;
465 | }
466 |
467 | static void multiplyMatrices(float *out, float *a, float *b)
468 | {
469 | for (int y = 0; y < 4; y++)
470 | for (int x = 0; x < 4; x++)
471 | out[y * 4 + x] = a[x] * b[y * 4] + a[4 + x] * b[y * 4 + 1] + a[8 + x] * b[y * 4 + 2] + a[12 + x] * b[y * 4 + 3];
472 | }
473 | static void translationMatrix(float *out, float x, float y, float z)
474 | {
475 | out[ 0] = 1.0f; out[ 1] = 0.0f; out[ 2] = 0.0f; out[ 3] = 0.0f;
476 | out[ 4] = 0.0f; out[ 5] = 1.0f; out[ 6] = 0.0f; out[ 7] = 0.0f;
477 | out[ 8] = 0.0f; out[ 9] = 0.0f; out[10] = 1.0f; out[11] = 0.0f;
478 | out[12] = x; out[13] = y; out[14] = z; out[15] = 1.0f;
479 | }
480 | static void rotationMatrix(float *out, float angle, float x, float y, float z)
481 | {
482 | angle *= (float)M_PI / 180.0f;
483 | float c = cosf(angle), s = sinf(angle), c2 = 1.0f - c;
484 | out[ 0] = x*x*c2 + c; out[ 1] = y*x*c2 + z*s; out[ 2] = x*z*c2 - y*s; out[ 3] = 0.0f;
485 | out[ 4] = x*y*c2 - z*s; out[ 5] = y*y*c2 + c; out[ 6] = y*z*c2 + x*s; out[ 7] = 0.0f;
486 | out[ 8] = x*z*c2 + y*s; out[ 9] = y*z*c2 - x*s; out[10] = z*z*c2 + c; out[11] = 0.0f;
487 | out[12] = 0.0f; out[13] = 0.0f; out[14] = 0.0f; out[15] = 1.0f;
488 | }
489 | static void transformPosition(float *out, float *m, float *p)
490 | {
491 | float d = 1.0f / (m[3] * p[0] + m[7] * p[1] + m[11] * p[2] + m[15]);
492 | out[2] = d * (m[2] * p[0] + m[6] * p[1] + m[10] * p[2] + m[14]);
493 | out[1] = d * (m[1] * p[0] + m[5] * p[1] + m[ 9] * p[2] + m[13]);
494 | out[0] = d * (m[0] * p[0] + m[4] * p[1] + m[ 8] * p[2] + m[12]);
495 | }
496 | static void transposeMatrix(float *out, float *m)
497 | {
498 | out[ 0] = m[0]; out[ 1] = m[4]; out[ 2] = m[ 8]; out[ 3] = m[12];
499 | out[ 4] = m[1]; out[ 5] = m[5]; out[ 6] = m[ 9]; out[ 7] = m[13];
500 | out[ 8] = m[2]; out[ 9] = m[6]; out[10] = m[10]; out[11] = m[14];
501 | out[12] = m[3]; out[13] = m[7]; out[14] = m[11]; out[15] = m[15];
502 | }
503 | static void perspectiveMatrix(float *out, float fovy, float aspect, float zNear, float zFar)
504 | {
505 | float f = 1.0f / tanf(fovy * (float)M_PI / 360.0f);
506 | float izFN = 1.0f / (zNear - zFar);
507 | out[ 0] = f / aspect; out[ 1] = 0.0f; out[ 2] = 0.0f; out[ 3] = 0.0f;
508 | out[ 4] = 0.0f; out[ 5] = f; out[ 6] = 0.0f; out[ 7] = 0.0f;
509 | out[ 8] = 0.0f; out[ 9] = 0.0f; out[10] = (zFar + zNear) * izFN; out[11] = -1.0f;
510 | out[12] = 0.0f; out[13] = 0.0f; out[14] = 2.0f * zFar * zNear * izFN; out[15] = 0.0f;
511 | }
512 |
513 | static void fpsCameraViewMatrix(GLFWwindow *window, float *view)
514 | {
515 | // initial camera config
516 | static float position[] = { 0.0f, 0.3f, 1.5f };
517 | static float rotation[] = { 0.0f, 0.0f };
518 |
519 | // mouse look
520 | static double lastMouse[] = { 0.0, 0.0 };
521 | double mouse[2];
522 | glfwGetCursorPos(window, &mouse[0], &mouse[1]);
523 | if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
524 | {
525 | rotation[0] += (float)(mouse[1] - lastMouse[1]) * -0.2f;
526 | rotation[1] += (float)(mouse[0] - lastMouse[0]) * -0.2f;
527 | }
528 | lastMouse[0] = mouse[0];
529 | lastMouse[1] = mouse[1];
530 |
531 | float rotationY[16], rotationX[16], rotationYX[16];
532 | rotationMatrix(rotationX, rotation[0], 1.0f, 0.0f, 0.0f);
533 | rotationMatrix(rotationY, rotation[1], 0.0f, 1.0f, 0.0f);
534 | multiplyMatrices(rotationYX, rotationY, rotationX);
535 |
536 | // keyboard movement (WSADEQ)
537 | float speed = (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) ? 0.1f : 0.01f;
538 | float movement[3] = {0};
539 | if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) movement[2] -= speed;
540 | if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) movement[2] += speed;
541 | if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) movement[0] -= speed;
542 | if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) movement[0] += speed;
543 | if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) movement[1] -= speed;
544 | if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) movement[1] += speed;
545 |
546 | float worldMovement[3];
547 | transformPosition(worldMovement, rotationYX, movement);
548 | position[0] += worldMovement[0];
549 | position[1] += worldMovement[1];
550 | position[2] += worldMovement[2];
551 |
552 | // construct view matrix
553 | float inverseRotation[16], inverseTranslation[16];
554 | transposeMatrix(inverseRotation, rotationYX);
555 | translationMatrix(inverseTranslation, -position[0], -position[1], -position[2]);
556 | multiplyMatrices(view, inverseRotation, inverseTranslation); // = inverse(translation(position) * rotationYX);
557 | }
558 |
--------------------------------------------------------------------------------
/example_images/debug_interpolation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ands/lightmapper/4fd3bf4e2c07263f85d5d875ebdef061bc512dd4/example_images/debug_interpolation.png
--------------------------------------------------------------------------------
/example_images/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ands/lightmapper/4fd3bf4e2c07263f85d5d875ebdef061bc512dd4/example_images/example.png
--------------------------------------------------------------------------------
/example_images/gazebo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ands/lightmapper/4fd3bf4e2c07263f85d5d875ebdef061bc512dd4/example_images/gazebo_light.png
--------------------------------------------------------------------------------
/example_images/gazebo_shadows_reflections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ands/lightmapper/4fd3bf4e2c07263f85d5d875ebdef061bc512dd4/example_images/gazebo_shadows_reflections.png
--------------------------------------------------------------------------------
/example_images/gazebo_skybox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ands/lightmapper/4fd3bf4e2c07263f85d5d875ebdef061bc512dd4/example_images/gazebo_skybox.png
--------------------------------------------------------------------------------
/lightmapper.h:
--------------------------------------------------------------------------------
1 | /***********************************************************
2 | * A single header file OpenGL lightmapping library *
3 | * https://github.com/ands/lightmapper *
4 | * no warranty implied | use at your own risk *
5 | * author: Andreas Mantler (ands) | last change: 10.05.2018 *
6 | * *
7 | * License: *
8 | * This software is in the public domain. *
9 | * Where that dedication is not recognized, *
10 | * you are granted a perpetual, irrevocable license to copy *
11 | * and modify this file however you want. *
12 | ***********************************************************/
13 |
14 | #ifndef LIGHTMAPPER_H
15 | #define LIGHTMAPPER_H
16 |
17 | #ifdef __cplusplus
18 | #define LM_DEFAULT_VALUE(value) = value
19 | #else
20 | #define LM_DEFAULT_VALUE(value)
21 | #endif
22 |
23 | #ifndef LM_CALLOC
24 | #define LM_CALLOC(count, size) calloc(count, size)
25 | #endif
26 |
27 | #ifndef LM_FREE
28 | #define LM_FREE(ptr) free(ptr)
29 | #endif
30 |
31 | typedef int lm_bool;
32 | #define LM_FALSE 0
33 | #define LM_TRUE 1
34 |
35 | typedef int lm_type;
36 | #define LM_NONE 0
37 | #define LM_UNSIGNED_BYTE GL_UNSIGNED_BYTE
38 | #define LM_UNSIGNED_SHORT GL_UNSIGNED_SHORT
39 | #define LM_UNSIGNED_INT GL_UNSIGNED_INT
40 | #define LM_FLOAT GL_FLOAT
41 |
42 | typedef struct lm_context lm_context;
43 |
44 | // creates a lightmapper instance. it can be used to render multiple lightmaps.
45 | lm_context *lmCreate(
46 | int hemisphereSize, // hemisphereSize: resolution of the hemisphere renderings. must be a power of two! typical: 64.
47 | float zNear, float zFar, // zNear/zFar: hemisphere min/max draw distances.
48 | float clearR, float clearG, float clearB, // clear color / background color / sky color.
49 | int interpolationPasses, float interpolationThreshold, // passes: hierarchical selective interpolation passes (0-8; initial step size = 2^passes).
50 | // threshold: error value below which lightmap pixels are interpolated instead of rendered.
51 | // use output image from LM_DEBUG_INTERPOLATION to determine a good value.
52 | // values around and below 0.01 are probably ok.
53 | // the lower the value, the more hemispheres are rendered -> slower, but possibly better quality.
54 | float cameraToSurfaceDistanceModifier LM_DEFAULT_VALUE(0.0f)); // modifier for the height of the rendered hemispheres above the surface
55 | // -1.0f => stick to surface, 0.0f => minimum height for interpolated surface normals,
56 | // > 0.0f => improves gradients on surfaces with interpolated normals due to the flat surface horizon,
57 | // but may introduce other artifacts.
58 |
59 | // optional: set material characteristics by specifying cos(theta)-dependent weights for incoming light.
60 | typedef float (*lm_weight_func)(float cos_theta, void *userdata);
61 | void lmSetHemisphereWeights(lm_context *ctx, lm_weight_func f, void *userdata); // precalculates weights for incoming light depending on its angle. (default: all weights are 1.0f)
62 |
63 | // specify an output lightmap image buffer with w * h * c * sizeof(float) bytes of memory.
64 | void lmSetTargetLightmap(lm_context *ctx, float *outLightmap, int w, int h, int c); // output HDR lightmap (linear 32bit float channels; c: 1->Greyscale, 2->Greyscale+Alpha, 3->RGB, 4->RGBA).
65 |
66 | // set the geometry to map to the currently set target lightmap (set the target lightmap before calling this!).
67 | void lmSetGeometry(lm_context *ctx,
68 | const float *transformationMatrix, // 4x4 object-to-world transform for the geometry or NULL (no transformation).
69 | lm_type positionsType, const void *positionsXYZ, int positionsStride, // triangle mesh in object space.
70 | lm_type normalsType, const void *normalsXYZ, int normalsStride, // optional normals for the mesh in object space (Use LM_NONE type in case you only need flat surfaces).
71 | lm_type lightmapCoordsType, const void *lightmapCoordsUV, int lightmapCoordsStride, // lightmap atlas texture coordinates for the mesh [0..1]x[0..1] (integer types are normalized to 0..1 range).
72 | int count, lm_type indicesType LM_DEFAULT_VALUE(LM_NONE), const void *indices LM_DEFAULT_VALUE(0));// if mesh indices are used, count = number of indices else count = number of vertices.
73 |
74 |
75 | // as long as lmBegin returns true, the scene has to be rendered with the
76 | // returned camera and view parameters to the currently bound framebuffer.
77 | // if lmBegin returns true, it must be followed by lmEnd after rendering!
78 | lm_bool lmBegin(lm_context *ctx,
79 | int* outViewport4, // output of the current viewport: { x, y, w, h }. use these to call glViewport()!
80 | float* outView4x4, // output of the current camera view matrix.
81 | float* outProjection4x4); // output of the current camera projection matrix.
82 |
83 | float lmProgress(lm_context *ctx); // should only be called between lmBegin/lmEnd!
84 | // provides the light mapping progress as a value increasing from 0.0 to 1.0.
85 |
86 | void lmEnd(lm_context *ctx);
87 |
88 | // destroys the lightmapper instance. should be called to free resources.
89 | void lmDestroy(lm_context *ctx);
90 |
91 |
92 | // image based post processing (c is the number of color channels in the image, m a channel mask for the operation)
93 | #define LM_ALL_CHANNELS 0x0f
94 | float lmImageMin(const float *image, int w, int h, int c, int m LM_DEFAULT_VALUE(LM_ALL_CHANNELS)); // find the minimum value (across the specified channels)
95 | float lmImageMax(const float *image, int w, int h, int c, int m LM_DEFAULT_VALUE(LM_ALL_CHANNELS)); // find the maximum value (across the specified channels)
96 | void lmImageAdd(float *image, int w, int h, int c, float value, int m LM_DEFAULT_VALUE(LM_ALL_CHANNELS)); // in-place add to the specified channels
97 | void lmImageScale(float *image, int w, int h, int c, float factor, int m LM_DEFAULT_VALUE(LM_ALL_CHANNELS)); // in-place scaling of the specified channels
98 | void lmImagePower(float *image, int w, int h, int c, float exponent, int m LM_DEFAULT_VALUE(LM_ALL_CHANNELS)); // in-place powf(v, exponent) of the specified channels (for gamma)
99 | void lmImageDilate(const float *image, float *outImage, int w, int h, int c); // widen the populated non-zero areas by 1 pixel.
100 | void lmImageSmooth(const float *image, float *outImage, int w, int h, int c); // simple box filter on only the non-zero values.
101 | void lmImageDownsample(const float *image, float *outImage, int w, int h, int c); // downsamples [0..w]x[0..h] to [0..w/2]x[0..h/2] by avereging only the non-zero values
102 | void lmImageFtoUB(const float *image, unsigned char *outImage, int w, int h, int c, float max LM_DEFAULT_VALUE(0.0f)); // casts a floating point image to an 8bit/channel image
103 |
104 | // TGA file output helpers
105 | lm_bool lmImageSaveTGAub(const char *filename, const unsigned char *image, int w, int h, int c);
106 | lm_bool lmImageSaveTGAf(const char *filename, const float *image, int w, int h, int c, float max LM_DEFAULT_VALUE(0.0f));
107 |
108 | #endif
109 | ////////////////////// END OF HEADER //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
110 | #ifdef LIGHTMAPPER_IMPLEMENTATION
111 | #undef LIGHTMAPPER_IMPLEMENTATION
112 |
113 | #include
114 | #include
115 | #include
116 | #include
117 | #include
118 | #include
119 |
120 | #define LM_SWAP(type, a, b) { type tmp = (a); (a) = (b); (b) = tmp; }
121 |
122 | #if defined(_MSC_VER) && !defined(__cplusplus)
123 | #define inline __inline
124 | #endif
125 |
126 | #if defined(_MSC_VER) && (_MSC_VER <= 1700)
127 | static inline lm_bool lm_finite(float a) { return _finite(a); }
128 | #else
129 | static inline lm_bool lm_finite(float a) { return isfinite(a); }
130 | #endif
131 |
132 | static inline int lm_mini (int a, int b) { return a < b ? a : b; }
133 | static inline int lm_maxi (int a, int b) { return a > b ? a : b; }
134 | static inline int lm_absi (int a ) { return a < 0 ? -a : a; }
135 | static inline float lm_minf (float a, float b) { return a < b ? a : b; }
136 | static inline float lm_maxf (float a, float b) { return a > b ? a : b; }
137 | static inline float lm_absf (float a ) { return a < 0.0f ? -a : a; }
138 | static inline float lm_pmodf (float a, float b) { return (a < 0.0f ? 1.0f : 0.0f) + (float)fmod(a, b); } // positive mod
139 |
140 | typedef struct lm_ivec2 { int x, y; } lm_ivec2;
141 | static inline lm_ivec2 lm_i2 (int x, int y) { lm_ivec2 v = { x, y }; return v; }
142 |
143 | typedef struct lm_vec2 { float x, y; } lm_vec2;
144 | static inline lm_vec2 lm_v2i (int x, int y) { lm_vec2 v = { (float)x, (float)y }; return v; }
145 | static inline lm_vec2 lm_v2 (float x, float y) { lm_vec2 v = { x, y }; return v; }
146 | static inline lm_vec2 lm_negate2 (lm_vec2 a ) { return lm_v2(-a.x, -a.y); }
147 | static inline lm_vec2 lm_add2 (lm_vec2 a, lm_vec2 b) { return lm_v2(a.x + b.x, a.y + b.y); }
148 | static inline lm_vec2 lm_sub2 (lm_vec2 a, lm_vec2 b) { return lm_v2(a.x - b.x, a.y - b.y); }
149 | static inline lm_vec2 lm_mul2 (lm_vec2 a, lm_vec2 b) { return lm_v2(a.x * b.x, a.y * b.y); }
150 | static inline lm_vec2 lm_scale2 (lm_vec2 a, float b) { return lm_v2(a.x * b, a.y * b); }
151 | static inline lm_vec2 lm_div2 (lm_vec2 a, float b) { return lm_scale2(a, 1.0f / b); }
152 | static inline lm_vec2 lm_pmod2 (lm_vec2 a, float b) { return lm_v2(lm_pmodf(a.x, b), lm_pmodf(a.y, b)); }
153 | static inline lm_vec2 lm_min2 (lm_vec2 a, lm_vec2 b) { return lm_v2(lm_minf(a.x, b.x), lm_minf(a.y, b.y)); }
154 | static inline lm_vec2 lm_max2 (lm_vec2 a, lm_vec2 b) { return lm_v2(lm_maxf(a.x, b.x), lm_maxf(a.y, b.y)); }
155 | static inline lm_vec2 lm_abs2 (lm_vec2 a ) { return lm_v2(lm_absf(a.x), lm_absf(a.y)); }
156 | static inline lm_vec2 lm_floor2 (lm_vec2 a ) { return lm_v2(floorf(a.x), floorf(a.y)); }
157 | static inline lm_vec2 lm_ceil2 (lm_vec2 a ) { return lm_v2(ceilf (a.x), ceilf (a.y)); }
158 | static inline float lm_dot2 (lm_vec2 a, lm_vec2 b) { return a.x * b.x + a.y * b.y; }
159 | static inline float lm_cross2 (lm_vec2 a, lm_vec2 b) { return a.x * b.y - a.y * b.x; } // pseudo cross product
160 | static inline float lm_length2sq (lm_vec2 a ) { return a.x * a.x + a.y * a.y; }
161 | static inline float lm_length2 (lm_vec2 a ) { return sqrtf(lm_length2sq(a)); }
162 | static inline lm_vec2 lm_normalize2(lm_vec2 a ) { return lm_div2(a, lm_length2(a)); }
163 | static inline lm_bool lm_finite2 (lm_vec2 a ) { return lm_finite(a.x) && lm_finite(a.y); }
164 |
165 | typedef struct lm_vec3 { float x, y, z; } lm_vec3;
166 | static inline lm_vec3 lm_v3 (float x, float y, float z) { lm_vec3 v = { x, y, z }; return v; }
167 | static inline lm_vec3 lm_negate3 (lm_vec3 a ) { return lm_v3(-a.x, -a.y, -a.z); }
168 | static inline lm_vec3 lm_add3 (lm_vec3 a, lm_vec3 b) { return lm_v3(a.x + b.x, a.y + b.y, a.z + b.z); }
169 | static inline lm_vec3 lm_sub3 (lm_vec3 a, lm_vec3 b) { return lm_v3(a.x - b.x, a.y - b.y, a.z - b.z); }
170 | static inline lm_vec3 lm_mul3 (lm_vec3 a, lm_vec3 b) { return lm_v3(a.x * b.x, a.y * b.y, a.z * b.z); }
171 | static inline lm_vec3 lm_scale3 (lm_vec3 a, float b) { return lm_v3(a.x * b, a.y * b, a.z * b); }
172 | static inline lm_vec3 lm_div3 (lm_vec3 a, float b) { return lm_scale3(a, 1.0f / b); }
173 | static inline lm_vec3 lm_pmod3 (lm_vec3 a, float b) { return lm_v3(lm_pmodf(a.x, b), lm_pmodf(a.y, b), lm_pmodf(a.z, b)); }
174 | static inline lm_vec3 lm_min3 (lm_vec3 a, lm_vec3 b) { return lm_v3(lm_minf(a.x, b.x), lm_minf(a.y, b.y), lm_minf(a.z, b.z)); }
175 | static inline lm_vec3 lm_max3 (lm_vec3 a, lm_vec3 b) { return lm_v3(lm_maxf(a.x, b.x), lm_maxf(a.y, b.y), lm_maxf(a.z, b.z)); }
176 | static inline lm_vec3 lm_abs3 (lm_vec3 a ) { return lm_v3(lm_absf(a.x), lm_absf(a.y), lm_absf(a.z)); }
177 | static inline lm_vec3 lm_floor3 (lm_vec3 a ) { return lm_v3(floorf(a.x), floorf(a.y), floorf(a.z)); }
178 | static inline lm_vec3 lm_ceil3 (lm_vec3 a ) { return lm_v3(ceilf (a.x), ceilf (a.y), ceilf (a.z)); }
179 | static inline float lm_dot3 (lm_vec3 a, lm_vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
180 | static inline lm_vec3 lm_cross3 (lm_vec3 a, lm_vec3 b) { return lm_v3(a.y * b.z - b.y * a.z, a.z * b.x - b.z * a.x, a.x * b.y - b.x * a.y); }
181 | static inline float lm_length3sq (lm_vec3 a ) { return a.x * a.x + a.y * a.y + a.z * a.z; }
182 | static inline float lm_length3 (lm_vec3 a ) { return sqrtf(lm_length3sq(a)); }
183 | static inline lm_vec3 lm_normalize3(lm_vec3 a ) { return lm_div3(a, lm_length3(a)); }
184 | static inline lm_bool lm_finite3 (lm_vec3 a ) { return lm_finite(a.x) && lm_finite(a.y) && lm_finite(a.z); }
185 |
186 | static lm_vec2 lm_toBarycentric(lm_vec2 p1, lm_vec2 p2, lm_vec2 p3, lm_vec2 p)
187 | {
188 | // http://www.blackpawn.com/texts/pointinpoly/
189 | // Compute vectors
190 | lm_vec2 v0 = lm_sub2(p3, p1);
191 | lm_vec2 v1 = lm_sub2(p2, p1);
192 | lm_vec2 v2 = lm_sub2(p, p1);
193 | // Compute dot products
194 | float dot00 = lm_dot2(v0, v0);
195 | float dot01 = lm_dot2(v0, v1);
196 | float dot02 = lm_dot2(v0, v2);
197 | float dot11 = lm_dot2(v1, v1);
198 | float dot12 = lm_dot2(v1, v2);
199 | // Compute barycentric coordinates
200 | float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
201 | float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
202 | float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
203 | return lm_v2(u, v);
204 | }
205 |
206 | static inline int lm_leftOf(lm_vec2 a, lm_vec2 b, lm_vec2 c)
207 | {
208 | float x = lm_cross2(lm_sub2(b, a), lm_sub2(c, b));
209 | return x < 0 ? -1 : x > 0;
210 | }
211 |
212 | static lm_bool lm_lineIntersection(lm_vec2 x0, lm_vec2 x1, lm_vec2 y0, lm_vec2 y1, lm_vec2* res)
213 | {
214 | lm_vec2 dx = lm_sub2(x1, x0);
215 | lm_vec2 dy = lm_sub2(y1, y0);
216 | lm_vec2 d = lm_sub2(x0, y0);
217 | float dyx = lm_cross2(dy, dx);
218 | if (dyx == 0.0f)
219 | return LM_FALSE;
220 | dyx = lm_cross2(d, dx) / dyx;
221 | if (dyx <= 0 || dyx >= 1)
222 | return LM_FALSE;
223 | res->x = y0.x + dyx * dy.x;
224 | res->y = y0.y + dyx * dy.y;
225 | return LM_TRUE;
226 | }
227 |
228 | // this modifies the poly array! the poly array must be big enough to hold the result!
229 | // res must be big enough to hold the result!
230 | static int lm_convexClip(lm_vec2 *poly, int nPoly, const lm_vec2 *clip, int nClip, lm_vec2 *res)
231 | {
232 | int nRes = nPoly;
233 | int dir = lm_leftOf(clip[0], clip[1], clip[2]);
234 | for (int i = 0, j = nClip - 1; i < nClip && nRes; j = i++)
235 | {
236 | if (i != 0)
237 | for (nPoly = 0; nPoly < nRes; nPoly++)
238 | poly[nPoly] = res[nPoly];
239 | nRes = 0;
240 | lm_vec2 v0 = poly[nPoly - 1];
241 | int side0 = lm_leftOf(clip[j], clip[i], v0);
242 | if (side0 != -dir)
243 | res[nRes++] = v0;
244 | for (int k = 0; k < nPoly; k++)
245 | {
246 | lm_vec2 v1 = poly[k], x;
247 | int side1 = lm_leftOf(clip[j], clip[i], v1);
248 | if (side0 + side1 == 0 && side0 && lm_lineIntersection(clip[j], clip[i], v0, v1, &x))
249 | res[nRes++] = x;
250 | if (k == nPoly - 1)
251 | break;
252 | if (side1 != -dir)
253 | res[nRes++] = v1;
254 | v0 = v1;
255 | side0 = side1;
256 | }
257 | }
258 |
259 | return nRes;
260 | }
261 |
262 | struct lm_context
263 | {
264 | struct
265 | {
266 | const float *modelMatrix;
267 | float normalMatrix[9];
268 |
269 | const unsigned char *positions;
270 | lm_type positionsType;
271 | int positionsStride;
272 | const unsigned char *normals;
273 | lm_type normalsType;
274 | int normalsStride;
275 | const unsigned char *uvs;
276 | lm_type uvsType;
277 | int uvsStride;
278 | const unsigned char *indices;
279 | lm_type indicesType;
280 | unsigned int count;
281 | } mesh;
282 |
283 | struct
284 | {
285 | int pass;
286 | int passCount;
287 |
288 | struct
289 | {
290 | unsigned int baseIndex;
291 | lm_vec3 p[3];
292 | lm_vec3 n[3];
293 | lm_vec2 uv[3];
294 | } triangle;
295 |
296 | struct
297 | {
298 | int minx, miny;
299 | int maxx, maxy;
300 | int x, y;
301 | } rasterizer;
302 |
303 | struct
304 | {
305 | lm_vec3 position;
306 | lm_vec3 direction;
307 | lm_vec3 up;
308 | } sample;
309 |
310 | struct
311 | {
312 | int side;
313 | } hemisphere;
314 | } meshPosition;
315 |
316 | struct
317 | {
318 | int width;
319 | int height;
320 | int channels;
321 | float *data;
322 |
323 | #ifdef LM_DEBUG_INTERPOLATION
324 | unsigned char *debug;
325 | #endif
326 | } lightmap;
327 |
328 | struct
329 | {
330 | unsigned int size;
331 | float zNear, zFar;
332 | float cameraToSurfaceDistanceModifier;
333 | struct { float r, g, b; } clearColor;
334 |
335 | unsigned int fbHemiCountX;
336 | unsigned int fbHemiCountY;
337 | unsigned int fbHemiIndex;
338 | lm_ivec2 *fbHemiToLightmapLocation;
339 | GLuint fbTexture[2];
340 | GLuint fb[2];
341 | GLuint fbDepth;
342 | GLuint vao;
343 | struct
344 | {
345 | GLuint programID;
346 | GLuint hemispheresTextureID;
347 | GLuint weightsTextureID;
348 | GLuint weightsTexture;
349 | } firstPass;
350 | struct
351 | {
352 | GLuint programID;
353 | GLuint hemispheresTextureID;
354 | } downsamplePass;
355 | struct
356 | {
357 | GLuint texture;
358 | lm_ivec2 writePosition;
359 | lm_ivec2 *toLightmapLocation;
360 | } storage;
361 | } hemisphere;
362 |
363 | float interpolationThreshold;
364 | };
365 |
366 | // pass order of one 4x4 interpolation patch for two interpolation steps (and the next neighbors right of/below it)
367 | // 0 4 1 4 0
368 | // 5 6 5 6 5
369 | // 2 4 3 4 2
370 | // 5 6 5 6 5
371 | // 0 4 1 4 0
372 |
373 | static unsigned int lm_passStepSize(lm_context *ctx)
374 | {
375 | unsigned int shift = ctx->meshPosition.passCount / 3 - (ctx->meshPosition.pass - 1) / 3;
376 | unsigned int step = (1 << shift);
377 | assert(step > 0);
378 | return step;
379 | }
380 |
381 | static unsigned int lm_passOffsetX(lm_context *ctx)
382 | {
383 | if (!ctx->meshPosition.pass)
384 | return 0;
385 | int passType = (ctx->meshPosition.pass - 1) % 3;
386 | unsigned int halfStep = lm_passStepSize(ctx) >> 1;
387 | return passType != 1 ? halfStep : 0;
388 | }
389 |
390 | static unsigned int lm_passOffsetY(lm_context *ctx)
391 | {
392 | if (!ctx->meshPosition.pass)
393 | return 0;
394 | int passType = (ctx->meshPosition.pass - 1) % 3;
395 | unsigned int halfStep = lm_passStepSize(ctx) >> 1;
396 | return passType != 0 ? halfStep : 0;
397 | }
398 |
399 | static lm_bool lm_hasConservativeTriangleRasterizerFinished(lm_context *ctx)
400 | {
401 | return ctx->meshPosition.rasterizer.y >= ctx->meshPosition.rasterizer.maxy;
402 | }
403 |
404 | static void lm_moveToNextPotentialConservativeTriangleRasterizerPosition(lm_context *ctx)
405 | {
406 | unsigned int step = lm_passStepSize(ctx);
407 | ctx->meshPosition.rasterizer.x += step;
408 | while (ctx->meshPosition.rasterizer.x >= ctx->meshPosition.rasterizer.maxx)
409 | {
410 | ctx->meshPosition.rasterizer.x = ctx->meshPosition.rasterizer.minx + lm_passOffsetX(ctx);
411 | ctx->meshPosition.rasterizer.y += step;
412 | if (lm_hasConservativeTriangleRasterizerFinished(ctx))
413 | break;
414 | }
415 | }
416 |
417 | static float *lm_getLightmapPixel(lm_context *ctx, int x, int y)
418 | {
419 | assert(x >= 0 && x < ctx->lightmap.width && y >= 0 && y < ctx->lightmap.height);
420 | return ctx->lightmap.data + (y * ctx->lightmap.width + x) * ctx->lightmap.channels;
421 | }
422 |
423 | static void lm_setLightmapPixel(lm_context *ctx, int x, int y, float *in)
424 | {
425 | assert(x >= 0 && x < ctx->lightmap.width && y >= 0 && y < ctx->lightmap.height);
426 | float *p = ctx->lightmap.data + (y * ctx->lightmap.width + x) * ctx->lightmap.channels;
427 | for (int j = 0; j < ctx->lightmap.channels; j++)
428 | *p++ = *in++;
429 | }
430 |
431 | #define lm_baseAngle 0.1f
432 | static const float lm_baseAngles[3][3] = {
433 | { lm_baseAngle, lm_baseAngle + 1.0f / 3.0f, lm_baseAngle + 2.0f / 3.0f },
434 | { lm_baseAngle + 1.0f / 3.0f, lm_baseAngle + 2.0f / 3.0f, lm_baseAngle },
435 | { lm_baseAngle + 2.0f / 3.0f, lm_baseAngle, lm_baseAngle + 1.0f / 3.0f }
436 | };
437 |
438 | static lm_bool lm_trySamplingConservativeTriangleRasterizerPosition(lm_context *ctx)
439 | {
440 | if (lm_hasConservativeTriangleRasterizerFinished(ctx))
441 | return LM_FALSE;
442 |
443 | // check if lightmap pixel was already set
444 | float *pixelValue = lm_getLightmapPixel(ctx, ctx->meshPosition.rasterizer.x, ctx->meshPosition.rasterizer.y);
445 | for (int j = 0; j < ctx->lightmap.channels; j++)
446 | if (pixelValue[j] != 0.0f)
447 | return LM_FALSE;
448 |
449 | // try calculating centroid by clipping the pixel against the triangle
450 | lm_vec2 pixel[16];
451 | pixel[0] = lm_v2i(ctx->meshPosition.rasterizer.x, ctx->meshPosition.rasterizer.y);
452 | pixel[1] = lm_v2i(ctx->meshPosition.rasterizer.x + 1, ctx->meshPosition.rasterizer.y);
453 | pixel[2] = lm_v2i(ctx->meshPosition.rasterizer.x + 1, ctx->meshPosition.rasterizer.y + 1);
454 | pixel[3] = lm_v2i(ctx->meshPosition.rasterizer.x, ctx->meshPosition.rasterizer.y + 1);
455 |
456 | lm_vec2 res[16];
457 | int nRes = lm_convexClip(pixel, 4, ctx->meshPosition.triangle.uv, 3, res);
458 | if (nRes == 0)
459 | return LM_FALSE; // nothing left
460 |
461 | // calculate centroid position and area
462 | lm_vec2 centroid = res[0];
463 | float area = res[nRes - 1].x * res[0].y - res[nRes - 1].y * res[0].x;
464 | for (int i = 1; i < nRes; i++)
465 | {
466 | centroid = lm_add2(centroid, res[i]);
467 | area += res[i - 1].x * res[i].y - res[i - 1].y * res[i].x;
468 | }
469 | centroid = lm_div2(centroid, (float)nRes);
470 | area = lm_absf(area / 2.0f);
471 |
472 | if (area <= 0.0f)
473 | return LM_FALSE; // no area left
474 |
475 | // calculate barycentric coords
476 | lm_vec2 uv = lm_toBarycentric(
477 | ctx->meshPosition.triangle.uv[0],
478 | ctx->meshPosition.triangle.uv[1],
479 | ctx->meshPosition.triangle.uv[2],
480 | centroid);
481 |
482 | if (!lm_finite2(uv))
483 | return LM_FALSE; // degenerate
484 |
485 | // try to interpolate color from neighbors:
486 | if (ctx->meshPosition.pass > 0)
487 | {
488 | float *neighbors[4];
489 | int neighborCount = 0;
490 | int neighborsExpected = 0;
491 | int d = (int)lm_passStepSize(ctx) / 2;
492 | int dirs = ((ctx->meshPosition.pass - 1) % 3) + 1;
493 | if (dirs & 1) // check x-neighbors with distance d
494 | {
495 | neighborsExpected += 2;
496 | if (ctx->meshPosition.rasterizer.x - d >= ctx->meshPosition.rasterizer.minx &&
497 | ctx->meshPosition.rasterizer.x + d <= ctx->meshPosition.rasterizer.maxx)
498 | {
499 | neighbors[neighborCount++] = lm_getLightmapPixel(ctx, ctx->meshPosition.rasterizer.x - d, ctx->meshPosition.rasterizer.y);
500 | neighbors[neighborCount++] = lm_getLightmapPixel(ctx, ctx->meshPosition.rasterizer.x + d, ctx->meshPosition.rasterizer.y);
501 | }
502 | }
503 | if (dirs & 2) // check y-neighbors with distance d
504 | {
505 | neighborsExpected += 2;
506 | if (ctx->meshPosition.rasterizer.y - d >= ctx->meshPosition.rasterizer.miny &&
507 | ctx->meshPosition.rasterizer.y + d <= ctx->meshPosition.rasterizer.maxy)
508 | {
509 | neighbors[neighborCount++] = lm_getLightmapPixel(ctx, ctx->meshPosition.rasterizer.x, ctx->meshPosition.rasterizer.y - d);
510 | neighbors[neighborCount++] = lm_getLightmapPixel(ctx, ctx->meshPosition.rasterizer.x, ctx->meshPosition.rasterizer.y + d);
511 | }
512 | }
513 | if (neighborCount == neighborsExpected) // are all interpolation neighbors available?
514 | {
515 | // calculate average neighbor pixel value
516 | float avg[4] = { 0 };
517 | for (int i = 0; i < neighborCount; i++)
518 | for (int j = 0; j < ctx->lightmap.channels; j++)
519 | avg[j] += neighbors[i][j];
520 | float ni = 1.0f / neighborCount;
521 | for (int j = 0; j < ctx->lightmap.channels; j++)
522 | avg[j] *= ni;
523 |
524 | // check if error from average pixel to neighbors is above the interpolation threshold
525 | lm_bool interpolate = LM_TRUE;
526 | for (int i = 0; i < neighborCount; i++)
527 | {
528 | lm_bool zero = LM_TRUE;
529 | for (int j = 0; j < ctx->lightmap.channels; j++)
530 | {
531 | if (neighbors[i][j] != 0.0f)
532 | zero = LM_FALSE;
533 | if (fabs(neighbors[i][j] - avg[j]) > ctx->interpolationThreshold)
534 | interpolate = LM_FALSE;
535 | }
536 | if (zero)
537 | interpolate = LM_FALSE;
538 | if (!interpolate)
539 | break;
540 | }
541 |
542 | // set interpolated value and return if interpolation is acceptable
543 | if (interpolate)
544 | {
545 | lm_setLightmapPixel(ctx, ctx->meshPosition.rasterizer.x, ctx->meshPosition.rasterizer.y, avg);
546 | #ifdef LM_DEBUG_INTERPOLATION
547 | // set interpolated pixel to green in debug output
548 | ctx->lightmap.debug[(ctx->meshPosition.rasterizer.y * ctx->lightmap.width + ctx->meshPosition.rasterizer.x) * 3 + 1] = 255;
549 | #endif
550 | return LM_FALSE;
551 | }
552 | }
553 | }
554 |
555 | // could not interpolate. must render a hemisphere.
556 | // calculate 3D sample position and orientation
557 | lm_vec3 p0 = ctx->meshPosition.triangle.p[0];
558 | lm_vec3 p1 = ctx->meshPosition.triangle.p[1];
559 | lm_vec3 p2 = ctx->meshPosition.triangle.p[2];
560 | lm_vec3 v1 = lm_sub3(p1, p0);
561 | lm_vec3 v2 = lm_sub3(p2, p0);
562 | ctx->meshPosition.sample.position = lm_add3(p0, lm_add3(lm_scale3(v2, uv.x), lm_scale3(v1, uv.y)));
563 |
564 | lm_vec3 n0 = ctx->meshPosition.triangle.n[0];
565 | lm_vec3 n1 = ctx->meshPosition.triangle.n[1];
566 | lm_vec3 n2 = ctx->meshPosition.triangle.n[2];
567 | lm_vec3 nv1 = lm_sub3(n1, n0);
568 | lm_vec3 nv2 = lm_sub3(n2, n0);
569 | ctx->meshPosition.sample.direction = lm_normalize3(lm_add3(n0, lm_add3(lm_scale3(nv2, uv.x), lm_scale3(nv1, uv.y))));
570 | ctx->meshPosition.sample.direction = lm_normalize3(ctx->meshPosition.sample.direction);
571 | float cameraToSurfaceDistance = (1.0f + ctx->hemisphere.cameraToSurfaceDistanceModifier) * ctx->hemisphere.zNear * sqrtf(2.0f);
572 | ctx->meshPosition.sample.position = lm_add3(ctx->meshPosition.sample.position, lm_scale3(ctx->meshPosition.sample.direction, cameraToSurfaceDistance));
573 |
574 | if (!lm_finite3(ctx->meshPosition.sample.position) ||
575 | !lm_finite3(ctx->meshPosition.sample.direction) ||
576 | lm_length3sq(ctx->meshPosition.sample.direction) < 0.5f) // don't allow 0.0f. should always be ~1.0f
577 | return LM_FALSE;
578 |
579 | lm_vec3 up = lm_v3(0.0f, 1.0f, 0.0f);
580 | if (lm_absf(lm_dot3(up, ctx->meshPosition.sample.direction)) > 0.8f)
581 | up = lm_v3(0.0f, 0.0f, 1.0f);
582 |
583 | #if 0
584 | // triangle-consistent up vector
585 | ctx->meshPosition.sample.up = lm_normalize3(lm_cross3(up, ctx->meshPosition.sample.direction));
586 | return LM_TRUE;
587 | #else
588 | // "randomized" rotation with pattern
589 | lm_vec3 side = lm_normalize3(lm_cross3(up, ctx->meshPosition.sample.direction));
590 | up = lm_normalize3(lm_cross3(side, ctx->meshPosition.sample.direction));
591 | int rx = ctx->meshPosition.rasterizer.x % 3;
592 | int ry = ctx->meshPosition.rasterizer.y % 3;
593 | static const float lm_pi = 3.14159265358979f;
594 | float phi = 2.0f * lm_pi * lm_baseAngles[ry][rx] + 0.1f * ((float)rand() / (float)RAND_MAX);
595 | ctx->meshPosition.sample.up = lm_normalize3(lm_add3(lm_scale3(side, cosf(phi)), lm_scale3(up, sinf(phi))));
596 | return LM_TRUE;
597 | #endif
598 | }
599 |
600 | // returns true if a sampling position was found and
601 | // false if we finished rasterizing the current triangle
602 | static lm_bool lm_findFirstConservativeTriangleRasterizerPosition(lm_context *ctx)
603 | {
604 | while (!lm_trySamplingConservativeTriangleRasterizerPosition(ctx))
605 | {
606 | lm_moveToNextPotentialConservativeTriangleRasterizerPosition(ctx);
607 | if (lm_hasConservativeTriangleRasterizerFinished(ctx))
608 | return LM_FALSE;
609 | }
610 | return LM_TRUE;
611 | }
612 |
613 | static lm_bool lm_findNextConservativeTriangleRasterizerPosition(lm_context *ctx)
614 | {
615 | lm_moveToNextPotentialConservativeTriangleRasterizerPosition(ctx);
616 | return lm_findFirstConservativeTriangleRasterizerPosition(ctx);
617 | }
618 |
619 | static void lm_integrateHemisphereBatch(lm_context *ctx)
620 | {
621 | if (!ctx->hemisphere.fbHemiIndex)
622 | return; // nothing to do
623 |
624 | glDisable(GL_DEPTH_TEST);
625 | glBindVertexArray(ctx->hemisphere.vao);
626 |
627 | int fbRead = 0;
628 | int fbWrite = 1;
629 |
630 | // weighted downsampling pass
631 | int outHemiSize = ctx->hemisphere.size / 2;
632 | glBindFramebuffer(GL_FRAMEBUFFER, ctx->hemisphere.fb[fbWrite]);
633 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ctx->hemisphere.fbTexture[fbWrite], 0);
634 | glViewport(0, 0, outHemiSize * ctx->hemisphere.fbHemiCountX, outHemiSize * ctx->hemisphere.fbHemiCountY);
635 | glUseProgram(ctx->hemisphere.firstPass.programID);
636 | glUniform1i(ctx->hemisphere.firstPass.hemispheresTextureID, 0);
637 | glActiveTexture(GL_TEXTURE0);
638 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.fbTexture[fbRead]);
639 | glUniform1i(ctx->hemisphere.firstPass.weightsTextureID, 1);
640 | glActiveTexture(GL_TEXTURE1);
641 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.firstPass.weightsTexture);
642 | glActiveTexture(GL_TEXTURE0);
643 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
644 | //glBindTexture(GL_TEXTURE_2D, 0);
645 |
646 | #if 0
647 | // debug output
648 | int w = outHemiSize * ctx->hemisphere.fbHemiCountX, h = outHemiSize * ctx->hemisphere.fbHemiCountY;
649 | glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
650 | glBindFramebuffer(GL_READ_FRAMEBUFFER, ctx->hemisphere.fb[fbWrite]);
651 | glReadBuffer(GL_COLOR_ATTACHMENT0);
652 | float *image = new float[3 * w * h];
653 | glReadPixels(0, 0, w, h, GL_RGB, GL_FLOAT, image);
654 | lmImageSaveTGAf("firstpass.png", image, w, h, 3);
655 | delete[] image;
656 | #endif
657 |
658 | // downsampling passes
659 | glUseProgram(ctx->hemisphere.downsamplePass.programID);
660 | glUniform1i(ctx->hemisphere.downsamplePass.hemispheresTextureID, 0);
661 | while (outHemiSize > 1)
662 | {
663 | LM_SWAP(int, fbRead, fbWrite);
664 | outHemiSize /= 2;
665 | glBindFramebuffer(GL_FRAMEBUFFER, ctx->hemisphere.fb[fbWrite]);
666 | glViewport(0, 0, outHemiSize * ctx->hemisphere.fbHemiCountX, outHemiSize * ctx->hemisphere.fbHemiCountY);
667 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.fbTexture[fbRead]);
668 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
669 | //glBindTexture(GL_TEXTURE_2D, 0);
670 | }
671 |
672 | // copy results to storage texture
673 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.storage.texture);
674 | glCopyTexSubImage2D(GL_TEXTURE_2D, 0,
675 | ctx->hemisphere.storage.writePosition.x, ctx->hemisphere.storage.writePosition.y,
676 | 0, 0, ctx->hemisphere.fbHemiCountX, ctx->hemisphere.fbHemiCountY);
677 | glBindTexture(GL_TEXTURE_2D, 0);
678 | glBindFramebuffer(GL_FRAMEBUFFER, 0);
679 | glBindVertexArray(0);
680 | glEnable(GL_DEPTH_TEST);
681 |
682 | // copy position mapping to storage
683 | for (unsigned int y = 0; y < ctx->hemisphere.fbHemiCountY; y++)
684 | {
685 | int sy = ctx->hemisphere.storage.writePosition.y + y;
686 | for (unsigned int x = 0; x < ctx->hemisphere.fbHemiCountX; x++)
687 | {
688 | int sx = ctx->hemisphere.storage.writePosition.x + x;
689 | unsigned int hemiIndex = y * ctx->hemisphere.fbHemiCountX + x;
690 | if (hemiIndex >= ctx->hemisphere.fbHemiIndex)
691 | ctx->hemisphere.storage.toLightmapLocation[sy * ctx->lightmap.width + sx] = lm_i2(-1, -1);
692 | else
693 | ctx->hemisphere.storage.toLightmapLocation[sy * ctx->lightmap.width + sx] = ctx->hemisphere.fbHemiToLightmapLocation[hemiIndex];
694 | }
695 | }
696 |
697 | // advance storage texture write position
698 | ctx->hemisphere.storage.writePosition.x += ctx->hemisphere.fbHemiCountX;
699 | if (ctx->hemisphere.storage.writePosition.x + (int)ctx->hemisphere.fbHemiCountX > ctx->lightmap.width)
700 | {
701 | ctx->hemisphere.storage.writePosition.x = 0;
702 | ctx->hemisphere.storage.writePosition.y += ctx->hemisphere.fbHemiCountY;
703 | assert(ctx->hemisphere.storage.writePosition.y + (int)ctx->hemisphere.fbHemiCountY < ctx->lightmap.height);
704 | }
705 |
706 | ctx->hemisphere.fbHemiIndex = 0;
707 | }
708 |
709 | static void lm_writeResultsToLightmap(lm_context *ctx)
710 | {
711 | // do the GPU->CPU transfer of downsampled hemispheres
712 | float *hemi = (float*)LM_CALLOC(ctx->lightmap.width * ctx->lightmap.height, 4 * sizeof(float));
713 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.storage.texture);
714 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, hemi);
715 |
716 | // write results to lightmap texture
717 | for (int y = 0; y < ctx->hemisphere.storage.writePosition.y + (int)ctx->hemisphere.fbHemiCountY; y++)
718 | {
719 | for (int x = 0; x < ctx->lightmap.width; x++)
720 | {
721 | lm_ivec2 lmUV = ctx->hemisphere.storage.toLightmapLocation[y * ctx->lightmap.width + x];
722 | if (lmUV.x >= 0)
723 | {
724 | float *c = hemi + (y * ctx->lightmap.width + x) * 4;
725 | float validity = c[3];
726 | float *lm = ctx->lightmap.data + (lmUV.y * ctx->lightmap.width + lmUV.x) * ctx->lightmap.channels;
727 | if (!lm[0] && validity > 0.9)
728 | {
729 | float scale = 1.0f / validity;
730 | switch (ctx->lightmap.channels)
731 | {
732 | case 1:
733 | lm[0] = lm_maxf((c[0] + c[1] + c[2]) * scale / 3.0f, FLT_MIN);
734 | break;
735 | case 2:
736 | lm[0] = lm_maxf((c[0] + c[1] + c[2]) * scale / 3.0f, FLT_MIN);
737 | lm[1] = 1.0f; // do we want to support this format?
738 | break;
739 | case 3:
740 | lm[0] = lm_maxf(c[0] * scale, FLT_MIN);
741 | lm[1] = lm_maxf(c[1] * scale, FLT_MIN);
742 | lm[2] = lm_maxf(c[2] * scale, FLT_MIN);
743 | break;
744 | case 4:
745 | lm[0] = lm_maxf(c[0] * scale, FLT_MIN);
746 | lm[1] = lm_maxf(c[1] * scale, FLT_MIN);
747 | lm[2] = lm_maxf(c[2] * scale, FLT_MIN);
748 | lm[3] = 1.0f;
749 | break;
750 | default:
751 | assert(LM_FALSE);
752 | break;
753 | }
754 |
755 | #ifdef LM_DEBUG_INTERPOLATION
756 | // set sampled pixel to red in debug output
757 | ctx->lightmap.debug[(lmUV.y * ctx->lightmap.width + lmUV.x) * 3 + 0] = 255;
758 | #endif
759 | }
760 | }
761 | ctx->hemisphere.storage.toLightmapLocation[y * ctx->lightmap.width + x].x = -1; // reset
762 | }
763 | }
764 |
765 | LM_FREE(hemi);
766 | ctx->hemisphere.storage.writePosition = lm_i2(0, 0);
767 | }
768 |
769 | static void lm_setView(
770 | int* viewport, int x, int y, int w, int h,
771 | float* view, lm_vec3 pos, lm_vec3 dir, lm_vec3 up,
772 | float* proj, float l, float r, float b, float t, float n, float f)
773 | {
774 | // viewport
775 | viewport[0] = x; viewport[1] = y; viewport[2] = w; viewport[3] = h;
776 |
777 | // view matrix: lookAt(pos, pos + dir, up)
778 | lm_vec3 side = lm_cross3(dir, up);
779 | //up = cross(side, dir);
780 | dir = lm_negate3(dir); pos = lm_negate3(pos);
781 | view[ 0] = side.x; view[ 1] = up.x; view[ 2] = dir.x; view[ 3] = 0.0f;
782 | view[ 4] = side.y; view[ 5] = up.y; view[ 6] = dir.y; view[ 7] = 0.0f;
783 | view[ 8] = side.z; view[ 9] = up.z; view[10] = dir.z; view[11] = 0.0f;
784 | view[12] = lm_dot3(side, pos); view[13] = lm_dot3(up, pos); view[14] = lm_dot3(dir, pos); view[15] = 1.0f;
785 |
786 | // projection matrix: frustum(l, r, b, t, n, f)
787 | float ilr = 1.0f / (r - l), ibt = 1.0f / (t - b), ninf = -1.0f / (f - n), n2 = 2.0f * n;
788 | proj[ 0] = n2 * ilr; proj[ 1] = 0.0f; proj[ 2] = 0.0f; proj[ 3] = 0.0f;
789 | proj[ 4] = 0.0f; proj[ 5] = n2 * ibt; proj[ 6] = 0.0f; proj[ 7] = 0.0f;
790 | proj[ 8] = (r + l) * ilr; proj[ 9] = (t + b) * ibt; proj[10] = (f + n) * ninf; proj[11] = -1.0f;
791 | proj[12] = 0.0f; proj[13] = 0.0f; proj[14] = f * n2 * ninf; proj[15] = 0.0f;
792 | }
793 |
794 | // returns true if a hemisphere side was prepared for rendering and
795 | // false if we finished the current hemisphere
796 | static lm_bool lm_beginSampleHemisphere(lm_context *ctx, int* viewport, float* view, float* proj)
797 | {
798 | if (ctx->meshPosition.hemisphere.side >= 5)
799 | return LM_FALSE;
800 |
801 | if (ctx->meshPosition.hemisphere.side == 0)
802 | {
803 | // prepare hemisphere
804 | glBindFramebuffer(GL_FRAMEBUFFER, ctx->hemisphere.fb[0]);
805 | if (ctx->hemisphere.fbHemiIndex == 0)
806 | {
807 | // prepare hemisphere batch
808 | glClearColor( // clear to valid background pixels!
809 | ctx->hemisphere.clearColor.r,
810 | ctx->hemisphere.clearColor.g,
811 | ctx->hemisphere.clearColor.b, 1.0f);
812 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
813 | }
814 | ctx->hemisphere.fbHemiToLightmapLocation[ctx->hemisphere.fbHemiIndex] =
815 | lm_i2(ctx->meshPosition.rasterizer.x, ctx->meshPosition.rasterizer.y);
816 | }
817 |
818 | // find the target position in the batch
819 | int x = (ctx->hemisphere.fbHemiIndex % ctx->hemisphere.fbHemiCountX) * ctx->hemisphere.size * 3;
820 | int y = (ctx->hemisphere.fbHemiIndex / ctx->hemisphere.fbHemiCountX) * ctx->hemisphere.size;
821 |
822 | int size = ctx->hemisphere.size;
823 | float zNear = ctx->hemisphere.zNear;
824 | float zFar = ctx->hemisphere.zFar;
825 |
826 | lm_vec3 pos = ctx->meshPosition.sample.position;
827 | lm_vec3 dir = ctx->meshPosition.sample.direction;
828 | lm_vec3 up = ctx->meshPosition.sample.up;
829 | lm_vec3 right = lm_cross3(dir, up);
830 |
831 | // find the view parameters of the hemisphere side that we will render next
832 | // hemisphere layout in the framebuffer:
833 | // +-------+---+---+-------+
834 | // | | | | D |
835 | // | C | R | L +-------+
836 | // | | | | U |
837 | // +-------+---+---+-------+
838 | switch (ctx->meshPosition.hemisphere.side)
839 | {
840 | case 0: // center
841 | lm_setView(viewport, x, y, size, size,
842 | view, pos, dir, up,
843 | proj, -zNear, zNear, -zNear, zNear, zNear, zFar);
844 | break;
845 | case 1: // right
846 | lm_setView(viewport, size + x, y, size / 2, size,
847 | view, pos, right, up,
848 | proj, -zNear, 0.0f, -zNear, zNear, zNear, zFar);
849 | break;
850 | case 2: // left
851 | lm_setView(viewport, size + x + size / 2, y, size / 2, size,
852 | view, pos, lm_negate3(right), up,
853 | proj, 0.0f, zNear, -zNear, zNear, zNear, zFar);
854 | break;
855 | case 3: // down
856 | lm_setView(viewport, 2 * size + x, y + size / 2, size, size / 2,
857 | view, pos, lm_negate3(up), dir,
858 | proj, -zNear, zNear, 0.0f, zNear, zNear, zFar);
859 | break;
860 | case 4: // up
861 | lm_setView(viewport, 2 * size + x, y, size, size / 2,
862 | view, pos, up, lm_negate3(dir),
863 | proj, -zNear, zNear, -zNear, 0.0f, zNear, zFar);
864 | break;
865 | default:
866 | assert(LM_FALSE);
867 | break;
868 | }
869 |
870 | return LM_TRUE;
871 | }
872 |
873 | static void lm_endSampleHemisphere(lm_context *ctx)
874 | {
875 | if (++ctx->meshPosition.hemisphere.side == 5)
876 | {
877 | // finish hemisphere
878 | glBindFramebuffer(GL_FRAMEBUFFER, 0);
879 | if (++ctx->hemisphere.fbHemiIndex == ctx->hemisphere.fbHemiCountX * ctx->hemisphere.fbHemiCountY)
880 | {
881 | // downsample new hemisphere batch and store the results
882 | lm_integrateHemisphereBatch(ctx);
883 | }
884 | }
885 | }
886 |
887 | static void lm_inverseTranspose(const float *m44, float *n33)
888 | {
889 | if (!m44)
890 | {
891 | n33[0] = 1.0f; n33[1] = 0.0f; n33[2] = 0.0f;
892 | n33[3] = 0.0f; n33[4] = 1.0f; n33[5] = 0.0f;
893 | n33[6] = 0.0f; n33[7] = 0.0f; n33[8] = 1.0f;
894 | return;
895 | }
896 |
897 | float determinant = m44[ 0] * (m44[ 5] * m44[10] - m44[ 9] * m44[ 6])
898 | - m44[ 1] * (m44[ 4] * m44[10] - m44[ 6] * m44[ 8])
899 | + m44[ 2] * (m44[ 4] * m44[ 9] - m44[ 5] * m44[ 8]);
900 |
901 | assert(fabs(determinant) > FLT_EPSILON);
902 | float rcpDeterminant = 1.0f / determinant;
903 |
904 | n33[0] = (m44[ 5] * m44[10] - m44[ 9] * m44[ 6]) * rcpDeterminant;
905 | n33[3] = -(m44[ 1] * m44[10] - m44[ 2] * m44[ 9]) * rcpDeterminant;
906 | n33[6] = (m44[ 1] * m44[ 6] - m44[ 2] * m44[ 5]) * rcpDeterminant;
907 | n33[1] = -(m44[ 4] * m44[10] - m44[ 6] * m44[ 8]) * rcpDeterminant;
908 | n33[4] = (m44[ 0] * m44[10] - m44[ 2] * m44[ 8]) * rcpDeterminant;
909 | n33[7] = -(m44[ 0] * m44[ 6] - m44[ 4] * m44[ 2]) * rcpDeterminant;
910 | n33[2] = (m44[ 4] * m44[ 9] - m44[ 8] * m44[ 5]) * rcpDeterminant;
911 | n33[5] = -(m44[ 0] * m44[ 9] - m44[ 8] * m44[ 1]) * rcpDeterminant;
912 | n33[8] = (m44[ 0] * m44[ 5] - m44[ 4] * m44[ 1]) * rcpDeterminant;
913 | }
914 |
915 | static lm_vec3 lm_transformNormal(const float *m, lm_vec3 n)
916 | {
917 | lm_vec3 r;
918 | r.x = m[0] * n.x + m[3] * n.y + m[6] * n.z;
919 | r.y = m[1] * n.x + m[4] * n.y + m[7] * n.z;
920 | r.z = m[2] * n.x + m[5] * n.y + m[8] * n.z;
921 | return r;
922 | }
923 |
924 | static lm_vec3 lm_transformPosition(const float *m, lm_vec3 v)
925 | {
926 | if (!m)
927 | return v;
928 | lm_vec3 r;
929 | r.x = m[0] * v.x + m[4] * v.y + m[ 8] * v.z + m[12];
930 | r.y = m[1] * v.x + m[5] * v.y + m[ 9] * v.z + m[13];
931 | r.z = m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14];
932 | float d = m[3] * v.x + m[7] * v.y + m[11] * v.z + m[15];
933 | assert(lm_absf(d - 1.0f) < 0.00001f); // could divide by d, but this shouldn't be a projection transform!
934 | return r;
935 | }
936 |
937 | static void lm_setMeshPosition(lm_context *ctx, unsigned int indicesTriangleBaseIndex)
938 | {
939 | // fetch triangle at the specified indicesTriangleBaseIndex
940 | ctx->meshPosition.triangle.baseIndex = indicesTriangleBaseIndex;
941 |
942 | // load and transform triangle to process next
943 | lm_vec2 uvMin = lm_v2(FLT_MAX, FLT_MAX), uvMax = lm_v2(-FLT_MAX, -FLT_MAX);
944 | lm_vec2 uvScale = lm_v2i(ctx->lightmap.width, ctx->lightmap.height);
945 | unsigned int vIndices[3];
946 | for (int i = 0; i < 3; i++)
947 | {
948 | // decode index
949 | unsigned int vIndex;
950 | switch (ctx->mesh.indicesType)
951 | {
952 | case LM_NONE:
953 | vIndex = ctx->meshPosition.triangle.baseIndex + i;
954 | break;
955 | case LM_UNSIGNED_BYTE:
956 | vIndex = ((const unsigned char*)ctx->mesh.indices + ctx->meshPosition.triangle.baseIndex)[i];
957 | break;
958 | case LM_UNSIGNED_SHORT:
959 | vIndex = ((const unsigned short*)ctx->mesh.indices + ctx->meshPosition.triangle.baseIndex)[i];
960 | break;
961 | case LM_UNSIGNED_INT:
962 | vIndex = ((const unsigned int*)ctx->mesh.indices + ctx->meshPosition.triangle.baseIndex)[i];
963 | break;
964 | default:
965 | assert(LM_FALSE);
966 | break;
967 | }
968 | vIndices[i] = vIndex;
969 |
970 | // decode and pre-transform vertex position
971 | const void *pPtr = ctx->mesh.positions + vIndex * ctx->mesh.positionsStride;
972 | lm_vec3 p;
973 | switch (ctx->mesh.positionsType)
974 | {
975 | // TODO: signed formats
976 | case LM_UNSIGNED_BYTE: {
977 | const unsigned char *uc = (const unsigned char*)pPtr;
978 | p = lm_v3(uc[0], uc[1], uc[2]);
979 | } break;
980 | case LM_UNSIGNED_SHORT: {
981 | const unsigned short *us = (const unsigned short*)pPtr;
982 | p = lm_v3(us[0], us[1], us[2]);
983 | } break;
984 | case LM_UNSIGNED_INT: {
985 | const unsigned int *ui = (const unsigned int*)pPtr;
986 | p = lm_v3((float)ui[0], (float)ui[1], (float)ui[2]);
987 | } break;
988 | case LM_FLOAT: {
989 | p = *(const lm_vec3*)pPtr;
990 | } break;
991 | default: {
992 | assert(LM_FALSE);
993 | } break;
994 | }
995 | ctx->meshPosition.triangle.p[i] = lm_transformPosition(ctx->mesh.modelMatrix, p);
996 |
997 | // decode and scale (to lightmap resolution) vertex lightmap texture coords
998 | const void *uvPtr = ctx->mesh.uvs + vIndex * ctx->mesh.uvsStride;
999 | lm_vec2 uv;
1000 | switch (ctx->mesh.uvsType)
1001 | {
1002 | case LM_UNSIGNED_BYTE: {
1003 | const unsigned char *uc = (const unsigned char*)uvPtr;
1004 | uv = lm_v2(uc[0] / (float)UCHAR_MAX, uc[1] / (float)UCHAR_MAX);
1005 | } break;
1006 | case LM_UNSIGNED_SHORT: {
1007 | const unsigned short *us = (const unsigned short*)uvPtr;
1008 | uv = lm_v2(us[0] / (float)USHRT_MAX, us[1] / (float)USHRT_MAX);
1009 | } break;
1010 | case LM_UNSIGNED_INT: {
1011 | const unsigned int *ui = (const unsigned int*)uvPtr;
1012 | uv = lm_v2(ui[0] / (float)UINT_MAX, ui[1] / (float)UINT_MAX);
1013 | } break;
1014 | case LM_FLOAT: {
1015 | uv = *(const lm_vec2*)uvPtr;
1016 | } break;
1017 | default: {
1018 | assert(LM_FALSE);
1019 | } break;
1020 | }
1021 |
1022 | ctx->meshPosition.triangle.uv[i] = lm_mul2(lm_pmod2(uv, 1.0f), uvScale); // maybe clamp to 0.0-1.0 instead of pmod?
1023 |
1024 | // update bounds on lightmap
1025 | uvMin = lm_min2(uvMin, ctx->meshPosition.triangle.uv[i]);
1026 | uvMax = lm_max2(uvMax, ctx->meshPosition.triangle.uv[i]);
1027 | }
1028 |
1029 | lm_vec3 flatNormal = lm_cross3(
1030 | lm_sub3(ctx->meshPosition.triangle.p[1], ctx->meshPosition.triangle.p[0]),
1031 | lm_sub3(ctx->meshPosition.triangle.p[2], ctx->meshPosition.triangle.p[0]));
1032 |
1033 | for (int i = 0; i < 3; i++)
1034 | {
1035 | // decode and pre-transform vertex normal
1036 | const void *nPtr = ctx->mesh.normals + vIndices[i] * ctx->mesh.normalsStride;
1037 | lm_vec3 n;
1038 | switch (ctx->mesh.normalsType)
1039 | {
1040 | // TODO: signed formats
1041 | case LM_FLOAT: {
1042 | n = *(const lm_vec3*)nPtr;
1043 | } break;
1044 | case LM_NONE: {
1045 | n = flatNormal;
1046 | } break;
1047 | default: {
1048 | assert(LM_FALSE);
1049 | } break;
1050 | }
1051 | ctx->meshPosition.triangle.n[i] = lm_normalize3(lm_transformNormal(ctx->mesh.normalMatrix, n));
1052 | }
1053 |
1054 | // calculate area of interest (on lightmap) for conservative rasterization
1055 | lm_vec2 bbMin = lm_floor2(uvMin);
1056 | lm_vec2 bbMax = lm_ceil2 (uvMax);
1057 | ctx->meshPosition.rasterizer.minx = lm_maxi((int)bbMin.x - 1, 0);
1058 | ctx->meshPosition.rasterizer.miny = lm_maxi((int)bbMin.y - 1, 0);
1059 | ctx->meshPosition.rasterizer.maxx = lm_mini((int)bbMax.x + 1, ctx->lightmap.width - 1);
1060 | ctx->meshPosition.rasterizer.maxy = lm_mini((int)bbMax.y + 1, ctx->lightmap.height - 1);
1061 | assert(ctx->meshPosition.rasterizer.minx <= ctx->meshPosition.rasterizer.maxx &&
1062 | ctx->meshPosition.rasterizer.miny <= ctx->meshPosition.rasterizer.maxy);
1063 | ctx->meshPosition.rasterizer.x = ctx->meshPosition.rasterizer.minx + lm_passOffsetX(ctx);
1064 | ctx->meshPosition.rasterizer.y = ctx->meshPosition.rasterizer.miny + lm_passOffsetY(ctx);
1065 |
1066 | // try moving to first valid sample position
1067 | if (ctx->meshPosition.rasterizer.x <= ctx->meshPosition.rasterizer.maxx &&
1068 | ctx->meshPosition.rasterizer.y <= ctx->meshPosition.rasterizer.maxy &&
1069 | lm_findFirstConservativeTriangleRasterizerPosition(ctx))
1070 | ctx->meshPosition.hemisphere.side = 0; // we can start sampling the hemisphere
1071 | else
1072 | ctx->meshPosition.hemisphere.side = 5; // no samples on this triangle! put hemisphere sampler into finished state
1073 | }
1074 |
1075 | static GLuint lm_LoadShader(GLenum type, const char *source)
1076 | {
1077 | GLuint shader = glCreateShader(type);
1078 | if (shader == 0)
1079 | {
1080 | fprintf(stderr, "Could not create shader!\n");
1081 | return 0;
1082 | }
1083 | glShaderSource(shader, 1, &source, NULL);
1084 | glCompileShader(shader);
1085 | GLint compiled;
1086 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
1087 | if (!compiled)
1088 | {
1089 | fprintf(stderr, "Could not compile shader!\n");
1090 | GLint infoLen = 0;
1091 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
1092 | if (infoLen)
1093 | {
1094 | char* infoLog = (char*)malloc(infoLen);
1095 | glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
1096 | fprintf(stderr, "%s\n", infoLog);
1097 | free(infoLog);
1098 | }
1099 | glDeleteShader(shader);
1100 | return 0;
1101 | }
1102 | return shader;
1103 | }
1104 |
1105 | static GLuint lm_LoadProgram(const char *vp, const char *fp)
1106 | {
1107 | GLuint program = glCreateProgram();
1108 | if (program == 0)
1109 | {
1110 | fprintf(stderr, "Could not create program!\n");
1111 | return 0;
1112 | }
1113 | GLuint vertexShader = lm_LoadShader(GL_VERTEX_SHADER, vp);
1114 | GLuint fragmentShader = lm_LoadShader(GL_FRAGMENT_SHADER, fp);
1115 | glAttachShader(program, vertexShader);
1116 | glAttachShader(program, fragmentShader);
1117 | glLinkProgram(program);
1118 | glDeleteShader(vertexShader);
1119 | glDeleteShader(fragmentShader);
1120 | GLint linked;
1121 | glGetProgramiv(program, GL_LINK_STATUS, &linked);
1122 | if (!linked)
1123 | {
1124 | fprintf(stderr, "Could not link program!\n");
1125 | GLint infoLen = 0;
1126 | glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
1127 | if (infoLen)
1128 | {
1129 | char* infoLog = (char*)malloc(sizeof(char) * infoLen);
1130 | glGetProgramInfoLog(program, infoLen, NULL, infoLog);
1131 | fprintf(stderr, "%s\n", infoLog);
1132 | free(infoLog);
1133 | }
1134 | glDeleteProgram(program);
1135 | return 0;
1136 | }
1137 | return program;
1138 | }
1139 |
1140 | static float lm_defaultWeights(float cos_theta, void *userdata)
1141 | {
1142 | return 1.0f;
1143 | }
1144 |
1145 | lm_context *lmCreate(int hemisphereSize, float zNear, float zFar,
1146 | float clearR, float clearG, float clearB,
1147 | int interpolationPasses, float interpolationThreshold,
1148 | float cameraToSurfaceDistanceModifier)
1149 | {
1150 | assert(hemisphereSize == 512 || hemisphereSize == 256 || hemisphereSize == 128 ||
1151 | hemisphereSize == 64 || hemisphereSize == 32 || hemisphereSize == 16);
1152 | assert(zNear < zFar && zNear > 0.0f);
1153 | assert(cameraToSurfaceDistanceModifier >= -1.0f);
1154 | assert(interpolationPasses >= 0 && interpolationPasses <= 8);
1155 | assert(interpolationThreshold >= 0.0f);
1156 |
1157 | lm_context *ctx = (lm_context*)LM_CALLOC(1, sizeof(lm_context));
1158 |
1159 | ctx->meshPosition.passCount = 1 + 3 * interpolationPasses;
1160 | ctx->interpolationThreshold = interpolationThreshold;
1161 | ctx->hemisphere.size = hemisphereSize;
1162 | ctx->hemisphere.zNear = zNear;
1163 | ctx->hemisphere.zFar = zFar;
1164 | ctx->hemisphere.cameraToSurfaceDistanceModifier = cameraToSurfaceDistanceModifier;
1165 | ctx->hemisphere.clearColor.r = clearR;
1166 | ctx->hemisphere.clearColor.g = clearG;
1167 | ctx->hemisphere.clearColor.b = clearB;
1168 |
1169 | // calculate hemisphere batch size
1170 | ctx->hemisphere.fbHemiCountX = 1536 / (3 * ctx->hemisphere.size);
1171 | ctx->hemisphere.fbHemiCountY = 512 / ctx->hemisphere.size;
1172 |
1173 | // hemisphere batch framebuffers
1174 | unsigned int w[] = {
1175 | ctx->hemisphere.fbHemiCountX * ctx->hemisphere.size * 3,
1176 | ctx->hemisphere.fbHemiCountX * ctx->hemisphere.size / 2 };
1177 | unsigned int h[] = {
1178 | ctx->hemisphere.fbHemiCountY * ctx->hemisphere.size,
1179 | ctx->hemisphere.fbHemiCountY * ctx->hemisphere.size / 2 };
1180 |
1181 | glGenTextures(2, ctx->hemisphere.fbTexture);
1182 | glGenFramebuffers(2, ctx->hemisphere.fb);
1183 | glGenRenderbuffers(1, &ctx->hemisphere.fbDepth);
1184 |
1185 | glBindRenderbuffer(GL_RENDERBUFFER, ctx->hemisphere.fbDepth);
1186 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, w[0], h[0]);
1187 | glBindFramebuffer(GL_FRAMEBUFFER, ctx->hemisphere.fb[0]);
1188 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ctx->hemisphere.fbDepth);
1189 | for (int i = 0; i < 2; i++)
1190 | {
1191 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.fbTexture[i]);
1192 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1193 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1194 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1195 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1196 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w[i], h[i], 0, GL_RGBA, GL_FLOAT, 0);
1197 |
1198 | glBindFramebuffer(GL_FRAMEBUFFER, ctx->hemisphere.fb[i]);
1199 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ctx->hemisphere.fbTexture[i], 0);
1200 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
1201 | if (status != GL_FRAMEBUFFER_COMPLETE)
1202 | {
1203 | fprintf(stderr, "Could not create framebuffer!\n");
1204 | glDeleteRenderbuffers(1, &ctx->hemisphere.fbDepth);
1205 | glDeleteFramebuffers(2, ctx->hemisphere.fb);
1206 | glDeleteTextures(2, ctx->hemisphere.fbTexture);
1207 | LM_FREE(ctx);
1208 | return NULL;
1209 | }
1210 | }
1211 | glBindFramebuffer(GL_FRAMEBUFFER, 0);
1212 |
1213 | // dummy vao for fullscreen quad rendering
1214 | glGenVertexArrays(1, &ctx->hemisphere.vao);
1215 |
1216 | // hemisphere shader (weighted downsampling of the 3x1 hemisphere layout to a 0.5x0.5 square)
1217 | {
1218 | const char *vs =
1219 | "#version 150 core\n"
1220 | "const vec2 ps[4] = vec2[](vec2(1, -1), vec2(1, 1), vec2(-1, -1), vec2(-1, 1));\n"
1221 | "void main()\n"
1222 | "{\n"
1223 | "gl_Position = vec4(ps[gl_VertexID], 0, 1);\n"
1224 | "}\n";
1225 | const char *fs =
1226 | "#version 150 core\n"
1227 | "uniform sampler2D hemispheres;\n"
1228 | "uniform sampler2D weights;\n"
1229 |
1230 | "layout(pixel_center_integer) in vec4 gl_FragCoord;\n" // whole integer values represent pixel centers, GL_ARB_fragment_coord_conventions
1231 |
1232 | "out vec4 outColor;\n"
1233 |
1234 | "vec4 weightedSample(ivec2 h_uv, ivec2 w_uv, ivec2 quadrant)\n"
1235 | "{\n"
1236 | "vec4 sample = texelFetch(hemispheres, h_uv + quadrant, 0);\n"
1237 | "vec2 weight = texelFetch(weights, w_uv + quadrant, 0).rg;\n"
1238 | "return vec4(sample.rgb * weight.r, sample.a * weight.g);\n"
1239 | "}\n"
1240 |
1241 | "vec4 threeWeightedSamples(ivec2 h_uv, ivec2 w_uv, ivec2 offset)\n"
1242 | "{\n" // horizontal triple sum
1243 | "vec4 sum = weightedSample(h_uv, w_uv, offset);\n"
1244 | "sum += weightedSample(h_uv, w_uv, offset + ivec2(2, 0));\n"
1245 | "sum += weightedSample(h_uv, w_uv, offset + ivec2(4, 0));\n"
1246 | "return sum;\n"
1247 | "}\n"
1248 |
1249 | "void main()\n"
1250 | "{\n" // this is a weighted sum downsampling pass (alpha component contains the weighted valid sample count)
1251 | "vec2 in_uv = gl_FragCoord.xy * vec2(6.0, 2.0) + vec2(0.5);\n"
1252 | "ivec2 h_uv = ivec2(in_uv);\n"
1253 | "ivec2 w_uv = ivec2(mod(in_uv, vec2(textureSize(weights, 0))));\n" // there's no integer modulo :(
1254 | "vec4 lb = threeWeightedSamples(h_uv, w_uv, ivec2(0, 0));\n"
1255 | "vec4 rb = threeWeightedSamples(h_uv, w_uv, ivec2(1, 0));\n"
1256 | "vec4 lt = threeWeightedSamples(h_uv, w_uv, ivec2(0, 1));\n"
1257 | "vec4 rt = threeWeightedSamples(h_uv, w_uv, ivec2(1, 1));\n"
1258 | "outColor = lb + rb + lt + rt;\n"
1259 | "}\n";
1260 | ctx->hemisphere.firstPass.programID = lm_LoadProgram(vs, fs);
1261 | if (!ctx->hemisphere.firstPass.programID)
1262 | {
1263 | fprintf(stderr, "Error loading the hemisphere first pass shader program... leaving!\n");
1264 | glDeleteVertexArrays(1, &ctx->hemisphere.vao);
1265 | glDeleteRenderbuffers(1, &ctx->hemisphere.fbDepth);
1266 | glDeleteFramebuffers(2, ctx->hemisphere.fb);
1267 | glDeleteTextures(2, ctx->hemisphere.fbTexture);
1268 | LM_FREE(ctx);
1269 | return NULL;
1270 | }
1271 | ctx->hemisphere.firstPass.hemispheresTextureID = glGetUniformLocation(ctx->hemisphere.firstPass.programID, "hemispheres");
1272 | ctx->hemisphere.firstPass.weightsTextureID = glGetUniformLocation(ctx->hemisphere.firstPass.programID, "weights");
1273 | }
1274 |
1275 | // downsample shader
1276 | {
1277 | const char *vs =
1278 | "#version 150 core\n"
1279 | "const vec2 ps[4] = vec2[](vec2(1, -1), vec2(1, 1), vec2(-1, -1), vec2(-1, 1));\n"
1280 | "void main()\n"
1281 | "{\n"
1282 | "gl_Position = vec4(ps[gl_VertexID], 0, 1);\n"
1283 | "}\n";
1284 | const char *fs =
1285 | "#version 150 core\n"
1286 | "uniform sampler2D hemispheres;\n"
1287 |
1288 | "layout(pixel_center_integer) in vec4 gl_FragCoord;\n" // whole integer values represent pixel centers, GL_ARB_fragment_coord_conventions
1289 |
1290 | "out vec4 outColor;\n"
1291 |
1292 | "void main()\n"
1293 | "{\n" // this is a sum downsampling pass (alpha component contains the weighted valid sample count)
1294 | "ivec2 h_uv = ivec2(gl_FragCoord.xy) * 2;\n"
1295 | "vec4 lb = texelFetch(hemispheres, h_uv + ivec2(0, 0), 0);\n"
1296 | "vec4 rb = texelFetch(hemispheres, h_uv + ivec2(1, 0), 0);\n"
1297 | "vec4 lt = texelFetch(hemispheres, h_uv + ivec2(0, 1), 0);\n"
1298 | "vec4 rt = texelFetch(hemispheres, h_uv + ivec2(1, 1), 0);\n"
1299 | "outColor = lb + rb + lt + rt;\n"
1300 | "}\n";
1301 | ctx->hemisphere.downsamplePass.programID = lm_LoadProgram(vs, fs);
1302 | if (!ctx->hemisphere.downsamplePass.programID)
1303 | {
1304 | fprintf(stderr, "Error loading the hemisphere downsample pass shader program... leaving!\n");
1305 | glDeleteProgram(ctx->hemisphere.firstPass.programID);
1306 | glDeleteVertexArrays(1, &ctx->hemisphere.vao);
1307 | glDeleteRenderbuffers(1, &ctx->hemisphere.fbDepth);
1308 | glDeleteFramebuffers(2, ctx->hemisphere.fb);
1309 | glDeleteTextures(2, ctx->hemisphere.fbTexture);
1310 | LM_FREE(ctx);
1311 | return NULL;
1312 | }
1313 | ctx->hemisphere.downsamplePass.hemispheresTextureID = glGetUniformLocation(ctx->hemisphere.downsamplePass.programID, "hemispheres");
1314 | }
1315 |
1316 | // hemisphere weights texture
1317 | glGenTextures(1, &ctx->hemisphere.firstPass.weightsTexture);
1318 | lmSetHemisphereWeights(ctx, lm_defaultWeights, 0);
1319 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1320 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1321 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1322 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1323 |
1324 | // allocate batchPosition-to-lightmapPosition map
1325 | ctx->hemisphere.fbHemiToLightmapLocation = (lm_ivec2*)LM_CALLOC(ctx->hemisphere.fbHemiCountX * ctx->hemisphere.fbHemiCountY, sizeof(lm_ivec2));
1326 |
1327 | return ctx;
1328 | }
1329 |
1330 | void lmDestroy(lm_context *ctx)
1331 | {
1332 | // reset state
1333 | glUseProgram(0);
1334 | glBindTexture(GL_TEXTURE_2D, 0);
1335 | glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
1336 | glBindVertexArray(0);
1337 | glBindFramebuffer(GL_FRAMEBUFFER, 0);
1338 | glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
1339 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
1340 |
1341 | // delete gl objects
1342 | glDeleteTextures(1, &ctx->hemisphere.firstPass.weightsTexture);
1343 | glDeleteTextures(1, &ctx->hemisphere.storage.texture);
1344 | glDeleteProgram(ctx->hemisphere.downsamplePass.programID);
1345 | glDeleteProgram(ctx->hemisphere.firstPass.programID);
1346 | glDeleteVertexArrays(1, &ctx->hemisphere.vao);
1347 | glDeleteRenderbuffers(1, &ctx->hemisphere.fbDepth);
1348 | glDeleteFramebuffers(2, ctx->hemisphere.fb);
1349 | glDeleteTextures(2, ctx->hemisphere.fbTexture);
1350 | glDeleteTextures(1, &ctx->hemisphere.storage.texture);
1351 |
1352 | // free memory
1353 | LM_FREE(ctx->hemisphere.storage.toLightmapLocation);
1354 | LM_FREE(ctx->hemisphere.fbHemiToLightmapLocation);
1355 | #ifdef LM_DEBUG_INTERPOLATION
1356 | LM_FREE(ctx->lightmap.debug);
1357 | #endif
1358 | LM_FREE(ctx);
1359 | }
1360 |
1361 | void lmSetHemisphereWeights(lm_context *ctx, lm_weight_func f, void *userdata)
1362 | {
1363 | // hemisphere weights texture. bakes in material dependent attenuation behaviour.
1364 | float *weights = (float*)LM_CALLOC(2 * 3 * ctx->hemisphere.size * ctx->hemisphere.size, sizeof(float));
1365 | float center = (ctx->hemisphere.size - 1) * 0.5f;
1366 | double sum = 0.0;
1367 | for (unsigned int y = 0; y < ctx->hemisphere.size; y++)
1368 | {
1369 | float dy = 2.0f * (y - center) / (float)ctx->hemisphere.size;
1370 | for (unsigned int x = 0; x < ctx->hemisphere.size; x++)
1371 | {
1372 | float dx = 2.0f * (x - center) / (float)ctx->hemisphere.size;
1373 | lm_vec3 v = lm_normalize3(lm_v3(dx, dy, 1.0f));
1374 |
1375 | float solidAngle = v.z * v.z * v.z;
1376 |
1377 | float *w0 = weights + 2 * (y * (3 * ctx->hemisphere.size) + x);
1378 | float *w1 = w0 + 2 * ctx->hemisphere.size;
1379 | float *w2 = w1 + 2 * ctx->hemisphere.size;
1380 |
1381 | // center weights
1382 | w0[0] = solidAngle * f(v.z, userdata);
1383 | w0[1] = solidAngle;
1384 |
1385 | // left/right side weights
1386 | w1[0] = solidAngle * f(lm_absf(v.x), userdata);
1387 | w1[1] = solidAngle;
1388 |
1389 | // up/down side weights
1390 | w2[0] = solidAngle * f(lm_absf(v.y), userdata);
1391 | w2[1] = solidAngle;
1392 |
1393 | sum += 3.0 * (double)solidAngle;
1394 | }
1395 | }
1396 |
1397 | // normalize weights
1398 | float weightScale = (float)(1.0 / sum);
1399 | for (unsigned int i = 0; i < 2 * 3 * ctx->hemisphere.size * ctx->hemisphere.size; i++)
1400 | weights[i] *= weightScale;
1401 |
1402 | // upload weight texture
1403 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.firstPass.weightsTexture);
1404 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, 3 * ctx->hemisphere.size, ctx->hemisphere.size, 0, GL_RG, GL_FLOAT, weights);
1405 | LM_FREE(weights);
1406 | }
1407 |
1408 | void lmSetTargetLightmap(lm_context *ctx, float *outLightmap, int w, int h, int c)
1409 | {
1410 | ctx->lightmap.data = outLightmap;
1411 | ctx->lightmap.width = w;
1412 | ctx->lightmap.height = h;
1413 | ctx->lightmap.channels = c;
1414 |
1415 | // allocate storage texture
1416 | if (!ctx->hemisphere.storage.texture)
1417 | glGenTextures(1, &ctx->hemisphere.storage.texture);
1418 | glBindTexture(GL_TEXTURE_2D, ctx->hemisphere.storage.texture);
1419 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1420 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1421 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1422 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1423 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_RGBA, GL_FLOAT, 0);
1424 |
1425 | // allocate storage position to lightmap position map
1426 | if (ctx->hemisphere.storage.toLightmapLocation)
1427 | LM_FREE(ctx->hemisphere.storage.toLightmapLocation);
1428 | ctx->hemisphere.storage.toLightmapLocation = (lm_ivec2*)LM_CALLOC(w * h, sizeof(lm_ivec2));
1429 | // invalidate all positions
1430 | for (int i = 0; i < w * h; i++)
1431 | ctx->hemisphere.storage.toLightmapLocation[i].x = -1;
1432 |
1433 | #ifdef LM_DEBUG_INTERPOLATION
1434 | if (ctx->lightmap.debug)
1435 | LM_FREE(ctx->lightmap.debug);
1436 | ctx->lightmap.debug = (unsigned char*)LM_CALLOC(ctx->lightmap.width * ctx->lightmap.height, 3);
1437 | #endif
1438 | }
1439 |
1440 | void lmSetGeometry(lm_context *ctx,
1441 | const float *transformationMatrix,
1442 | lm_type positionsType, const void *positionsXYZ, int positionsStride,
1443 | lm_type normalsType, const void *normalsXYZ, int normalsStride,
1444 | lm_type lightmapCoordsType, const void *lightmapCoordsUV, int lightmapCoordsStride,
1445 | int count, lm_type indicesType, const void *indices)
1446 | {
1447 | ctx->mesh.modelMatrix = transformationMatrix;
1448 | ctx->mesh.positions = (const unsigned char*)positionsXYZ;
1449 | ctx->mesh.positionsType = positionsType;
1450 | ctx->mesh.positionsStride = positionsStride == 0 ? sizeof(lm_vec3) : positionsStride;
1451 | ctx->mesh.normals = (const unsigned char*)normalsXYZ;
1452 | ctx->mesh.normalsType = normalsType;
1453 | ctx->mesh.normalsStride = normalsStride == 0 ? sizeof(lm_vec3) : normalsStride;
1454 | ctx->mesh.uvs = (const unsigned char*)lightmapCoordsUV;
1455 | ctx->mesh.uvsType = lightmapCoordsType;
1456 | ctx->mesh.uvsStride = lightmapCoordsStride == 0 ? sizeof(lm_vec2) : lightmapCoordsStride;
1457 | ctx->mesh.indicesType = indicesType;
1458 | ctx->mesh.indices = (const unsigned char*)indices;
1459 | ctx->mesh.count = count;
1460 |
1461 | lm_inverseTranspose(transformationMatrix, ctx->mesh.normalMatrix);
1462 |
1463 | ctx->meshPosition.pass = 0;
1464 | lm_setMeshPosition(ctx, 0);
1465 | }
1466 |
1467 | lm_bool lmBegin(lm_context *ctx, int* outViewport4, float* outView4x4, float* outProjection4x4)
1468 | {
1469 | assert(ctx->meshPosition.triangle.baseIndex < ctx->mesh.count);
1470 | while (!lm_beginSampleHemisphere(ctx, outViewport4, outView4x4, outProjection4x4))
1471 | { // as long as there are no hemisphere sides to render...
1472 | // try moving to the next rasterizer position
1473 | if (lm_findNextConservativeTriangleRasterizerPosition(ctx))
1474 | { // if we successfully moved to the next sample position on the current triangle...
1475 | ctx->meshPosition.hemisphere.side = 0; // start sampling a hemisphere there
1476 | }
1477 | else
1478 | { // if there are no valid sample positions on the current triangle...
1479 | if (ctx->meshPosition.triangle.baseIndex + 3 < ctx->mesh.count)
1480 | { // ...and there are triangles left: move to the next triangle and continue sampling.
1481 | lm_setMeshPosition(ctx, ctx->meshPosition.triangle.baseIndex + 3);
1482 | }
1483 | else
1484 | { // ...and there are no triangles left: finish
1485 | lm_integrateHemisphereBatch(ctx); // integrate and store last batch
1486 | lm_writeResultsToLightmap(ctx); // read storage data from gpu memory and write it to the lightmap
1487 |
1488 | if (++ctx->meshPosition.pass == ctx->meshPosition.passCount)
1489 | {
1490 | ctx->meshPosition.pass = 0;
1491 | ctx->meshPosition.triangle.baseIndex = ctx->mesh.count; // set end condition (in case someone accidentally calls lmBegin again)
1492 |
1493 | #ifdef LM_DEBUG_INTERPOLATION
1494 | lmImageSaveTGAub("debug_interpolation.tga", ctx->lightmap.debug, ctx->lightmap.width, ctx->lightmap.height, 3);
1495 |
1496 | // lightmap texel statistics
1497 | int rendered = 0, interpolated = 0, wasted = 0;
1498 | for (int y = 0; y < ctx->lightmap.height; y++)
1499 | {
1500 | for (int x = 0; x < ctx->lightmap.width; x++)
1501 | {
1502 | if (ctx->lightmap.debug[(y * ctx->lightmap.width + x) * 3 + 0])
1503 | rendered++;
1504 | else if (ctx->lightmap.debug[(y * ctx->lightmap.width + x) * 3 + 1])
1505 | interpolated++;
1506 | else
1507 | wasted++;
1508 | }
1509 | }
1510 | int used = rendered + interpolated;
1511 | int total = used + wasted;
1512 | printf("\n#######################################################################\n");
1513 | printf("%10d %6.2f%% rendered hemicubes integrated to lightmap texels.\n", rendered, 100.0f * (float)rendered / (float)total);
1514 | printf("%10d %6.2f%% interpolated lightmap texels.\n", interpolated, 100.0f * (float)interpolated / (float)total);
1515 | printf("%10d %6.2f%% wasted lightmap texels.\n", wasted, 100.0f * (float)wasted / (float)total);
1516 | printf("\n%17.2f%% of used texels were rendered.\n", 100.0f * (float)rendered / (float)used);
1517 | printf("#######################################################################\n");
1518 | #endif
1519 |
1520 | return LM_FALSE;
1521 | }
1522 |
1523 | lm_setMeshPosition(ctx, 0); // start over with the next pass
1524 | }
1525 | }
1526 | }
1527 | return LM_TRUE;
1528 | }
1529 |
1530 | float lmProgress(lm_context *ctx)
1531 | {
1532 | float passProgress = (float)ctx->meshPosition.triangle.baseIndex / (float)ctx->mesh.count;
1533 | return ((float)ctx->meshPosition.pass + passProgress) / (float)ctx->meshPosition.passCount;
1534 | }
1535 |
1536 | void lmEnd(lm_context *ctx)
1537 | {
1538 | lm_endSampleHemisphere(ctx);
1539 | }
1540 |
1541 | // these are not performance tuned since their impact on the whole lightmapping duration is insignificant
1542 | float lmImageMin(const float *image, int w, int h, int c, int m)
1543 | {
1544 | assert(c > 0 && m);
1545 | float minValue = FLT_MAX;
1546 | for (int i = 0; i < w * h; i++)
1547 | for (int j = 0; j < c; j++)
1548 | if (m & (1 << j))
1549 | minValue = lm_minf(minValue, image[i * c + j]);
1550 | return minValue;
1551 | }
1552 |
1553 | float lmImageMax(const float *image, int w, int h, int c, int m)
1554 | {
1555 | assert(c > 0 && m);
1556 | float maxValue = 0.0f;
1557 | for (int i = 0; i < w * h; i++)
1558 | for (int j = 0; j < c; j++)
1559 | if (m & (1 << j))
1560 | maxValue = lm_maxf(maxValue, image[i * c + j]);
1561 | return maxValue;
1562 | }
1563 |
1564 | void lmImageAdd(float *image, int w, int h, int c, float value, int m)
1565 | {
1566 | assert(c > 0 && m);
1567 | for (int i = 0; i < w * h; i++)
1568 | for (int j = 0; j < c; j++)
1569 | if (m & (1 << j))
1570 | image[i * c + j] += value;
1571 | }
1572 |
1573 | void lmImageScale(float *image, int w, int h, int c, float factor, int m)
1574 | {
1575 | assert(c > 0 && m);
1576 | for (int i = 0; i < w * h; i++)
1577 | for (int j = 0; j < c; j++)
1578 | if (m & (1 << j))
1579 | image[i * c + j] *= factor;
1580 | }
1581 |
1582 | void lmImagePower(float *image, int w, int h, int c, float exponent, int m)
1583 | {
1584 | assert(c > 0 && m);
1585 | for (int i = 0; i < w * h; i++)
1586 | for (int j = 0; j < c; j++)
1587 | if (m & (1 << j))
1588 | image[i * c + j] = powf(image[i * c + j], exponent);
1589 | }
1590 |
1591 | void lmImageDilate(const float *image, float *outImage, int w, int h, int c)
1592 | {
1593 | assert(c > 0 && c <= 4);
1594 | for (int y = 0; y < h; y++)
1595 | {
1596 | for (int x = 0; x < w; x++)
1597 | {
1598 | float color[4];
1599 | lm_bool valid = LM_FALSE;
1600 | for (int i = 0; i < c; i++)
1601 | {
1602 | color[i] = image[(y * w + x) * c + i];
1603 | valid |= color[i] > 0.0f;
1604 | }
1605 | if (!valid)
1606 | {
1607 | int n = 0;
1608 | const int dx[] = { -1, 0, 1, 0 };
1609 | const int dy[] = { 0, 1, 0, -1 };
1610 | for (int d = 0; d < 4; d++)
1611 | {
1612 | int cx = x + dx[d];
1613 | int cy = y + dy[d];
1614 | if (cx >= 0 && cx < w && cy >= 0 && cy < h)
1615 | {
1616 | float dcolor[4];
1617 | lm_bool dvalid = LM_FALSE;
1618 | for (int i = 0; i < c; i++)
1619 | {
1620 | dcolor[i] = image[(cy * w + cx) * c + i];
1621 | dvalid |= dcolor[i] > 0.0f;
1622 | }
1623 | if (dvalid)
1624 | {
1625 | for (int i = 0; i < c; i++)
1626 | color[i] += dcolor[i];
1627 | n++;
1628 | }
1629 | }
1630 | }
1631 | if (n)
1632 | {
1633 | float in = 1.0f / n;
1634 | for (int i = 0; i < c; i++)
1635 | color[i] *= in;
1636 | }
1637 | }
1638 | for (int i = 0; i < c; i++)
1639 | outImage[(y * w + x) * c + i] = color[i];
1640 | }
1641 | }
1642 | }
1643 |
1644 | void lmImageSmooth(const float *image, float *outImage, int w, int h, int c)
1645 | {
1646 | assert(c > 0 && c <= 4);
1647 | for (int y = 0; y < h; y++)
1648 | {
1649 | for (int x = 0; x < w; x++)
1650 | {
1651 | float color[4] = {0};
1652 | int n = 0;
1653 | for (int dy = -1; dy <= 1; dy++)
1654 | {
1655 | int cy = y + dy;
1656 | for (int dx = -1; dx <= 1; dx++)
1657 | {
1658 | int cx = x + dx;
1659 | if (cx >= 0 && cx < w && cy >= 0 && cy < h)
1660 | {
1661 | lm_bool valid = LM_FALSE;
1662 | for (int i = 0; i < c; i++)
1663 | valid |= image[(cy * w + cx) * c + i] > 0.0f;
1664 | if (valid)
1665 | {
1666 | for (int i = 0; i < c; i++)
1667 | color[i] += image[(cy * w + cx) * c + i];
1668 | n++;
1669 | }
1670 | }
1671 | }
1672 | }
1673 | for (int i = 0; i < c; i++)
1674 | outImage[(y * w + x) * c + i] = n ? color[i] / n : 0.0f;
1675 | }
1676 | }
1677 | }
1678 |
1679 | void lmImageDownsample(const float *image, float *outImage, int w, int h, int c)
1680 | {
1681 | assert(c > 0 && c <= 4);
1682 | for (int y = 0; y < h / 2; y++)
1683 | {
1684 | for (int x = 0; x < w / 2; x++)
1685 | {
1686 | int p0 = 2 * (y * w + x) * c;
1687 | int p1 = p0 + w * c;
1688 | int valid[2][2] = {0};
1689 | float sums[4] = {0};
1690 | for (int i = 0; i < c; i++)
1691 | {
1692 | valid[0][0] |= image[p0 + i] != 0.0f ? 1 : 0;
1693 | valid[0][1] |= image[p0 + c + i] != 0.0f ? 1 : 0;
1694 | valid[1][0] |= image[p1 + i] != 0.0f ? 1 : 0;
1695 | valid[1][1] |= image[p1 + c + i] != 0.0f ? 1 : 0;
1696 | sums[i] += image[p0 + i] + image[p0 + c + i] + image[p1 + i] + image[p1 + c + i];
1697 | }
1698 | int n = valid[0][0] + valid[0][1] + valid[1][0] + valid[1][1];
1699 | int p = (y * w / 2 + x) * c;
1700 | for (int i = 0; i < c; i++)
1701 | outImage[p + i] = n ? sums[i] / n : 0.0f;
1702 | }
1703 | }
1704 | }
1705 |
1706 | void lmImageFtoUB(const float *image, unsigned char *outImage, int w, int h, int c, float max)
1707 | {
1708 | assert(c > 0);
1709 | float scale = 255.0f / (max != 0.0f ? max : lmImageMax(image, w, h, c, LM_ALL_CHANNELS));
1710 | for (int i = 0; i < w * h * c; i++)
1711 | outImage[i] = (unsigned char)lm_minf(lm_maxf(image[i] * scale, 0.0f), 255.0f);
1712 | }
1713 |
1714 | // TGA output helpers
1715 | static void lm_swapRandBub(unsigned char *image, int w, int h, int c)
1716 | {
1717 | assert(c >= 3);
1718 | for (int i = 0; i < w * h * c; i += c)
1719 | LM_SWAP(unsigned char, image[i], image[i + 2]);
1720 | }
1721 |
1722 | lm_bool lmImageSaveTGAub(const char *filename, const unsigned char *image, int w, int h, int c)
1723 | {
1724 | assert(c == 1 || c == 3 || c == 4);
1725 | lm_bool isGreyscale = c == 1;
1726 | lm_bool hasAlpha = c == 4;
1727 | unsigned char header[18] = {
1728 | 0, 0, (unsigned char)(isGreyscale ? 3 : 2), 0, 0, 0, 0, 0, 0, 0, 0, 0,
1729 | (unsigned char)(w & 0xff), (unsigned char)((w >> 8) & 0xff), (unsigned char)(h & 0xff), (unsigned char)((h >> 8) & 0xff),
1730 | (unsigned char)(8 * c), (unsigned char)(hasAlpha ? 8 : 0)
1731 | };
1732 | #if defined(_MSC_VER) && _MSC_VER >= 1400
1733 | FILE *file;
1734 | if (fopen_s(&file, filename, "wb") != 0) return LM_FALSE;
1735 | #else
1736 | FILE *file = fopen(filename, "wb");
1737 | if (!file) return LM_FALSE;
1738 | #endif
1739 | fwrite(header, 1, sizeof(header), file);
1740 |
1741 | // we make sure to swap it back! trust me. :)
1742 | if (!isGreyscale)
1743 | lm_swapRandBub((unsigned char*)image, w, h, c);
1744 | fwrite(image, 1, w * h * c , file);
1745 | if (!isGreyscale)
1746 | lm_swapRandBub((unsigned char*)image, w, h, c);
1747 |
1748 | fclose(file);
1749 | return LM_TRUE;
1750 | }
1751 |
1752 | lm_bool lmImageSaveTGAf(const char *filename, const float *image, int w, int h, int c, float max)
1753 | {
1754 | unsigned char *temp = (unsigned char*)LM_CALLOC(w * h * c, sizeof(unsigned char));
1755 | lmImageFtoUB(image, temp, w, h, c, max);
1756 | lm_bool success = lmImageSaveTGAub(filename, temp, w, h, c);
1757 | LM_FREE(temp);
1758 | return success;
1759 | }
1760 |
1761 | #endif // LIGHTMAPPER_IMPLEMENTATION
1762 |
--------------------------------------------------------------------------------