408 lines
14 KiB
C
408 lines
14 KiB
C
#include "v4k.h"
|
|
|
|
#define LIGHTMAPPER_IMPLEMENTATION
|
|
// #define LM_DEBUG_INTERPOLATION
|
|
#include "3rd_lightmapper.h"
|
|
|
|
#define scene_t scene_t2
|
|
|
|
typedef struct {
|
|
float p[3];
|
|
float t[2];
|
|
} vertex_t;
|
|
|
|
typedef struct
|
|
{
|
|
GLuint program;
|
|
GLint u_lightmap;
|
|
GLint u_projection;
|
|
GLint u_view;
|
|
|
|
GLuint lightmap;
|
|
int w, h;
|
|
|
|
GLuint vao, vbo, ibo;
|
|
vertex_t *vertices;
|
|
unsigned short *indices;
|
|
unsigned int vertexCount, indexCount;
|
|
} scene_t;
|
|
|
|
static int initScene(scene_t *scene);
|
|
static void drawScene(scene_t *scene, float *view, float *projection);
|
|
static void destroyScene(scene_t *scene);
|
|
|
|
static int bake(scene_t *scene)
|
|
{
|
|
lm_context *ctx = lmCreate(
|
|
64, // hemisphere resolution (power of two, max=512)
|
|
0.001f, 100.0f, // zNear, zFar of hemisphere cameras
|
|
1.0f, 1.0f, 1.0f, // background color (white for ambient occlusion)
|
|
2, 0.01f, // lightmap interpolation threshold (small differences are interpolated rather than sampled)
|
|
// check debug_interpolation.tga for an overview of sampled (red) vs interpolated (green) pixels.
|
|
0.0f); // modifier for camera-to-surface distance for hemisphere rendering.
|
|
// tweak this to trade-off between interpolated normals quality and other artifacts (see declaration).
|
|
|
|
if (!ctx)
|
|
{
|
|
fprintf(stderr, "Error: Could not initialize lightmapper.\n");
|
|
return 0;
|
|
}
|
|
|
|
int w = scene->w, h = scene->h;
|
|
float *data = CALLOC(w * h * 4, sizeof(float));
|
|
for (int b = 0; b < 1; b++) {
|
|
memset(data, 0, w*h*4);
|
|
lmSetTargetLightmap(ctx, data, w, h, 4);
|
|
|
|
lmSetGeometry(ctx, NULL, // no transformation in this example
|
|
LM_FLOAT, (unsigned char*)scene->vertices + offsetof(vertex_t, p), sizeof(vertex_t),
|
|
LM_NONE , NULL , 0 , // no interpolated normals in this example
|
|
LM_FLOAT, (unsigned char*)scene->vertices + offsetof(vertex_t, t), sizeof(vertex_t),
|
|
scene->indexCount, LM_UNSIGNED_SHORT, scene->indices);
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
int vp[4];
|
|
float view[16], projection[16];
|
|
double lastUpdateTime = 0.0;
|
|
while (lmBegin(ctx, vp, view, projection))
|
|
{
|
|
// render to lightmapper framebuffer
|
|
glViewport(vp[0], vp[1], vp[2], vp[3]);
|
|
drawScene(scene, view, projection);
|
|
|
|
// display progress every second (printf is expensive)
|
|
double time = time_ms() / 1000.0;
|
|
if (time - lastUpdateTime > 1.0)
|
|
{
|
|
lastUpdateTime = time;
|
|
printf("\r%6.2f%%", lmProgress(ctx) * 100.0f);
|
|
fflush(stdout);
|
|
}
|
|
|
|
lmEnd(ctx);
|
|
// window_swap();
|
|
}
|
|
printf("\rFinished baking %d triangles.\n", scene->indexCount / 3);
|
|
}
|
|
|
|
lmDestroy(ctx);
|
|
|
|
// postprocess texture
|
|
float *temp = CALLOC(w * h * 4, sizeof(float));
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
lmImageDilate(data, temp, w, h, 4);
|
|
lmImageDilate(temp, data, w, h, 4);
|
|
}
|
|
lmImageSmooth(data, temp, w, h, 4);
|
|
lmImageDilate(temp, data, w, h, 4);
|
|
lmImagePower(data, w, h, 4, 1.0f / 2.2f, 0x7); // gamma correct color channels
|
|
FREE(temp);
|
|
|
|
// save result to a file
|
|
if (lmImageSaveTGAf("result.tga", data, w, h, 4, 1.0f))
|
|
printf("Saved result.tga\n");
|
|
|
|
// upload result
|
|
glBindTexture(GL_TEXTURE_2D, scene->lightmap);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_FLOAT, data);
|
|
FREE(data);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void fpsCameraViewMatrix(float *view);
|
|
|
|
static void mainLoop(scene_t *scene)
|
|
{
|
|
glViewport(0, 0, window_width(), window_height());
|
|
|
|
// camera for glfw window
|
|
float view[16], projection[16];
|
|
fpsCameraViewMatrix(view);
|
|
perspective44(projection, 45.0f, window_aspect(), 0.01f, 100.0f);
|
|
|
|
// draw to screen with a blueish sky
|
|
glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
drawScene(scene, view, projection);
|
|
}
|
|
|
|
int main()
|
|
{
|
|
window_create(0.5, WINDOW_VSYNC_DISABLED);
|
|
|
|
scene_t scene = {0};
|
|
if (!initScene(&scene))
|
|
{
|
|
fprintf(stderr, "Could not initialize scene.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
window_title("AO Baking demo");
|
|
|
|
while (window_swap())
|
|
{
|
|
mainLoop(&scene);
|
|
|
|
if( ui_panel("Lightmapper", PANEL_OPEN) ) {
|
|
ui_label2("Freecam", "Mouse + W/A/S/D/E/Q keys");
|
|
ui_label("Warning " ICON_MD_WARNING "@This will take a few seconds and bake a lightmap illuminated by: The mesh itself (initially black) + A white sky (1.0f, 1.0f, 1.0f)");
|
|
if( ui_button("Bake 1 light bounce") ) {
|
|
bake(&scene);
|
|
}
|
|
ui_panel_end();
|
|
}
|
|
}
|
|
|
|
destroyScene(&scene);
|
|
}
|
|
|
|
// helpers ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
static int loadSimpleObjFile(const char *filename, vertex_t **vertices, unsigned int *vertexCount, unsigned short **indices, unsigned int *indexCount);
|
|
|
|
static int initScene(scene_t *scene)
|
|
{
|
|
// load mesh
|
|
if (!loadSimpleObjFile("demos/art/meshes/gazebo.obj", &scene->vertices, &scene->vertexCount, &scene->indices, &scene->indexCount))
|
|
{
|
|
fprintf(stderr, "Error loading obj file\n");
|
|
return 0;
|
|
}
|
|
|
|
glGenVertexArrays(1, &scene->vao);
|
|
glBindVertexArray(scene->vao);
|
|
|
|
glGenBuffers(1, &scene->vbo);
|
|
glBindBuffer(GL_ARRAY_BUFFER, scene->vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, scene->vertexCount * sizeof(vertex_t), scene->vertices, GL_STATIC_DRAW);
|
|
|
|
glGenBuffers(1, &scene->ibo);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, scene->ibo);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, scene->indexCount * sizeof(unsigned short), scene->indices, GL_STATIC_DRAW);
|
|
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, p));
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), (void*)offsetof(vertex_t, t));
|
|
|
|
// create lightmap texture
|
|
scene->w = 654;
|
|
scene->h = 654;
|
|
glGenTextures(1, &scene->lightmap);
|
|
glBindTexture(GL_TEXTURE_2D, scene->lightmap);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
unsigned char emissive[] = { 0, 0, 0, 255 };
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, emissive);
|
|
|
|
// load shader
|
|
const char *vp =
|
|
"in vec3 a_position;\n"
|
|
"in vec2 a_texcoord;\n"
|
|
"uniform mat4 u_view;\n"
|
|
"uniform mat4 u_projection;\n"
|
|
"out vec2 v_texcoord;\n"
|
|
|
|
"void main()\n"
|
|
"{\n"
|
|
"gl_Position = u_projection * (u_view * vec4(a_position, 1.0));\n"
|
|
"v_texcoord = a_texcoord;\n"
|
|
"}\n";
|
|
|
|
const char *fp =
|
|
"in vec2 v_texcoord;\n"
|
|
"uniform sampler2D u_lightmap;\n"
|
|
"out vec4 o_color;\n"
|
|
|
|
"void main()\n"
|
|
"{\n"
|
|
"o_color = vec4(texture(u_lightmap, v_texcoord).rgb, gl_FrontFacing ? 1.0 : 0.0);\n"
|
|
"}\n";
|
|
|
|
scene->program = shader(vp, fp, "a_position,a_texcoord", "o_color", NULL);
|
|
if (!scene->program)
|
|
{
|
|
fprintf(stderr, "Error loading shader\n");
|
|
return 0;
|
|
}
|
|
scene->u_view = glGetUniformLocation(scene->program, "u_view");
|
|
scene->u_projection = glGetUniformLocation(scene->program, "u_projection");
|
|
scene->u_lightmap = glGetUniformLocation(scene->program, "u_lightmap");
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void drawScene(scene_t *scene, float *view, float *projection)
|
|
{
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
glUseProgram(scene->program);
|
|
glUniform1i(scene->u_lightmap, 0);
|
|
glUniformMatrix4fv(scene->u_projection, 1, GL_FALSE, projection);
|
|
glUniformMatrix4fv(scene->u_view, 1, GL_FALSE, view);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, scene->lightmap);
|
|
|
|
glBindVertexArray(scene->vao);
|
|
glDrawElements(GL_TRIANGLES, scene->indexCount, GL_UNSIGNED_SHORT, 0);
|
|
}
|
|
|
|
static void destroyScene(scene_t *scene)
|
|
{
|
|
FREE(scene->vertices);
|
|
FREE(scene->indices);
|
|
glDeleteVertexArrays(1, &scene->vao);
|
|
glDeleteBuffers(1, &scene->vbo);
|
|
glDeleteBuffers(1, &scene->ibo);
|
|
glDeleteTextures(1, &scene->lightmap);
|
|
glDeleteProgram(scene->program);
|
|
}
|
|
|
|
static int loadSimpleObjFile(const char *filename, vertex_t **vertices, unsigned int *vertexCount, unsigned short **indices, unsigned int *indexCount)
|
|
{
|
|
FILE *file = fopen(filename, "rt");
|
|
if (!file)
|
|
return 0;
|
|
char line[1024];
|
|
|
|
// first pass
|
|
unsigned int np = 0, nn = 0, nt = 0, nf = 0;
|
|
while (!feof(file))
|
|
{
|
|
fgets(line, 1024, file);
|
|
if (line[0] == '#') continue;
|
|
if (line[0] == 'v')
|
|
{
|
|
if (line[1] == ' ') { np++; continue; }
|
|
if (line[1] == 'n') { nn++; continue; }
|
|
if (line[1] == 't') { nt++; continue; }
|
|
assert(!"unknown vertex attribute");
|
|
}
|
|
if (line[0] == 'f') { nf++; continue; }
|
|
assert(!"unknown identifier");
|
|
}
|
|
assert(np && np == nn && np == nt && nf); // only supports obj files without separately indexed vertex attributes
|
|
|
|
// allocate memory
|
|
*vertexCount = np;
|
|
*vertices = CALLOC(np, sizeof(vertex_t));
|
|
*indexCount = nf * 3;
|
|
*indices = CALLOC(nf * 3, sizeof(unsigned short));
|
|
|
|
// second pass
|
|
fseek(file, 0, SEEK_SET);
|
|
unsigned int cp = 0, cn = 0, ct = 0, cf = 0;
|
|
while (!feof(file))
|
|
{
|
|
fgets(line, 1024, file);
|
|
if (line[0] == '#') continue;
|
|
if (line[0] == 'v')
|
|
{
|
|
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; }
|
|
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
|
|
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; }
|
|
assert(!"unknown vertex attribute");
|
|
}
|
|
if (line[0] == 'f')
|
|
{
|
|
unsigned short *tri = (*indices) + cf;
|
|
cf += 3;
|
|
char *e1, *e2, *e3 = line + 1;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
unsigned long pi = strtoul(e3 + 1, &e1, 10);
|
|
assert(e1[0] == '/');
|
|
unsigned long ti = strtoul(e1 + 1, &e2, 10);
|
|
assert(e2[0] == '/');
|
|
unsigned long ni = strtoul(e2 + 1, &e3, 10);
|
|
assert(pi == ti && pi == ni);
|
|
tri[i] = (unsigned short)(pi - 1);
|
|
}
|
|
continue;
|
|
}
|
|
assert(!"unknown identifier");
|
|
}
|
|
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
static void fpsCameraViewMatrix(float *view)
|
|
{
|
|
// initial camera config
|
|
static float position[] = { 0.0f, 0.3f, 1.5f };
|
|
static float rotation[] = { 0.0f, 0.0f };
|
|
|
|
// mouse look
|
|
static double lastMouse[] = { 0.0, 0.0 };
|
|
double mouse_coord[2];
|
|
mouse_coord[0] = input(MOUSE_X);
|
|
mouse_coord[1] = input(MOUSE_Y);
|
|
if (input(MOUSE_L))
|
|
{
|
|
rotation[0] += (float)(mouse_coord[1] - lastMouse[1]) * -0.2f;
|
|
rotation[1] += (float)(mouse_coord[0] - lastMouse[0]) * -0.2f;
|
|
}
|
|
lastMouse[0] = mouse_coord[0];
|
|
lastMouse[1] = mouse_coord[1];
|
|
|
|
float rotationY[16], rotationX[16], rotationYX[16];
|
|
rotation44(rotationX, rotation[0], 1.0f, 0.0f, 0.0f);
|
|
rotation44(rotationY, rotation[1], 0.0f, 1.0f, 0.0f);
|
|
multiply44x2(rotationYX, rotationY, rotationX);
|
|
|
|
// keyboard movement (WSADEQ)
|
|
float speed = input(KEY_SHIFT) ? 0.1f : 0.01f;
|
|
vec3 movement = {0};
|
|
if (input(KEY_W)) movement.z -= speed;
|
|
if (input(KEY_S)) movement.z += speed;
|
|
if (input(KEY_A)) movement.x -= speed;
|
|
if (input(KEY_D)) movement.x += speed;
|
|
if (input(KEY_E)) movement.y -= speed;
|
|
if (input(KEY_Q)) movement.y += speed;
|
|
|
|
vec3 worldMovement = transform344(rotationYX, movement);
|
|
position[0] += worldMovement.x;
|
|
position[1] += worldMovement.y;
|
|
position[2] += worldMovement.z;
|
|
|
|
// construct view matrix
|
|
float inverseRotation[16], inverseTranslation[16];
|
|
transpose44(inverseRotation, rotationYX);
|
|
translation44(inverseTranslation, -position[0], -position[1], -position[2]);
|
|
multiply44x2(view, inverseRotation, inverseTranslation); // = inverse(translation(position) * rotationYX);
|
|
}
|
|
|
|
#if 0
|
|
|
|
#######################################################################
|
|
76702 17.93% rendered hemicubes integrated to lightmap texels.
|
|
179388 41.94% interpolated lightmap texels.
|
|
171626 40.13% wasted lightmap texels.
|
|
|
|
29.95% of used texels were rendered.
|
|
#######################################################################
|
|
Finished baking 731 triangles.
|
|
Saved result.tga
|
|
|
|
vs
|
|
|
|
#######################################################################
|
|
124 0.05% rendered hemicubes integrated to lightmap texels.
|
|
201 0.08% interpolated lightmap texels.
|
|
261947 99.88% wasted lightmap texels.
|
|
|
|
38.15% of used texels were rendered.
|
|
#######################################################################
|
|
Finished baking 731 triangles.
|
|
Saved result.tga
|
|
|
|
#endif
|
|
|