volviewer

Volumetric Data Toy Viewer
git clone anongit@rnpnr.xyz:volviewer.git
Log | Files | Refs | Feed | LICENSE

Commit: af4c8ec39796779c9fd0b93b87f8b31d216a0037
Parent: 5e2feb594a335bc7e8a834e8c559ef829862d213
Author: Randy Palamar
Date:   Sat, 24 May 2025 15:10:25 -0600

add camera and projection matrices

Diffstat:
Mcommon.c | 135++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mmain_linux.c | 5++++-
Mrender_model.frag.glsl | 2+-
Mutil.c | 56+++++++++++++++++++++++++++++++++++++++++++++++---------
Mutil.h | 9+++++++++
5 files changed, 177 insertions(+), 30 deletions(-)

diff --git a/common.c b/common.c @@ -4,7 +4,9 @@ #include "GLFW/glfw3.h" #define BG_CLEAR_COLOUR (v4){{0.12, 0.1, 0.1, 1}} -#define RENDER_TARGET_SIZE 720, 720 +#define RENDER_TARGET_SIZE 1024, 1024 + +#define CAMERA_ELEVATION_ANGLE 30.0f /* NOTE(rnp): for video output we will render a full rotation in this much time at the * the specified frame rate */ @@ -12,10 +14,13 @@ #define OUTPUT_FRAME_RATE 30.0f #define OUTPUT_BG_CLEAR_COLOUR (v4){{0.0, 0.2, 0.0, 1}} -#define VIEWER_RENDER_DYNAMIC_RANGE_LOC (0) -#define VIEWER_RENDER_THRESHOLD_LOC (1) -#define VIEWER_RENDER_GAMMA_LOC (2) -#define VIEWER_RENDER_LOG_SCALE_LOC (3) +#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) typedef struct { v3 normal; @@ -144,41 +149,60 @@ function FILE_WATCH_CALLBACK_FN(reload_shader) } function RenderModel -load_render_model(Arena arena, c8 *positions_file_name, c8 *indices_file_name) +load_render_model(Arena arena, c8 *positions_file_name, c8 *indices_file_name, c8 *normals_file_name) { RenderModel result = {0}; str8 positions = os_read_whole_file(&arena, positions_file_name); + str8 normals = os_read_whole_file(&arena, normals_file_name); str8 indices = os_read_whole_file(&arena, indices_file_name); result.elements = indices.len / sizeof(u16); - s32 buffer_size = positions.len + indices.len; + s32 buffer_size = positions.len + indices.len + normals.len; - result.elements_offset = positions.len; + s32 el_offset = positions.len + normals.len; + result.elements_offset = el_offset; glCreateBuffers(1, &result.buffer); glNamedBufferStorage(result.buffer, buffer_size, 0, GL_DYNAMIC_STORAGE_BIT); glNamedBufferSubData(result.buffer, 0, positions.len, positions.data); - glNamedBufferSubData(result.buffer, positions.len, indices.len, indices.data); + glNamedBufferSubData(result.buffer, positions.len, normals.len, normals.data); + glNamedBufferSubData(result.buffer, el_offset, indices.len, indices.data); glCreateVertexArrays(1, &result.vao); - glVertexArrayVertexBuffer(result.vao, 0, result.buffer, 0, 3 * sizeof(f32)); + glVertexArrayVertexBuffer(result.vao, 0, result.buffer, 0, 3 * sizeof(f32)); + glVertexArrayVertexBuffer(result.vao, 1, result.buffer, positions.len, 3 * sizeof(f32)); glVertexArrayElementBuffer(result.vao, result.buffer); glEnableVertexArrayAttrib(result.vao, 0); - glVertexArrayAttribFormat(result.vao, 0, 3, GL_FLOAT, 0, 0); + glEnableVertexArrayAttrib(result.vao, 1); + + glVertexArrayAttribFormat(result.vao, 0, 3, GL_FLOAT, 0, 0); + glVertexArrayAttribFormat(result.vao, 1, 3, GL_FLOAT, 0, positions.len); + glVertexArrayAttribBinding(result.vao, 0, 0); + glVertexArrayAttribBinding(result.vao, 1, 0); return result; } function void +scroll_callback(GLFWwindow *window, f64 x, f64 y) +{ + ViewerContext *ctx = glfwGetWindowUserPointer(window); + ctx->camera_radius += 0.2 * y; +} + +function void key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 modifiers) { ViewerContext *ctx = glfwGetWindowUserPointer(window); if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) ctx->should_exit = 1; + + 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; } function void @@ -191,7 +215,9 @@ fb_callback(GLFWwindow *window, s32 w, s32 h) function void init_viewer(ViewerContext *ctx) { - ctx->window_size = (sv2){.w = 640, .h = 640}; + ctx->window_size = (sv2){.w = 640, .h = 640}; + ctx->camera_radius = 5; + ctx->camera_angle = CAMERA_ELEVATION_ANGLE * PI / 180.0f; if (!glfwInit()) os_fatal(str8("failed to start glfw\n")); @@ -205,6 +231,7 @@ init_viewer(ViewerContext *ctx) glfwSwapInterval(1); glfwSetKeyCallback(ctx->window, key_callback); + glfwSetScrollCallback(ctx->window, scroll_callback); glfwSetFramebufferSizeCallback(ctx->window, fb_callback); if (!gladLoadGL(glfwGetProcAddress)) os_fatal(str8("failed to load glad\n")); @@ -218,6 +245,8 @@ init_viewer(ViewerContext *ctx) glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); #endif + glEnable(GL_DEPTH_TEST); + RenderContext *rc = &ctx->model_render_context; RenderTarget *rt = &ctx->output_target; @@ -236,25 +265,48 @@ init_viewer(ViewerContext *ctx) "#version 460 core\n" "\n" "layout(location = 0) in vec3 v_position;\n" + "layout(location = 1) in vec3 v_normal;\n" + "\n" + "layout(location = 0) out vec3 f_normal;\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" + "\n" "\n" "void main()\n" "{\n" - "\tgl_Position = vec4(0.5 * v_position, 1);\n" + //"\tf_normal = normalize(mat3(u_model) * v_normal);\n" + "\tf_normal = v_normal;\n" + "\tgl_Position = u_projection * u_view * u_model * vec4(v_position, 1);\n" "}\n"); model_rc->fragment_header = str8("" "#version 460 core\n\n" + "layout(location = 0) in vec3 normal;\n\n" "layout(location = 0) out vec4 out_colour;\n\n" - "layout(location = " str(VIEWER_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n" - "layout(location = " str(VIEWER_RENDER_THRESHOLD_LOC) ") uniform float u_threshold = 40;\n" - "layout(location = " str(VIEWER_RENDER_GAMMA_LOC) ") uniform float u_gamma = 1;\n" - "layout(location = " str(VIEWER_RENDER_LOG_SCALE_LOC) ") uniform bool u_log_scale;\n" + "layout(location = " str(MODEL_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n" + "layout(location = " str(MODEL_RENDER_THRESHOLD_LOC) ") uniform float u_threshold = 40;\n" + "layout(location = " str(MODEL_RENDER_GAMMA_LOC) ") uniform float u_gamma = 1;\n" + "layout(location = " str(MODEL_RENDER_LOG_SCALE_LOC) ") uniform bool u_log_scale;\n" "\n#line 1\n"); str8 render_model = str8("render_model.frag.glsl"); reload_shader(&ctx->os, render_model, (sptr)model_rc, ctx->arena); os_add_file_watch(&ctx->os, &ctx->arena, render_model, reload_shader, (sptr)model_rc); + /* TODO(rnp): think about a reasonable region (probably min_coord -> max_coord + 10%) */ + f32 n = 1; + f32 f = 20; + f32 a = -f / (f - n); + f32 b = -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(rc->shader, MODEL_RENDER_PROJ_MATRIX_LOC, 1, 0, projection.E); + rc = &ctx->overlay_render_context; ShaderReloadContext *overlay_rc = push_struct(&ctx->arena, ShaderReloadContext); overlay_rc->render_context = rc; @@ -312,19 +364,63 @@ init_viewer(ViewerContext *ctx) os_add_file_watch(&ctx->os, &ctx->arena, render_overlay, reload_shader, (sptr)overlay_rc); ctx->unit_cube = load_render_model(ctx->arena, "unit_cube_positions.bin", - "unit_cube_indices.bin"); + "unit_cube_indices.bin", "unit_cube_normals.bin"); +} + +function void +set_camera(u32 program, u32 location, v3 position, v3 normal, v3 orthogonal) +{ + v3 right = cross(orthogonal, normal); + v3 up = cross(normal, right); + + v3 translate; + position = v3_sub((v3){0}, position); + translate.x = v3_dot(position, right); + translate.y = v3_dot(position, up); + translate.z = v3_dot(position, normal); + + m4 transform; + transform.c[0] = (v4){{right.x, up.x, normal.x, 0}}; + transform.c[1] = (v4){{right.y, up.y, normal.y, 0}}; + transform.c[2] = (v4){{right.z, up.z, normal.z, 0}}; + transform.c[3] = (v4){{translate.x, translate.y, translate.z, 1}}; + glProgramUniformMatrix4fv(program, location, 1, 0, transform.E); } function void -viewer_frame_step(ViewerContext *ctx) +viewer_frame_step(ViewerContext *ctx, f32 dt) { + ctx->cycle_t += 0.25 * dt; + if (ctx->cycle_t > 1) ctx->cycle_t -= 1; + + f32 angle = ctx->cycle_t * 2 * PI; + ctx->camera_position.x = ctx->camera_radius * cos_f32(angle); + ctx->camera_position.z = -ctx->camera_radius * sin_f32(angle); + ctx->camera_position.y = ctx->camera_radius * tan_f32(ctx->camera_angle); + ////////////// // 3D Models + 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); + + 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}}); + + f32 scale = 2; + m4 model_transform; + model_transform.c[0] = (v4){{scale, 0, 0, 0}}; + model_transform.c[1] = (v4){{0, scale, 0, 0}}; + model_transform.c[2] = (v4){{0, 0, scale, 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); + glBindVertexArray(ctx->unit_cube.vao); glDrawElements(GL_TRIANGLES, ctx->unit_cube.elements, GL_UNSIGNED_SHORT, (void *)ctx->unit_cube.elements_offset); @@ -333,6 +429,7 @@ viewer_frame_step(ViewerContext *ctx) // UI Overlay glBindFramebuffer(GL_FRAMEBUFFER, 0); glClearNamedFramebufferfv(0, GL_COLOR, 0, BG_CLEAR_COLOUR.E); + glClearNamedFramebufferfv(0, GL_DEPTH, 0, &one); f32 aspect_ratio = (f32)ctx->output_target.size.w / (f32)ctx->output_target.size.h; sv2 target_size = ctx->window_size; diff --git a/main_linux.c b/main_linux.c @@ -60,10 +60,13 @@ main(void) fds[0].fd = ctx->os.file_watch_context.handle; fds[0].events = POLLIN; + f64 last_time = 0; while (!ctx->should_exit) { poll(fds, countof(fds), 0); if (fds[0].revents & POLLIN) dispatch_file_watch_events(&ctx->os, ctx->arena); - viewer_frame_step(ctx); + f64 now = glfwGetTime(); + viewer_frame_step(ctx, now - last_time); + last_time = now; } } diff --git a/render_model.frag.glsl b/render_model.frag.glsl @@ -36,5 +36,5 @@ void main() //v_out_colour = vec4(hsv2rgb(vec3(360 * smp, 0.8, 0.95)), 1); #endif - out_colour = vec4(1); + out_colour = vec4(abs(normal), 1); } diff --git a/util.c b/util.c @@ -80,6 +80,53 @@ da_reserve_(Arena *a, void *data, sz *capacity, sz needed, sz align, sz size) return data; } +function sv2 +sv2_sub(sv2 a, sv2 b) +{ + sv2 result; + result.x = a.x - b.x; + result.y = a.y - b.y; + return result; +} + +function v3 +v3_sub(v3 a, v3 b) +{ + v3 result; + result.x = a.x - b.x; + result.y = a.y - b.y; + result.z = a.z - b.z; + return result; +} + +function f32 +v3_dot(v3 a, v3 b) +{ + f32 result = a.x * b.x + a.y * b.y + a.z * b.z; + return result; +} + +function v3 +v3_normalize(v3 a) +{ + f32 scale = sqrt_f32(1 / v3_dot(a, a)); + v3 result = a; + result.x *= scale; + result.y *= scale; + result.z *= scale; + return result; +} + +function v3 +cross(v3 a, v3 b) +{ + v3 result; + result.x = a.y * b.z - a.z * b.y; + result.y = a.z * b.x - a.x * b.z; + result.z = a.x * b.y - a.y * b.x; + return result; +} + function u32 utf8_encode(u8 *out, u32 cp) { @@ -407,15 +454,6 @@ parse_f64(str8 s) return result; } -function sv2 -sv2_sub(sv2 a, sv2 b) -{ - sv2 result; - result.x = a.x - b.x; - result.y = a.y - b.y; - return result; -} - function FileWatchDirectory * lookup_file_watch_directory(FileWatchContext *ctx, u64 hash) { diff --git a/util.h b/util.h @@ -37,6 +37,9 @@ /* TODO(rnp): msvc probably won't build this but there are other things preventing that as well */ #define sqrt_f32(a) __builtin_sqrtf(a) #define atan2_f32(y, x) __builtin_atan2f(y, x) +#define sin_f32(x) __builtin_sinf(x) +#define cos_f32(x) __builtin_cosf(x) +#define tan_f32(x) __builtin_tanf(x) #if ARCH_ARM64 /* TODO? debuggers just loop here forever and need a manual PC increment (step over) */ @@ -111,6 +114,8 @@ #define U32_MAX (0xFFFFFFFFUL) #define F32_INFINITY (__builtin_inff()) +#define PI (3.14159265358979323846) + #define INVALID_FILE (-1) typedef char c8; @@ -294,6 +299,10 @@ typedef struct { sv2 window_size; + f32 cycle_t; + f32 camera_angle; + f32 camera_radius; + v3 camera_position; b32 should_exit; void *window;