├── .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 | ![Lightmapper Example Screenshot](https://github.com/ands/lightmapper/raw/master/example_images/example.png) 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 | ![Interpolated texels](https://github.com/ands/lightmapper/raw/master/example_images/debug_interpolation.png) 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 | Showing Some Editor Features (Master Thesis) 116 | 117 |

118 | 119 | ![Sphere Light](https://github.com/ands/lightmapper/raw/master/example_images/gazebo_light.png) 120 | ![Skybox](https://github.com/ands/lightmapper/raw/master/example_images/gazebo_skybox.png) 121 | ![Shadows and Reflections](https://github.com/ands/lightmapper/raw/master/example_images/gazebo_shadows_reflections.png) 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 | --------------------------------------------------------------------------------