From 3540468ef627f7990d49e6e59fa07deb2d2c0b2d Mon Sep 17 00:00:00 2001 From: andrewjhaman Date: Thu, 6 Jul 2023 01:06:21 -0400 Subject: [PATCH 1/2] Fix collisions. Switch to No-Culling Shadow Maps. Introduce Better Shadow Bias Calculation. Calculate Normals in the Shader. --- main.c | 98 +++++++++++++++++++++++++++++++-------------------- threedee.glsl | 52 +++++++++++++++++++-------- 2 files changed, 96 insertions(+), 54 deletions(-) diff --git a/main.c b/main.c index ccb5fe8..1439d39 100644 --- a/main.c +++ b/main.c @@ -3974,6 +3974,21 @@ void dbg3dline(Vec3 from, Vec3 to) dbgline(from_screenspace, to_screenspace); } +void dbg3dline2d(Vec2 a, Vec2 b) { + Vec3 a_3 = V3(a.x, 0.0, a.y); + Vec3 b_3 = V3(b.x, 0.0, b.y); + + dbg3dline(a_3, b_3); +} + +void dbg3dline2dOffset(Vec2 a, Vec2 offset) { + Vec3 a_3 = V3(a.x, 0.0, a.y); + Vec3 b_3 = V3(offset.x, 0.0, offset.y); + + dbg3dline(a_3, AddV3(a_3,b_3)); +} + + void colorquadplane(Quad q, Color col) { Quad warped = {0}; @@ -4223,6 +4238,41 @@ typedef struct MoveSlideParams } MoveSlideParams; +Vec2 get_penetration_vector(AABB stable, AABB dynamic) +{ + //Assumes we already know that they are colliding. + //It could be faster to use this info for collision detection as well, + //but this would require an intrusive refactor, and it is not the common + //case that things are colliding anyway, so it's actually not that much + //duplicated work. + Vec2 dynamic_centre = aabb_center(dynamic); + Vec2 dynamic_half_dims = MulV2F(aabb_size(dynamic), 0.5f); + + stable.lower_right.x += dynamic_half_dims.x; + stable.lower_right.y -= dynamic_half_dims.y; + stable.upper_left.x -= dynamic_half_dims.x; + stable.upper_left.y += dynamic_half_dims.y; + + float right_delta = stable.lower_right.x - dynamic_centre.x; + float left_delta = stable.upper_left.x - dynamic_centre.x; + float bottom_delta = stable.lower_right.y - dynamic_centre.y; + float top_delta = stable.upper_left.y - dynamic_centre.y; + + float r = fabsf( right_delta); + float l = fabsf( left_delta); + float b = fabsf(bottom_delta); + float t = fabsf( top_delta); + + if (r <= l && r <= b && r <= t) + return V2(right_delta, 0.0); + if (left_delta <= r && l <= b && l <= t) + return V2(left_delta, 0.0); + if (b <= r && b <= l && b <= t) + return V2(0.0, bottom_delta); + return V2(0.0, top_delta); +} + + // returns new pos after moving and sliding against collidable things Vec2 move_and_slide(MoveSlideParams p) { @@ -4328,43 +4378,12 @@ Vec2 move_and_slide(MoveSlideParams p) BUFF_ITER(CollisionObj, &overlapping_smallest_first) { AABB to_depenetrate_from = it->aabb; - int iters_tried_to_push_apart = 0; - bool happened_with_this_one = false; - while (overlapping(to_depenetrate_from, at_new) && iters_tried_to_push_apart < 500) - { - happened_with_this_one = true; - const float move_dist = 0.01f; - - info.happened = true; - Vec2 from_point = aabb_center(to_depenetrate_from); - Vec2 to_player = NormV2(SubV2(aabb_center(at_new), from_point)); - Vec2 compass_dirs[4] = { - V2(1.0, 0.0), - V2(-1.0, 0.0), - V2(0.0, 1.0), - V2(0.0, -1.0), - }; - int closest_index = -1; - float closest_dot = -99999999.0f; - for (int i = 0; i < 4; i++) - { - float dot = DotV2(compass_dirs[i], to_player); - if (dot > closest_dot) - { - closest_index = i; - closest_dot = dot; - } - } - // sometimes when objects are perfectly overlapping, every dot product is negative infinity - if(closest_index == -1) closest_index = 0; - Vec2 move_dir = compass_dirs[closest_index]; - info.normal = move_dir; - dbgplanevec(from_point, MulV2F(move_dir, 30.0f)); - Vec2 move = MulV2F(move_dir, move_dist); - at_new.upper_left = AddV2(at_new.upper_left, move); - at_new.lower_right = AddV2(at_new.lower_right, move); - iters_tried_to_push_apart++; - } + + Vec2 resolution_vector = get_penetration_vector(to_depenetrate_from, at_new); + at_new.upper_left = AddV2(at_new.upper_left , resolution_vector); + at_new.lower_right = AddV2(at_new.lower_right, resolution_vector); + bool happened_with_this_one = true; + if(happened_with_this_one) { bool already_in_happened = false; @@ -4680,7 +4699,7 @@ void draw_dialog_panel(Entity *talking_to, float alpha) line(AddV2(dialog_quad.lr, V2(line_width, 0.0)), AddV2(dialog_quad.ll, V2(-line_width, 0.0)), line_width, line_color); line(dialog_quad.ll, dialog_quad.ul, line_width, line_color); - float padding = 5.0f; + float padding = 7.5f; dialog_panel.upper_left = AddV2(dialog_panel.upper_left, V2(padding, -padding)); dialog_panel.lower_right = AddV2(dialog_panel.lower_right, V2(-padding, padding)); @@ -4945,7 +4964,7 @@ Shadow_State init_shadow_state() { }, .shader = sg_make_shader(shadow_mapper_program_shader_desc(sg_query_backend())), // Cull front faces in the shadow map pass - .cull_mode = SG_CULLMODE_FRONT, + // .cull_mode = SG_CULLMODE_BACK, .sample_count = 1, .depth = { .pixel_format = SG_PIXELFORMAT_DEPTH, @@ -5078,6 +5097,7 @@ Shadow_Volume_Params calculate_shadow_volume_params(Vec3 light_dir) return result; } + void frame(void) { static float speed_factor = 1.0f; diff --git a/threedee.glsl b/threedee.glsl index e1a212c..6cff400 100644 --- a/threedee.glsl +++ b/threedee.glsl @@ -7,7 +7,8 @@ in vec2 uv_in; out vec3 pos; out vec2 uv; out vec4 light_space_fragment_position; - +out vec3 light_dir; +out vec4 world_space_frag_pos; uniform vs_params { mat4 model; @@ -20,10 +21,13 @@ void main() { pos = pos_in; uv = uv_in; - vec4 world_space_frag_pos = model * vec4(pos_in, 1.0); + world_space_frag_pos = model * vec4(pos_in, 1.0); vec4 frag_pos = view * world_space_frag_pos; gl_Position = projection * frag_pos; + //@Speed I think we can just take the third row here and be fine. + light_dir = normalize(inverse(directional_light_space_matrix) * vec4(0.0, 0.0, -1.0, 0.0)).xyz; + light_space_fragment_position = directional_light_space_matrix * vec4(world_space_frag_pos.xyz, 1.0); } @end @@ -40,6 +44,8 @@ uniform fs_params { in vec3 pos; in vec2 uv; in vec4 light_space_fragment_position; +in vec3 light_dir; +in vec4 world_space_frag_pos; out vec4 frag_color; @@ -47,7 +53,7 @@ float decodeDepth(vec4 rgba) { return dot(rgba, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/16581375.0)); } -float do_shadow_sample(sampler2D shadowMap, vec2 uv, float scene_depth) { +float do_shadow_sample(sampler2D shadowMap, vec2 uv, float scene_depth, float n_dot_l) { { //WebGL does not support GL_CLAMP_TO_BORDER, or border colors at all it seems, so we have to check explicitly. //This will probably slow down other versions which do support texture borders, but the current system does @@ -56,12 +62,21 @@ float do_shadow_sample(sampler2D shadowMap, vec2 uv, float scene_depth) { return 1.0; } float map_depth = decodeDepth(texture(shadowMap, uv)); - map_depth += 0.001;//bias to counter self-shadowing + + // float bias = max(0.03f * (1.0f - n_dot_l), 0.005f); + // bias = clamp(bias, 0.0, 0.01); + + float offset_scale_N = sqrt(1 - n_dot_l*n_dot_l); + float offset_scale_L = offset_scale_N / n_dot_l; + float bias = 0.0002 * offset_scale_N + 0.0001 * offset_scale_L; + + map_depth += bias; + return step(scene_depth, map_depth); } -float bilinear_shadow_sample(sampler2D shadowMap, vec2 uv, int texture_width, int texture_height, float scene_depth_light_space) { +float bilinear_shadow_sample(sampler2D shadowMap, vec2 uv, int texture_width, int texture_height, float scene_depth_light_space, float n_dot_l) { vec2 texture_dim = vec2(float(texture_width), float(texture_height)); vec2 texel_dim = vec2(1.0 / float(texture_width ), 1.0 / float(texture_height)); @@ -75,10 +90,10 @@ float bilinear_shadow_sample(sampler2D shadowMap, vec2 uv, int texture_width, in vec2 uv_2 = vec2(texel_uv_floor.x, texel_uv_ceil.y ); vec2 uv_3 = vec2(texel_uv_ceil.x , texel_uv_ceil.y ); - float bl = do_shadow_sample(shadowMap, uv_0, scene_depth_light_space); - float br = do_shadow_sample(shadowMap, uv_1, scene_depth_light_space); - float tl = do_shadow_sample(shadowMap, uv_2, scene_depth_light_space); - float tr = do_shadow_sample(shadowMap, uv_3, scene_depth_light_space); + float bl = do_shadow_sample(shadowMap, uv_0, scene_depth_light_space, n_dot_l); + float br = do_shadow_sample(shadowMap, uv_1, scene_depth_light_space, n_dot_l); + float tl = do_shadow_sample(shadowMap, uv_2, scene_depth_light_space, n_dot_l); + float tr = do_shadow_sample(shadowMap, uv_3, scene_depth_light_space, n_dot_l); vec2 interp = fract(texel_uv); @@ -89,7 +104,7 @@ float bilinear_shadow_sample(sampler2D shadowMap, vec2 uv, int texture_width, in return result; } -float calculate_shadow_factor(sampler2D shadowMap, vec4 light_space_fragment_position) { +float calculate_shadow_factor(sampler2D shadowMap, vec4 light_space_fragment_position, float n_dot_l) { float shadow = 1.0; vec3 projected_coords = light_space_fragment_position.xyz / light_space_fragment_position.w; @@ -109,11 +124,13 @@ float calculate_shadow_factor(sampler2D shadowMap, vec4 light_space_fragment_pos for (int y=-2; y<=2; y++) { vec2 off = vec2(x*texel_step_size, y*texel_step_size); // shadow += do_shadow_sample(shadowMap, shadow_uv+off, current_depth); - shadow += bilinear_shadow_sample(shadowMap, shadow_uv+off, shadow_map_dimension, shadow_map_dimension, current_depth); + shadow += bilinear_shadow_sample(shadowMap, shadow_uv+off, shadow_map_dimension, shadow_map_dimension, current_depth, n_dot_l); } } shadow /= 25.0; + + return shadow; } @@ -126,12 +143,17 @@ void main() { } else { - vec3 light_dir = normalize(vec3(1, -1, 0)); - float shadow_factor = calculate_shadow_factor(shadow_map, light_space_fragment_position); - shadow_factor = shadow_factor * 0.5 + 0.5; + vec3 normal = normalize(cross(dFdx(world_space_frag_pos.xyz), dFdy(world_space_frag_pos.xyz))); + + float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0); + float shadow_factor = calculate_shadow_factor(shadow_map, light_space_fragment_position, n_dot_l); + + float lighting_factor = shadow_factor * n_dot_l; + lighting_factor = lighting_factor * 0.5 + 0.5; - frag_color = vec4(col.rgb*shadow_factor, 1.0); + frag_color = vec4(col.rgb*lighting_factor, 1.0); + } } @end From 85926061d5b51a452a70a5c2675a5eb58ef854ce Mon Sep 17 00:00:00 2001 From: andrewjhaman Date: Sun, 9 Jul 2023 21:14:59 -0400 Subject: [PATCH 2/2] Fit Shadows to Frustum --- main.c | 153 ++++++++++++++++++++++++++++++++++++-------------- threedee.glsl | 2 +- tuning.h | 2 +- 3 files changed, 112 insertions(+), 45 deletions(-) diff --git a/main.c b/main.c index 3051b0c..04b35fd 100644 --- a/main.c +++ b/main.c @@ -4989,7 +4989,45 @@ float round_to_nearest(float input, float round_target) return result; } -Shadow_Volume_Params calculate_shadow_volume_params(Vec3 light_dir) + +typedef struct +{ + //For now we consider all vertices on the near plane to be equal to the camera position, and store that at vertices[0]; + Vec3 vertices[5]; +} FrustumVertices; + +FrustumVertices get_frustum_vertices(Vec3 cam_pos, Vec3 cam_forward, Vec3 cam_right) { + FrustumVertices result = {0}; + + float aspect_ratio = (float)sapp_width() / (float)sapp_height(); + + const int num_frustum_vertices = sizeof(result.vertices)/sizeof(result.vertices[0]); + + const float cascade_distance = FAR_PLANE_DISTANCE; + + Vec2 far_plane_half_dims; + far_plane_half_dims.y = cascade_distance * tanf(FIELD_OF_VIEW * 0.5f); + far_plane_half_dims.x = far_plane_half_dims.y * aspect_ratio; + + Vec3 cam_up = Cross(cam_right, cam_forward); + + Vec3 far_plane_centre = AddV3(cam_pos, MulV3F(cam_forward, cascade_distance)); + Vec3 far_plane_offset_to_right_side = MulV3F(cam_right, far_plane_half_dims.x); + Vec3 far_plane_offset_to_top_side = MulV3F(cam_up , far_plane_half_dims.y); + Vec3 far_plane_offset_to_left_side = MulV3F(far_plane_offset_to_right_side, -1.0); + Vec3 far_plane_offset_to_bot_side = MulV3F(far_plane_offset_to_top_side , -1.0); + + result.vertices[0] = cam_pos; + result.vertices[1] = AddV3(far_plane_centre, AddV3(far_plane_offset_to_bot_side, far_plane_offset_to_left_side )); + result.vertices[2] = AddV3(far_plane_centre, AddV3(far_plane_offset_to_bot_side, far_plane_offset_to_right_side)); + result.vertices[3] = AddV3(far_plane_centre, AddV3(far_plane_offset_to_top_side, far_plane_offset_to_right_side)); + result.vertices[4] = AddV3(far_plane_centre, AddV3(far_plane_offset_to_top_side, far_plane_offset_to_left_side )); + + + return result; +} + +Shadow_Volume_Params calculate_shadow_volume_params(Vec3 light_dir, Vec3 cam_pos, Vec3 cam_forward, Vec3 cam_right) { Shadow_Volume_Params result = {0}; @@ -5002,55 +5040,34 @@ Shadow_Volume_Params calculate_shadow_volume_params(Vec3 light_dir) //To make up for this, we add an extra padding-skirt to the bounds. Mat4 light_space_matrix = LookAt_RH((Vec3){0}, light_dir, V3(0, 1, 0)); - Vec3 scene_min = V3( INFINITY, INFINITY, INFINITY); - Vec3 scene_max = V3(-INFINITY, -INFINITY, -INFINITY); + Vec3 frustum_min = V3( INFINITY, INFINITY, INFINITY); + Vec3 frustum_max = V3(-INFINITY, -INFINITY, -INFINITY); - for(PlacedMesh *cur = level_threedee.placed_mesh_list; cur; cur = cur->next) - { - Vec3 p = MulM4V3(light_space_matrix, cur->t.offset); + FrustumVertices frustum_vertices_worldspace = get_frustum_vertices(cam_pos, cam_forward, cam_right); + const int num_frustum_vertices = sizeof(frustum_vertices_worldspace.vertices)/sizeof(frustum_vertices_worldspace.vertices[0]); - scene_min.x = fminf(scene_min.x, p.x); - scene_max.x = fmaxf(scene_max.x, p.x); + for (int i = 0; i < num_frustum_vertices; ++i) { + Vec3 p = frustum_vertices_worldspace.vertices[i]; - scene_min.y = fminf(scene_min.y, p.y); - scene_max.y = fmaxf(scene_max.y, p.y); - - scene_min.z = fminf(scene_min.z, p.z); - scene_max.z = fmaxf(scene_max.z, p.z); - } + p = MulM4V3(light_space_matrix, p); - ENTITIES_ITER(gs.entities) - { - if(it->is_npc || it->is_character) - { - Transform draw_with = entity_transform(it); - Vec3 p = MulM4V3(light_space_matrix, draw_with.offset); + frustum_min.x = fminf(frustum_min.x, p.x); + frustum_max.x = fmaxf(frustum_max.x, p.x); - scene_min.x = fminf(scene_min.x, p.x); - scene_max.x = fmaxf(scene_max.x, p.x); - - scene_min.y = fminf(scene_min.y, p.y); - scene_max.y = fmaxf(scene_max.y, p.y); - - scene_min.z = fminf(scene_min.z, p.z); - scene_max.z = fmaxf(scene_max.z, p.z); - } + frustum_min.y = fminf(frustum_min.y, p.y); + frustum_max.y = fmaxf(frustum_max.y, p.y); + + frustum_min.z = fminf(frustum_min.z, p.z); + frustum_max.z = fmaxf(frustum_max.z, p.z); } - //pad to account for entity width - float pad = 2.5f; - - scene_min.x -= pad; - scene_min.y -= pad; - scene_max.x += pad; - scene_max.y += pad; - result.l = scene_min.x; - result.r = scene_max.x; + result.l = frustum_min.x; + result.r = frustum_max.x; - result.b = scene_min.y; - result.t = scene_max.y; + result.b = frustum_min.y; + result.t = frustum_max.y; float w = result.r - result.l; float h = result.t - result.b; @@ -5087,6 +5104,54 @@ Shadow_Volume_Params calculate_shadow_volume_params(Vec3 light_dir) return result; } + +void debug_draw_img(sg_image img, int index) { + draw_quad((DrawParams){quad_at(V2(512.0f*index, 512.0), V2(512.0, 512.0)), IMG(state.shadows.color_img), WHITE, .layer=LAYER_UI}); +} + +void debug_draw_img_with_border(sg_image img, int index) { + float bs = 50.0; + draw_quad((DrawParams){quad_at(V2(512.0f*index, 512.0), V2(512.0, 512.0)), state.shadows.color_img, (AABB){V2(-bs, -bs), AddV2(img_size(img), V2(bs, bs))}, WHITE, .layer=LAYER_UI}); +} + +void debug_draw_shadow_info(Vec3 frustum_tip, Vec3 cam_forward, Vec3 cam_right, Mat4 light_space_matrix) { + debug_draw_img(state.shadows.color_img, 0); + FrustumVertices fv = get_frustum_vertices(frustum_tip, cam_forward, cam_right); + + Vec2 projs[5]; + for (int i = 0; i < 5; ++i) { + Vec3 v = fv.vertices[i]; + Vec4 p = V4(v.x, v.y, v.z, 1.0); + Vec4 proj = MulM4V4(light_space_matrix, p); + proj.x /= proj.w; + proj.y /= proj.w; + proj.z /= proj.w; + + proj.x *= 0.5f; + proj.x += 0.5f; + proj.y *= 0.5f; + proj.y += 0.5f; + proj.z *= 0.5f; + proj.z += 0.5f; + + proj.x *= 512.0f; + proj.y *= 512.0f; + + projs[i] = proj.XY; + dbgsquare(proj.XY); + } + + dbgline(projs[0], projs[1]); + dbgline(projs[0], projs[2]); + dbgline(projs[0], projs[3]); + dbgline(projs[0], projs[4]); + dbgline(projs[1], projs[2]); + dbgline(projs[2], projs[3]); + dbgline(projs[3], projs[4]); + dbgline(projs[4], projs[1]); +} + + void actually_draw_thing(DrawnThing *it, Mat4 light_space_matrix, bool for_outline) { if(it->mesh) @@ -5163,7 +5228,7 @@ void actually_draw_thing(DrawnThing *it, Mat4 light_space_matrix, bool for_outli // I moved this out into its own separate function so that you could // define helper functions to be used multiple times in it, and those functions // would be near the actual 3d drawing in the file -void flush_all_drawn_things(Vec3 light_dir) +void flush_all_drawn_things(Vec3 light_dir, Vec3 cam_pos, Vec3 cam_facing, Vec3 cam_right) { // Draw all the 3D drawn things. Draw the shadows, then draw the things with the shadows. // Process armatures and upload their skeleton textures @@ -5247,10 +5312,12 @@ void flush_all_drawn_things(Vec3 light_dir) // do the shadow pass Mat4 light_space_matrix; { - Shadow_Volume_Params svp = calculate_shadow_volume_params(light_dir); + Shadow_Volume_Params svp = calculate_shadow_volume_params(light_dir, cam_pos, cam_facing, cam_right); + Mat4 shadow_view = LookAt_RH(V3(0, 0, 0), light_dir, V3(0, 1, 0)); Mat4 shadow_projection = Orthographic_RH_NO(svp.l, svp.r, svp.b, svp.t, svp.n, svp.f); light_space_matrix = MulM4(shadow_projection, shadow_view); + // debug_draw_shadow_info(cam_pos, cam_facing, cam_right, light_space_matrix); sg_begin_pass(state.shadows.pass, &state.shadows.pass_action); @@ -5544,7 +5611,7 @@ void frame(void) } - flush_all_drawn_things(light_dir); + flush_all_drawn_things(light_dir, cam_pos, facing, right); // draw the freaking outline. Play ball! draw_quad((DrawParams){quad_at(V2(0.0, screen_size().y), screen_size()), IMG(state.outline_pass_image), WHITE, .layer = LAYER_UI_FG, .custom_pipeline = state.twodee_outline_pip}); diff --git a/threedee.glsl b/threedee.glsl index 68f1099..f2dfbeb 100644 --- a/threedee.glsl +++ b/threedee.glsl @@ -147,7 +147,7 @@ float do_shadow_sample(sampler2D shadowMap, vec2 uv, float scene_depth, float n_ float offset_scale_N = sqrt(1 - n_dot_l*n_dot_l); float offset_scale_L = offset_scale_N / n_dot_l; - float bias = 0.0002 * offset_scale_N + 0.0001 * offset_scale_L; + float bias = 0.0004 * offset_scale_N + 0.0001 * offset_scale_L; map_depth += bias; diff --git a/tuning.h b/tuning.h index d919521..796c78e 100644 --- a/tuning.h +++ b/tuning.h @@ -59,5 +59,5 @@ //Rendering #define FIELD_OF_VIEW (PI32/4.0f) #define NEAR_PLANE_DISTANCE (0.01f) -#define FAR_PLANE_DISTANCE (1000.0f) +#define FAR_PLANE_DISTANCE (45.0f) #define SHADOW_MAP_DIMENSION (2048)