From 633a922e42e1fd18dc85cd828f612024b1e99924 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sat, 4 Oct 2025 17:53:06 +0100 Subject: [PATCH] Run shadow render pass and compute light position in vertex shaders --- default-frag.glsl | 2 + floor-vert.glsl | 12 ++++-- gpu.c | 105 +++++++++++++++++++++++++++++++++++++++++----- gpu.h | 2 +- main.c | 9 ++-- model-vert.glsl | 14 ++++--- 6 files changed, 121 insertions(+), 23 deletions(-) diff --git a/default-frag.glsl b/default-frag.glsl index 7cd38c5..123c1e0 100644 --- a/default-frag.glsl +++ b/default-frag.glsl @@ -5,6 +5,8 @@ vec3 light_dir = normalize(vec3(2, 2, -1)); vec3 base_colour = vec3(1.0, 0.94, 0.8); layout(location = 0) in vec3 frag_normal; +layout(location = 1) in vec4 light_pos; + layout(location = 0) out vec4 out_colour; void main() diff --git a/floor-vert.glsl b/floor-vert.glsl index cdf2e5d..1c8f33c 100644 --- a/floor-vert.glsl +++ b/floor-vert.glsl @@ -1,10 +1,12 @@ #version 450 layout(location = 0) out vec3 frag_normal; +layout(location = 1) out vec4 light_pos; -layout(push_constant) uniform vp_t { - mat4 transform; -} vp; +layout(push_constant) uniform push_t { + mat4 vp; + mat4 light_vp; +} push; vec2 vertices[4] = vec2[]( vec2(-1, -1), @@ -15,6 +17,8 @@ vec2 vertices[4] = vec2[]( void main() { const vec2 pos = 3 * vertices[gl_VertexIndex]; - gl_Position = vp.transform * vec4(pos.x, 0, pos.y, 1); + const vec4 world_pos = vec4(pos.x, 0, pos.y, 1); + gl_Position = push.vp * world_pos; frag_normal = vec3(0, 1, 0); + light_pos = push.light_vp * world_pos; } diff --git a/gpu.c b/gpu.c index 97adc48..b886346 100644 --- a/gpu.c +++ b/gpu.c @@ -21,6 +21,11 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define CLAMP(x, min, max) MAX((min), MIN(x, (max))) +typedef struct { + mat4_t vp; + mat4_t light_vp; +} push_t; + static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; static const char *extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, @@ -547,7 +552,7 @@ void gpu_init(gpu_ctx_t *ctx, GLFWwindow *window, const char *name) const VkPushConstantRange push_constant_range = { .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, - .size = sizeof(mat4_t), + .size = sizeof(push_t), }; // Load vertex and fragment shader modules @@ -1083,7 +1088,7 @@ void gpu_model_cleanup(const gpu_ctx_t *ctx, const gpu_model_t *model) } void gpu_render( - const gpu_ctx_t *ctx, mat4_t vp, size_t model_count, + const gpu_ctx_t *ctx, mat4_t vp, mat4_t light_vp, size_t model_count, const gpu_model_t *models, const mat4_t **bone_matrices, const mat4_t *model_transforms) { @@ -1100,6 +1105,14 @@ void gpu_render( &curr_img); assert(result == VK_SUCCESS); + // Update models' transforms and bones + for (unsigned i = 0; i < model_count; ++i) { + models[i].transform_ptr[curr_img] = model_transforms[i]; + memcpy( + models[i].bone_ptr + (models[i].bone_count * curr_img), + bone_matrices[i], models[i].bone_count * sizeof(mat4_t)); + } + // Reset command buffer, then begin writing vkResetCommandBuffer(ctx->cmd_buf, 0); const VkCommandBufferBeginInfo cmd_begin = { @@ -1108,6 +1121,61 @@ void gpu_render( result = vkBeginCommandBuffer(ctx->cmd_buf, &cmd_begin); assert(result == VK_SUCCESS); + // Run shadow pass + VkRenderPassBeginInfo shadow_pass_begin = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = ctx->shadow_pass, + .framebuffer = ctx->shadow_framebuf, + .renderArea.extent = { + .width = SHADOW_IMG_SIZE, + .height = SHADOW_IMG_SIZE, + }, + .clearValueCount = 1, + .pClearValues = &(VkClearValue){ .depthStencil = { 1.0, 0.0 } }, + }; + vkCmdBeginRenderPass( + ctx->cmd_buf, &shadow_pass_begin, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline( + ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->shadow_pipeline); + vkCmdPushConstants( + ctx->cmd_buf, ctx->model_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, + 0, sizeof(push_t), &(push_t) { .vp = light_vp }); + for (unsigned i = 0; i < model_count; ++i) { + vkCmdBindVertexBuffers( + ctx->cmd_buf, 0, 1, &models[i].vertex_buf, + &(VkDeviceSize) { 0 }); + vkCmdBindIndexBuffer( + ctx->cmd_buf, models[i].index_buf, 0, VK_INDEX_TYPE_UINT16); + + vkCmdBindDescriptorSets( + ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, + ctx->model_pipeline_layout, 0, 1, models[i].desc_sets + curr_img, + 0, nullptr); + + vkCmdDrawIndexed(ctx->cmd_buf, models[i].index_count, 1, 0, 0, 0); + } + vkCmdEndRenderPass(ctx->cmd_buf); + + // Wait until shadow map is fully written and transition to shader + // read-only layout. + const VkImageMemoryBarrier shadows_ready = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .image = ctx->shadow_img, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + vkCmdPipelineBarrier( + ctx->cmd_buf, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, + &shadows_ready); + VkClearValue clear_values[] = { [0] = { .color = { { 0.0, 0.0, 0.0, 1.0 } } }, [1] = { .depthStencil = { 1.0, 0.0 } }, @@ -1123,12 +1191,14 @@ void gpu_render( vkCmdBeginRenderPass( ctx->cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); + const push_t push = { .vp = vp, .light_vp = light_vp }; + // Draw floor vkCmdBindPipeline( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->floor_pipeline); vkCmdPushConstants( ctx->cmd_buf, ctx->model_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, - 0, sizeof(mat4_t), &vp); + 0, sizeof(push_t), &push); vkCmdDraw(ctx->cmd_buf, 4, 1, 0, 0); // Draw models @@ -1136,13 +1206,8 @@ void gpu_render( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->model_pipeline); vkCmdPushConstants( ctx->cmd_buf, ctx->model_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, - 0, sizeof(mat4_t), &vp); + 0, sizeof(push_t), &push); for (unsigned i = 0; i < model_count; ++i) { - models[i].transform_ptr[curr_img] = model_transforms[i]; - memcpy( - models[i].bone_ptr + (models[i].bone_count * curr_img), - bone_matrices[i], models[i].bone_count * sizeof(mat4_t)); - vkCmdBindVertexBuffers( ctx->cmd_buf, 0, 1, &models[i].vertex_buf, &(VkDeviceSize) { 0 }); @@ -1157,8 +1222,28 @@ void gpu_render( vkCmdDrawIndexed(ctx->cmd_buf, models[i].index_count, 1, 0, 0, 0); } - // Finish render pass and end writing to command buffer vkCmdEndRenderPass(ctx->cmd_buf); + + // Wait until render pass is finished reading shadow map and transition + // back to depth stencil layout + const VkImageMemoryBarrier shadows_done = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + .image = ctx->shadow_img, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + vkCmdPipelineBarrier( + ctx->cmd_buf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, 0, 0, nullptr, 0, + nullptr, 1, &shadows_done); + result = vkEndCommandBuffer(ctx->cmd_buf); assert(result == VK_SUCCESS); diff --git a/gpu.h b/gpu.h index f738e49..8088247 100644 --- a/gpu.h +++ b/gpu.h @@ -74,7 +74,7 @@ void gpu_model_load( void gpu_model_cleanup(const gpu_ctx_t *ctx, const gpu_model_t *model); void gpu_render( - const gpu_ctx_t *ctx, mat4_t vp, size_t model_count, + const gpu_ctx_t *ctx, mat4_t vp, mat4_t light_vp, size_t model_count, const gpu_model_t *models, const mat4_t **bone_matrices, const mat4_t *model_transforms); diff --git a/main.c b/main.c index 70e963d..09b769c 100644 --- a/main.c +++ b/main.c @@ -59,9 +59,12 @@ int main(int argc, char *argv[]) const vec3_t cam = { 0, 4, -8 }; const vec3_t tgt = { 0, 0, 0 }; - const mat4_t view = mat4_lookat(cam, tgt, (vec3_t) { 0, 1, 0 }); + const vec3_t up = { 0, 1, 0 }; + const vec3_t light = { 2, 2, -1 }; + const mat4_t view = mat4_lookat(cam, tgt, up); const mat4_t proj = mat4_perspective((float)W / H, PI / 8, 0.1, 100.0); const mat4_t vp = mat4_mul(proj, view); + const mat4_t light_vp = mat4_lookat(light, tgt, up); mat4_t transforms[NELEMS(gpu_models)] = { mat4_mul( MAT4_TRANSLATE(1, 0, 0), @@ -100,8 +103,8 @@ int main(int argc, char *argv[]) anim.bone_matrices, }; gpu_render( - &gpu, vp, NELEMS(gpu_models), gpu_models, bone_matrices, - transforms); + &gpu, vp, light_vp, NELEMS(gpu_models), gpu_models, + bone_matrices, transforms); } // Clean up diff --git a/model-vert.glsl b/model-vert.glsl index c146e86..8a1e68d 100644 --- a/model-vert.glsl +++ b/model-vert.glsl @@ -6,10 +6,12 @@ layout(location = 2) in uvec4 bone_indices; layout(location = 3) in vec4 weights; layout(location = 0) out vec3 frag_normal; +layout(location = 1) out vec4 light_pos; -layout(push_constant) uniform vp_t { - mat4 transform; -} vp; +layout(push_constant) uniform push_t { + mat4 vp; + mat4 light_vp; +} push; layout(binding = 0) uniform transform_t { mat4 model; @@ -25,8 +27,10 @@ void main() { + weights.y * bones.matrices[bone_indices.y] + weights.z * bones.matrices[bone_indices.z] + weights.w * bones.matrices[bone_indices.w]; - gl_Position = vp.transform * transform.model * skinning_matrix - * vec4(position, 1.0); + const vec4 world_pos + = transform.model * skinning_matrix * vec4(position, 1.0); + gl_Position = push.vp * world_pos; frag_normal = transpose( inverse(mat3(transform.model * skinning_matrix))) * normal; + light_pos = push.light_vp * world_pos; }