Commit: 1a87fc8b28e9232f27f13573cfe07b5fd6168f58
Parent: 9b9d12a749b6d314cc435b108e39e693370dc01d
Author: Randy Palamar
Date: Sun, 25 May 2025 11:46:42 -0600
add multisampling, camera fov, multiple volume support
Diffstat:
5 files changed, 165 insertions(+), 85 deletions(-)
diff --git a/common.c b/common.c
@@ -6,30 +6,52 @@
#include <stdio.h>
#include <stdarg.h>
-global v3 min_coord_mm = {.x = -18.5, .y = -9.6, .z = 5};
-global v3 max_coord_mm = {.x = 18.5, .y = 9.6, .z = 42};
-
/* NOTE(rnp): for video output we will render a full rotation in this much time at the
* the specified frame rate */
#define OUTPUT_TIME_SECONDS 8.0f
#define OUTPUT_FRAME_RATE 30.0f
#define OUTPUT_BG_CLEAR_COLOUR (v4){{0.05, 0.05, 0.05, 1}}
-#define RENDER_TARGET_SIZE 1024, 1024
-#define CAMERA_ELEVATION_ANGLE 30.0f
+#define RENDER_MSAA_SAMPLES 8
+#define RENDER_TARGET_SIZE 1920, 1080
+#define CAMERA_ELEVATION_ANGLE 25.0f
+#define CAMERA_RADIUS 200.0f
#define BOUNDING_BOX_COLOUR 0.78, 0.07, 0.20, 1
-#define BOUNDING_BOX_FRACTION 0.005f
+#define BOUNDING_BOX_FRACTION 0.007f
+
+typedef struct {
+ c8 *file_path;
+ u32 width; /* number of points in data */
+ u32 height;
+ u32 depth;
+ v3 min_coord_mm;
+ v3 max_coord_mm;
+ f32 clip_fraction; /* fraction of half volume used to create pyramidal shape (0 for cube) */
+ f32 threshold;
+ f32 translate_x; /* mm to translate by when multi display is active */
+ b32 swizzle; /* 1 -> swap y-z coordinates when sampling texture */
+ b32 multi_file; /* 1 -> depth == N-frames, file_path == fmt string */
+ u32 texture;
+} VolumeDisplayItem;
+
+#define DRAW_ALL_VOLUMES 0
+global u32 single_volume_index = 0;
+global VolumeDisplayItem volumes[] = {
+ /* WALKING FORCES */
+ {"./data/test/frame_%02u.bin", 512, 1024, 64, {{-18.5, -9.6, 5}}, {{18.5, 9.6, 42}}, 0.58, 62, 0, 0, 1},
+};
#define MODEL_RENDER_MODEL_MATRIX_LOC (0)
#define MODEL_RENDER_VIEW_MATRIX_LOC (1)
#define MODEL_RENDER_PROJ_MATRIX_LOC (2)
-#define MODEL_RENDER_LOG_SCALE_LOC (3)
-#define MODEL_RENDER_DYNAMIC_RANGE_LOC (4)
-#define MODEL_RENDER_THRESHOLD_LOC (5)
-#define MODEL_RENDER_GAMMA_LOC (6)
-#define MODEL_RENDER_BB_COLOUR_LOC (7)
-#define MODEL_RENDER_BB_FRACTION_LOC (8)
+#define MODEL_RENDER_CLIP_FRACTION_LOC (3)
+#define MODEL_RENDER_LOG_SCALE_LOC (4)
+#define MODEL_RENDER_DYNAMIC_RANGE_LOC (5)
+#define MODEL_RENDER_THRESHOLD_LOC (6)
+#define MODEL_RENDER_GAMMA_LOC (7)
+#define MODEL_RENDER_BB_COLOUR_LOC (8)
+#define MODEL_RENDER_BB_FRACTION_LOC (9)
#define BG_CLEAR_COLOUR (v4){{0.12, 0.1, 0.1, 1}}
@@ -166,14 +188,14 @@ function FILE_WATCH_CALLBACK_FN(reload_shader)
return 1;
}
-function Texture
+function u32
load_complex_texture(Arena arena, c8 *file_path, b32 multi_file, u32 width, u32 height, u32 depth)
{
- Texture result = {0};
- glCreateTextures(GL_TEXTURE_3D, 1, &result.texture);
- glTextureStorage3D(result.texture, 1, GL_RG32F, width, height, depth);
- glTextureParameteri(result.texture, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
- glTextureParameteri(result.texture, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
+ u32 result = 0;
+ glCreateTextures(GL_TEXTURE_3D, 1, &result);
+ glTextureStorage3D(result, 1, GL_RG32F, width, height, depth);
+ glTextureParameteri(result, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
+ glTextureParameteri(result, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
if (multi_file) {
/* NOTE(rnp): assumes single plane */
@@ -182,13 +204,11 @@ load_complex_texture(Arena arena, c8 *file_path, b32 multi_file, u32 width, u32
stream_printf(&spath, file_path, i);
str8 path = arena_stream_commit_zero(&arena, &spath);
str8 raw = os_read_whole_file(&arena, (char *)path.data);
- glTextureSubImage3D(result.texture, 0, 0, 0, i, width, height, 1, GL_RG,
- GL_FLOAT, raw.data);
+ glTextureSubImage3D(result, 0, 0, 0, i, width, height, 1, GL_RG, GL_FLOAT, raw.data);
}
} else {
str8 raw = os_read_whole_file(&arena, file_path);
- glTextureSubImage3D(result.texture, 0, 0, 0, 0, width, height, depth, GL_RG,
- GL_FLOAT, raw.data);
+ glTextureSubImage3D(result, 0, 0, 0, 0, width, height, depth, GL_RG, GL_FLOAT, raw.data);
}
return result;
}
@@ -236,7 +256,7 @@ function void
scroll_callback(GLFWwindow *window, f64 x, f64 y)
{
ViewerContext *ctx = glfwGetWindowUserPointer(window);
- ctx->camera_radius += y;
+ ctx->camera_fov += y;
}
function void
@@ -246,6 +266,14 @@ key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifier
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
ctx->should_exit = 1;
+ if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
+ ctx->demo_mode = !ctx->demo_mode;
+
+ if (key == GLFW_KEY_RIGHT && (action == GLFW_PRESS || action == GLFW_REPEAT))
+ ctx->input_dt += 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
+ if (key == GLFW_KEY_LEFT && (action == GLFW_PRESS || action == GLFW_REPEAT))
+ ctx->input_dt -= 4.0f / (OUTPUT_TIME_SECONDS * OUTPUT_FRAME_RATE);
+
ctx->camera_angle += (key == GLFW_KEY_W && action != GLFW_RELEASE) * 5 * PI / 180.0f;
ctx->camera_angle -= (key == GLFW_KEY_S && action != GLFW_RELEASE) * 5 * PI / 180.0f;
}
@@ -260,12 +288,15 @@ fb_callback(GLFWwindow *window, s32 w, s32 h)
function void
init_viewer(ViewerContext *ctx)
{
+ ctx->demo_mode = 1;
ctx->window_size = (sv2){.w = 640, .h = 640};
- ctx->camera_radius = 80;
+ ctx->camera_radius = CAMERA_RADIUS;
ctx->camera_angle = -CAMERA_ELEVATION_ANGLE * PI / 180.0f;
+ ctx->camera_fov = 60.0f;
if (!glfwInit()) os_fatal(str8("failed to start glfw\n"));
+
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
@@ -290,22 +321,37 @@ init_viewer(ViewerContext *ctx)
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#endif
+ glEnable(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
RenderContext *rc = &ctx->model_render_context;
- RenderTarget *rt = &ctx->output_target;
+ RenderTarget *rt = &ctx->multisample_target;
+ rt->size = (sv2){{RENDER_TARGET_SIZE}};
+ glCreateRenderbuffers(countof(rt->textures), rt->textures);
+ glNamedRenderbufferStorageMultisample(rt->textures[0], RENDER_MSAA_SAMPLES,
+ GL_RGBA8, RENDER_TARGET_SIZE);
+ glNamedRenderbufferStorageMultisample(rt->textures[1], RENDER_MSAA_SAMPLES,
+ GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
+ glCreateFramebuffers(1, &rt->fb);
+ glNamedFramebufferRenderbuffer(rt->fb, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rt->textures[0]);
+ glNamedFramebufferRenderbuffer(rt->fb, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rt->textures[1]);
+
+ rt = &ctx->output_target;
glCreateTextures(GL_TEXTURE_2D, countof(rt->textures), rt->textures);
rt->size = (sv2){{RENDER_TARGET_SIZE}};
- glTextureStorage2D(rt->textures[0], 1, GL_RGBA8, RENDER_TARGET_SIZE);
+ glTextureStorage2D(rt->textures[0], 8, GL_RGBA8, RENDER_TARGET_SIZE);
glTextureStorage2D(rt->textures[1], 1, GL_DEPTH_COMPONENT24, RENDER_TARGET_SIZE);
glCreateFramebuffers(1, &rt->fb);
glNamedFramebufferTexture(rt->fb, GL_COLOR_ATTACHMENT0, rt->textures[0], 0);
glNamedFramebufferTexture(rt->fb, GL_DEPTH_ATTACHMENT, rt->textures[1], 0);
+ glTextureParameteri(rt->textures[0], GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTextureParameteri(rt->textures[0], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+
ShaderReloadContext *model_rc = push_struct(&ctx->arena, ShaderReloadContext);
model_rc->render_context = rc;
model_rc->vertex_text = str8(""
@@ -318,16 +364,17 @@ init_viewer(ViewerContext *ctx)
"layout(location = 1) out vec3 f_texture_coordinate;\n"
"layout(location = 2) out vec3 f_orig_texture_coordinate;\n"
"\n"
- "layout(location = " str(MODEL_RENDER_MODEL_MATRIX_LOC) ") uniform mat4 u_model;\n"
- "layout(location = " str(MODEL_RENDER_VIEW_MATRIX_LOC) ") uniform mat4 u_view;\n"
- "layout(location = " str(MODEL_RENDER_PROJ_MATRIX_LOC) ") uniform mat4 u_projection;\n"
+ "layout(location = " str(MODEL_RENDER_MODEL_MATRIX_LOC) ") uniform mat4 u_model;\n"
+ "layout(location = " str(MODEL_RENDER_VIEW_MATRIX_LOC) ") uniform mat4 u_view;\n"
+ "layout(location = " str(MODEL_RENDER_PROJ_MATRIX_LOC) ") uniform mat4 u_projection;\n"
+ "layout(location = " str(MODEL_RENDER_CLIP_FRACTION_LOC) ") uniform float u_clip_fraction = 1;\n"
"\n"
"\n"
"void main()\n"
"{\n"
"\tvec3 pos = v_position;\n"
"\tf_orig_texture_coordinate = (v_position + 1) / 2;\n"
- "\tif (v_position.y == -1) pos.x = clamp(v_position.x, -0.42, 0.42);\n"
+ "\tif (v_position.y == -1) pos.x = clamp(v_position.x, -u_clip_fraction, u_clip_fraction);\n"
"\tf_texture_coordinate = (pos + 1) / 2;\n"
//"\tf_normal = normalize(mat3(u_model) * v_normal);\n"
"\tf_normal = v_normal;\n"
@@ -411,9 +458,6 @@ init_viewer(ViewerContext *ctx)
ctx->unit_cube = load_render_model(ctx->arena, "unit_cube_positions.bin",
"unit_cube_indices.bin", "unit_cube_normals.bin");
-
- c8 *walking_cysts = "./data/test/frame_%02u.bin";
- ctx->view_texture = load_complex_texture(ctx->arena, walking_cysts, 1, 512, 1024, 64);
}
function void
@@ -437,7 +481,33 @@ set_camera(u32 program, u32 location, v3 position, v3 normal, v3 orthogonal)
}
function void
-viewer_frame_step(ViewerContext *ctx, f32 dt)
+draw_volume_item(ViewerContext *ctx, VolumeDisplayItem *v)
+{
+ if (!v->texture) {
+ v->texture = load_complex_texture(ctx->arena, v->file_path, v->multi_file,
+ v->width, v->height, v->depth);
+ }
+
+ u32 program = ctx->model_render_context.shader;
+ v3 scale = v3_sub(v->max_coord_mm, v->min_coord_mm);
+ m4 model_transform;
+ model_transform.c[0] = (v4){{scale.x, 0, 0, 0}};
+ model_transform.c[1] = (v4){{0, scale.z, 0, 0}};
+ model_transform.c[2] = (v4){{0, 0, scale.y, 0}};
+ model_transform.c[3] = (v4){{v->translate_x, 0, 0, 1}};
+ glProgramUniformMatrix4fv(program, MODEL_RENDER_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
+
+ glProgramUniform1f(program, MODEL_RENDER_CLIP_FRACTION_LOC, 1 - v->clip_fraction);
+ glProgramUniform1f(program, MODEL_RENDER_THRESHOLD_LOC, v->threshold);
+
+ glBindTextureUnit(0, v->texture);
+ glBindVertexArray(ctx->unit_cube.vao);
+ glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT,
+ (void *)ctx->unit_cube.elements_offset);
+}
+
+function void
+update_scene(ViewerContext *ctx, f32 dt)
{
ctx->cycle_t += 0.25 * dt;
if (ctx->cycle_t > 1) ctx->cycle_t -= 1;
@@ -447,50 +517,58 @@ viewer_frame_step(ViewerContext *ctx, f32 dt)
ctx->camera_position.z = -ctx->camera_radius * sin_f32(angle);
ctx->camera_position.y = ctx->camera_radius * tan_f32(ctx->camera_angle);
- //////////////
- // 3D Models
+ RenderTarget *rt = &ctx->multisample_target;
f32 one = 1;
- glBindFramebuffer(GL_FRAMEBUFFER, ctx->output_target.fb);
- glClearNamedFramebufferfv(ctx->output_target.fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
- glClearNamedFramebufferfv(ctx->output_target.fb, GL_DEPTH, 0, &one);
- glViewport(0, 0, ctx->output_target.size.w, ctx->output_target.size.h);
-
- glUseProgram(ctx->model_render_context.shader);
-
- /* TODO(rnp): this needs to be set on hot reload */
- /* TODO(rnp): think about a reasonable region (probably min_coord -> max_coord + 10%) */
- f32 n = 1;
- f32 f = 200;
- f32 a = -f / (f - n);
- f32 b = -f * n / (f - n);
+ glBindFramebuffer(GL_FRAMEBUFFER, rt->fb);
+ glClearNamedFramebufferfv(rt->fb, GL_COLOR, 0, OUTPUT_BG_CLEAR_COLOUR.E);
+ glClearNamedFramebufferfv(rt->fb, GL_DEPTH, 0, &one);
+ glViewport(0, 0, rt->size.w, rt->size.h);
+
+ u32 program = ctx->model_render_context.shader;
+ glUseProgram(program);
+
+ /* TODO(rnp): set this on hot reload instead of every frame */
+ v2 points = {{RENDER_TARGET_SIZE}};
+ f32 n = 0.1f;
+ f32 f = 400.0f;
+ f32 r = n * tan_f32(ctx->camera_fov / 2 * PI / 180.0f);
+ f32 t = r * points.h / points.w;
+ f32 a = -(f + n) / (f - n);
+ f32 b = -2 * f * n / (f - n);
+
m4 projection;
- projection.c[0] = (v4){{1, 0, 0, 0}};
- projection.c[1] = (v4){{0, 1, 0, 0}};
- projection.c[2] = (v4){{0, 0, a, -1}};
- projection.c[3] = (v4){{0, 0, b, 0}};
- glProgramUniformMatrix4fv(ctx->model_render_context.shader, MODEL_RENDER_PROJ_MATRIX_LOC,
- 1, 0, projection.E);
+ projection.c[0] = (v4){{n / r, 0, 0, 0}};
+ projection.c[1] = (v4){{0, n / t, 0, 0}};
+ projection.c[2] = (v4){{0, 0, a, -1}};
+ projection.c[3] = (v4){{0, 0, b, 0}};
+ glProgramUniformMatrix4fv(program, MODEL_RENDER_PROJ_MATRIX_LOC, 1, 0, projection.E);
v3 camera = ctx->camera_position;
- set_camera(ctx->model_render_context.shader, MODEL_RENDER_VIEW_MATRIX_LOC,
- camera, v3_normalize(v3_sub(camera, (v3){0})), (v3){{0, 1, 0}});
+ set_camera(program, MODEL_RENDER_VIEW_MATRIX_LOC, camera,
+ v3_normalize(v3_sub(camera, (v3){0})), (v3){{0, 1, 0}});
- v3 scale = v3_sub(max_coord_mm, min_coord_mm);
- m4 model_transform;
- model_transform.c[0] = (v4){{scale.x, 0, 0, 0}};
- model_transform.c[1] = (v4){{0, scale.z, 0, 0}};
- model_transform.c[2] = (v4){{0, 0, scale.y, 0}};
- model_transform.c[3] = (v4){{0, 0, 0, 1}};
- glProgramUniformMatrix4fv(ctx->model_render_context.shader, MODEL_RENDER_MODEL_MATRIX_LOC,
- 1, 0, model_transform.E);
-
- glBindTextureUnit(0, ctx->view_texture.texture);
- glBindVertexArray(ctx->unit_cube.vao);
- glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT,
- (void *)ctx->unit_cube.elements_offset);
+ #if DRAW_ALL_VOLUMES
+ for (u32 i = 0; i < countof(volumes); i++)
+ draw_volume_item(ctx, volumes + i);
+ #else
+ draw_volume_item(ctx, volumes + single_volume_index);
+ #endif
+
+ /* NOTE(rnp): resolve multisampled scene */
+ glBlitNamedFramebuffer(rt->fb, ctx->output_target.fb, 0, 0, rt->size.w, rt->size.h,
+ 0, 0, rt->size.w, rt->size.h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ glGenerateTextureMipmap(ctx->output_target.textures[0]);
+}
+
+function void
+viewer_frame_step(ViewerContext *ctx, f32 dt)
+{
+ if (dt != 0) update_scene(ctx, dt);
////////////////
// UI Overlay
+ f32 one = 1;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_CLEAR_COLOUR.E);
glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one);
@@ -516,7 +594,4 @@ viewer_frame_step(ViewerContext *ctx, f32 dt)
glBindTextureUnit(0, ctx->output_target.textures[0]);
glBindVertexArray(ctx->overlay_render_context.vao);
glDrawArrays(GL_TRIANGLES, 0, 6);
-
- glfwSwapBuffers(ctx->window);
- glfwPollEvents();
}
diff --git a/main_linux.c b/main_linux.c
@@ -66,7 +66,10 @@ main(void)
if (fds[0].revents & POLLIN)
dispatch_file_watch_events(&ctx->os, ctx->arena);
f64 now = glfwGetTime();
- viewer_frame_step(ctx, now - last_time);
+ viewer_frame_step(ctx, ctx->demo_mode * (now - last_time) + ctx->input_dt);
last_time = now;
+ ctx->input_dt = 0;
+ glfwSwapBuffers(ctx->window);
+ glfwPollEvents();
}
}
diff --git a/main_w32.c b/main_w32.c
@@ -89,8 +89,14 @@ main(void)
init_viewer(ctx);
+ f64 last_time = 0;
while (!ctx->should_exit) {
- clear_io_queue(&ctx->os, &input, ctx->arena);
- viewer_frame_step(ctx, &input);
+ clear_io_queue(&ctx->os, ctx->arena);
+ f64 now = glfwGetTime();
+ viewer_frame_step(ctx, ctx->demo_mode * (now - last_time) + ctx->input_dt);
+ last_time = now;
+ ctx->input_dt = 0;
+ glfwSwapBuffers(ctx->window);
+ glfwPollEvents();
}
}
diff --git a/render_model.frag.glsl b/render_model.frag.glsl
@@ -21,7 +21,7 @@ bool bounding_box_test(vec3 coord, float p)
void main()
{
float smp = length(texture(u_texture, texture_coordinate).xy);
- float threshold_val = pow(10.0f, (u_threshold + 13) / 20.0f);
+ float threshold_val = pow(10.0f, u_threshold / 20.0f);
smp = clamp(smp, 0.0f, threshold_val);
smp = smp / threshold_val;
smp = pow(smp, u_gamma);
diff --git a/util.h b/util.h
@@ -269,13 +269,6 @@ struct OS {
};
typedef struct {
- u32 texture;
- u32 width;
- u32 height;
- u32 depth;
-} Texture;
-
-typedef struct {
u32 shader;
u32 vao;
u32 vbo;
@@ -301,17 +294,20 @@ typedef struct {
RenderContext model_render_context;
RenderContext overlay_render_context;
+ RenderTarget multisample_target;
RenderTarget output_target;
RenderModel unit_cube;
sv2 window_size;
+ b32 demo_mode;
f32 cycle_t;
f32 camera_angle;
+ f32 camera_fov;
f32 camera_radius;
v3 camera_position;
- Texture view_texture;
+ f32 input_dt;
b32 should_exit;