From 503ed33aecea9bf70f0cf53258165bec2b710375 Mon Sep 17 00:00:00 2001 From: Lorenzo Torres Date: Fri, 8 Aug 2025 03:18:08 +0200 Subject: [PATCH] Fully implemented lighting Two types of light sources are currently supported: directional lights and point lights. A scene can have only one directional light and up to 1024 point lights. --- assets/shaders/shader.frag | 83 +++++++++++++++++++------- src/rendering/Device.zig | 5 ++ src/rendering/GraphicsPipeline.zig | 95 +++++++++++++++++++++++------- src/rendering/Renderer.zig | 29 +++++++-- src/rendering/lights.zig | 14 +++++ src/rendering/rendering.zig | 1 + src/sideros.zig | 2 +- 7 files changed, 181 insertions(+), 48 deletions(-) create mode 100644 src/rendering/lights.zig diff --git a/assets/shaders/shader.frag b/assets/shaders/shader.frag index 51bb9a9..d0b894a 100644 --- a/assets/shaders/shader.frag +++ b/assets/shaders/shader.frag @@ -1,39 +1,80 @@ #version 450 +#define MAX_POINT_LIGHTS 1024 + + + layout(location = 0) out vec4 outColor; layout(location = 2) in vec3 Normal; layout(location = 3) in vec3 FragPos; layout(location = 4) in vec2 TexCoords; -layout (binding = 2) uniform LightUniform { - vec3 pos; -} lightPos; +layout (binding = 2) uniform DirectionalLight { + vec3 direction; + + vec3 ambient; + vec3 diffuse; + vec3 specular; +} directional_light; +layout (binding = 5) uniform PointLight { + vec3 position; + vec3 ambient; + vec3 diffuse; + vec3 specular; + vec3 data; + +} point_lights[MAX_POINT_LIGHTS]; layout (binding = 3) uniform ViewUniform { vec3 pos; } viewPos; +layout(push_constant) uniform pc { + int light_count; +} pushConstants; + layout (set = 1, binding = 0) uniform sampler2D textureSampler; layout (set = 1, binding = 1) uniform sampler2D diffuseSampler; -void main() { - vec3 lightDiffuse = vec3(0.5, 0.5, 0.5); - vec3 lightAmbient = vec3(0.2, 0.2, 0.2); - vec3 lightSpecular = vec3(1.0, 1.0, 1.0); - - vec3 norm = normalize(Normal); - vec3 lightDir = normalize(lightPos.pos - FragPos); - float diff = max(dot(norm, lightDir), 0.0); - vec3 diffuse = lightDiffuse * diff * vec3(texture(textureSampler, TexCoords)); - vec3 ambient = lightAmbient * vec3(texture(textureSampler, TexCoords)); - - vec3 viewDir = normalize(viewPos.pos - FragPos); - vec3 reflectDir = reflect(-lightDir, norm); +vec3 calc_directional_light(vec3 normal, vec3 viewDir) { + vec3 lightDir = normalize(-directional_light.direction); + float diff = max(dot(normal, lightDir), 0.0); + vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), 2); - vec3 specular = lightSpecular * spec * vec3(texture(diffuseSampler, TexCoords)); - //vec3 specular = lightSpecular * spec * vec3(1.0, 1.0, 1.0); - - vec3 result = (ambient + diffuse + specular); - outColor = vec4(result, 1.0); + vec3 ambient = directional_light.ambient * vec3(texture(textureSampler, TexCoords)); + vec3 diffuse = directional_light.diffuse * diff * vec3(texture(textureSampler , TexCoords)); + vec3 specular = directional_light.specular * spec * vec3(texture(diffuseSampler, TexCoords)); + return (ambient + diffuse + specular); +} + +vec3 calc_point_light(int index, vec3 normal, vec3 fragPos, vec3 viewDir) { + float constant = point_lights[index].data[0]; + float linear = point_lights[index].data[1]; + float quadratic = point_lights[index].data[2]; + + vec3 lightDir = normalize(point_lights[index].position - fragPos); + float diff = max(dot(normal, lightDir), 0.0); + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 2); + float distance = length(point_lights[index].position - fragPos); + float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance)); + vec3 ambient = point_lights[index].ambient * vec3(texture(textureSampler, TexCoords)); + vec3 diffuse = point_lights[index].diffuse * diff * vec3(texture(textureSampler, TexCoords)); + vec3 specular = point_lights[index].specular * spec * vec3(texture(diffuseSampler, TexCoords)); + ambient *= attenuation; + diffuse *= attenuation; + specular *= attenuation; + return (ambient + diffuse + specular); +} + +void main() { + vec3 norm = normalize(Normal); + vec3 viewDir = normalize(viewPos.pos - FragPos); + + vec3 result = calc_directional_light(norm, viewDir); + for(int i = 0; i < pushConstants.light_count; i++) + result += calc_point_light(i, norm, FragPos, viewDir); + + outColor = vec4(result, 1.0); } diff --git a/src/rendering/Device.zig b/src/rendering/Device.zig index 34a4292..cfffd8a 100644 --- a/src/rendering/Device.zig +++ b/src/rendering/Device.zig @@ -314,6 +314,11 @@ pub fn initShader(self: Self, comptime name: []const u8) !c.VkShaderModule { return shader_module; } +pub fn pushConstant(self: Self, pipeline: vk.GraphicsPipeline, stage: u32, offset: u32, size: u32, data: [*c]u8, frame: usize) void { + _ = stage; + c.vkCmdPushConstants(self.command_buffers[frame], pipeline.layout, c.VK_SHADER_STAGE_FRAGMENT_BIT, offset, size, data); +} + pub fn deinitShader(self: Self, shader: c.VkShaderModule) void { c.vkDestroyShaderModule(self.handle, shader, null); } diff --git a/src/rendering/GraphicsPipeline.zig b/src/rendering/GraphicsPipeline.zig index 6248bb6..26a976c 100644 --- a/src/rendering/GraphicsPipeline.zig +++ b/src/rendering/GraphicsPipeline.zig @@ -7,6 +7,10 @@ const c = vk.c; const math = @import("math"); const gltf = @import("gltf.zig"); const Allocator = std.mem.Allocator; +const rendering = @import("rendering.zig"); +const lights = rendering.lights; + +const max_point_lights = 1024; layout: c.VkPipelineLayout, handle: c.VkPipeline, @@ -17,7 +21,6 @@ descriptor_pool: c.VkDescriptorPool, descriptor_set: c.VkDescriptorSet, descriptor_set_layout: c.VkDescriptorSetLayout, projection_buffer: vk.Buffer, -light_buffer: vk.Buffer, view_buffer: vk.Buffer, view_memory: [*c]u8, transform_memory: [*c]u8, @@ -25,7 +28,8 @@ view_pos_memory: [*c]u8, texture_sampler: vk.Sampler, diffuse_sampler: vk.Sampler, textures: std.ArrayList(c.VkDescriptorSet), -light_pos: [*]f32, +directional_light: *lights.DirectionalLight, +point_lights: []lights.PointLight, const Self = @This(); @@ -304,13 +308,20 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re .stageFlags = c.VK_SHADER_STAGE_VERTEX_BIT, }; - const light_binding = c.VkDescriptorSetLayoutBinding{ + const directional_light_binding = c.VkDescriptorSetLayoutBinding{ .binding = 2, .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT, }; + const point_lights_binding = c.VkDescriptorSetLayoutBinding{ + .binding = 5, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = max_point_lights, + .stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT, + }; + const view_pos_binding = c.VkDescriptorSetLayoutBinding{ .binding = 3, .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, @@ -332,7 +343,7 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re .stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT, }; - const bindings = [_]c.VkDescriptorSetLayoutBinding{projection_binding, view_binding, transform_binding, light_binding, view_pos_binding}; + const bindings = [_]c.VkDescriptorSetLayoutBinding{projection_binding, view_binding, transform_binding, directional_light_binding, point_lights_binding, view_pos_binding}; const texture_bindings = [_]c.VkDescriptorSetLayoutBinding{texture_sampler_binding, diffuse_sampler_binding}; var descriptor_set_layout: c.VkDescriptorSetLayout = undefined; @@ -340,7 +351,7 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re const descriptor_set_layout_info = c.VkDescriptorSetLayoutCreateInfo{ .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .bindingCount = 5, + .bindingCount = 6, .pBindings = bindings[0..].ptr, }; @@ -355,12 +366,18 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re var set_layouts = [_]c.VkDescriptorSetLayout{descriptor_set_layout, texture_descriptor_set_layout}; + const range: c.VkPushConstantRange = .{ + .stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = 4, + }; + const layout_info: c.VkPipelineLayoutCreateInfo = .{ .sType = c.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 2, .pSetLayouts = set_layouts[0..].ptr, - .pushConstantRangeCount = 0, - .pPushConstantRanges = null, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &range, }; var layout: c.VkPipelineLayout = undefined; @@ -392,7 +409,7 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re const size = c.VkDescriptorPoolSize{ .type = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = 5, + .descriptorCount = 5 + max_point_lights, }; const sampler_size = c.VkDescriptorPoolSize{ @@ -519,38 +536,72 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re c.vkUpdateDescriptorSets(device.handle, 1, &write_transform_descriptor_set, 0, null); - const light_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf([3]f32)); + const directional_light_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(lights.DirectionalLight)); - var light_data: [*c]u8 = undefined; + var directional_light_data: [*c]u8 = undefined; try vk.mapError(c.vkMapMemory( device.handle, - light_buffer.memory, + directional_light_buffer.memory, 0, - light_buffer.size, + directional_light_buffer.size, 0, - @ptrCast(&light_data), + @ptrCast(&directional_light_data), )); - const light_pos: [*]f32 = @alignCast(@ptrCast(light_data)); + const directional_light: *lights.DirectionalLight = @alignCast(@ptrCast(directional_light_data)); - const light_descriptor_buffer_info = c.VkDescriptorBufferInfo{ - .buffer = light_buffer.handle, + const directional_light_descriptor_buffer_info = c.VkDescriptorBufferInfo{ + .buffer = directional_light_buffer.handle, .offset = 0, - .range = light_buffer.size, + .range = directional_light_buffer.size, }; - const write_light_descriptor_set = c.VkWriteDescriptorSet{ + const write_directional_light_descriptor_set = c.VkWriteDescriptorSet{ .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = descriptor_set, .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .pBufferInfo = &light_descriptor_buffer_info, + .pBufferInfo = &directional_light_descriptor_buffer_info, }; - c.vkUpdateDescriptorSets(device.handle, 1, &write_light_descriptor_set, 0, null); + c.vkUpdateDescriptorSets(device.handle, 1, &write_directional_light_descriptor_set, 0, null); + + const point_lights_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(lights.PointLight) * max_point_lights); + + var point_lights_data: [*c]u8 = undefined; + + try vk.mapError(c.vkMapMemory( + device.handle, + point_lights_buffer.memory, + 0, + point_lights_buffer.size, + 0, + @ptrCast(&point_lights_data), + )); + + const point_lights: []lights.PointLight = @as([*]lights.PointLight, @alignCast(@ptrCast(point_lights_data)))[0..max_point_lights]; + + var point_lights_descriptor_buffer_info: [max_point_lights]c.VkDescriptorBufferInfo = undefined; + for (0..max_point_lights) |i| { + point_lights_descriptor_buffer_info[i].buffer = point_lights_buffer.handle; + point_lights_descriptor_buffer_info[i].offset = @sizeOf(lights.PointLight) * i; + point_lights_descriptor_buffer_info[i].range = @sizeOf(lights.PointLight); + } + + const write_point_lights_descriptor_set = c.VkWriteDescriptorSet{ + .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set, + .dstBinding = 5, + .dstArrayElement = 0, + .descriptorCount = max_point_lights, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = @ptrCast(point_lights_descriptor_buffer_info[0..].ptr), + }; + + c.vkUpdateDescriptorSets(device.handle, 1, &write_point_lights_descriptor_set, 0, null); const view_pos_buffer = try device.initBuffer(vk.BufferUsage{ .uniform_buffer = true, .transfer_dst = true }, vk.BufferFlags{ .device_local = true }, @sizeOf([3]f32)); @@ -593,15 +644,15 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re .projection_buffer = projection_buffer, .view_buffer = view_buffer, .view_memory = view_data, - .light_buffer = light_buffer, .view_pos_memory = view_pos_data, .transform_memory = transform_data, .texture_sampler = try vk.Sampler.init(device), .diffuse_sampler = try vk.Sampler.init(device), .textures = std.ArrayList(c.VkDescriptorSet).init(allocator), - .light_pos = light_pos, .vertex_buffer = vertex_buffer, .index_buffer = index_buffer, + .directional_light = directional_light, + .point_lights = point_lights, }; } diff --git a/src/rendering/Renderer.zig b/src/rendering/Renderer.zig index 569160a..b877b5d 100644 --- a/src/rendering/Renderer.zig +++ b/src/rendering/Renderer.zig @@ -19,6 +19,7 @@ graphics_pipeline: vk.GraphicsPipeline, current_frame: u32, mesh: Mesh, transform: math.Transform, +transform2: math.Transform, previous_time: std.time.Instant, pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_handle: vk.c.VkSurfaceKHR) !Renderer { @@ -52,9 +53,26 @@ pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_hand _ = try graphics_pipeline.addTexture(device, texture, diffuse); - graphics_pipeline.light_pos[0] = -10.0; - graphics_pipeline.light_pos[1] = 0.0; - graphics_pipeline.light_pos[2] = 0.0; + graphics_pipeline.directional_light.direction = .{-0.2, -1.0, -0.3}; + graphics_pipeline.directional_light.ambient = .{0.5, 0.5, 0.5}; + graphics_pipeline.directional_light.diffuse = .{0.5, 0.5, 0.5}; + graphics_pipeline.directional_light.specular = .{0.5, 0.5, 0.5}; + + graphics_pipeline.point_lights[0].position = .{0.0, 3.0, 3.0}; + graphics_pipeline.point_lights[0].data[0] = 1.0; + graphics_pipeline.point_lights[0].data[1] = 0.0014; + graphics_pipeline.point_lights[0].data[2] = 0.000007; + graphics_pipeline.point_lights[0].ambient = .{0.5, 0.5, 0.5}; + graphics_pipeline.point_lights[0].diffuse = .{0.5, 0.5, 0.5}; + graphics_pipeline.point_lights[0].specular = .{1.0, 1.0, 1.0}; + + graphics_pipeline.point_lights[1].position = .{-1.0, 0.0, 0.0}; + graphics_pipeline.point_lights[1].data[0] = 1.0; + graphics_pipeline.point_lights[1].data[1] = 0.09; + graphics_pipeline.point_lights[1].data[2] = 0.032; + graphics_pipeline.point_lights[1].ambient = .{0.5, 0.5, 0.5}; + graphics_pipeline.point_lights[1].diffuse = .{0.5, 0.5, 0.5}; + graphics_pipeline.point_lights[1].specular = .{1.0, 1.0, 1.0}; return Renderer{ .instance = instance, @@ -66,6 +84,7 @@ pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_hand .graphics_pipeline = graphics_pipeline, .current_frame = 0, .transform = math.Transform.init(.{0.0, 0.0, 0.0}, .{1.0, 1.0, 1.0}, .{0.0, 0.0, 0.0}), + .transform2 = math.Transform.init(.{0.0, 3.0, 3.0}, .{0.5, 0.5, 0.5}, .{0.0, 0.0, 0.0}), .previous_time = try std.time.Instant.now(), .mesh = mesh, }; @@ -97,7 +116,7 @@ pub fn render(pool: *ecs.Pool) anyerror!void { view_pos[1] = camera.position[1]; view_pos[2] = camera.position[2]; - renderer.transform.rotate(math.rad(10) * delta_time, .{0.0, 1.0, 0.0}); + renderer.transform.rotate(math.rad(15) * delta_time, .{0.0, 1.0, 0.0}); const transform_memory = renderer.graphics_pipeline.transform_memory; @memcpy(transform_memory[0..(@sizeOf(math.Transform)-@sizeOf(math.Quaternion))], std.mem.asBytes(&renderer.transform)[0..(@sizeOf(math.Transform)-@sizeOf(math.Quaternion))]); @@ -111,6 +130,8 @@ pub fn render(pool: *ecs.Pool) anyerror!void { renderer.device.bindVertexBuffer(renderer.graphics_pipeline.vertex_buffer, renderer.current_frame); renderer.device.bindIndexBuffer(renderer.graphics_pipeline.index_buffer, renderer.current_frame); renderer.device.bindDescriptorSets(renderer.graphics_pipeline, renderer.current_frame, 0); + var lights: u32 = 2; + renderer.device.pushConstant(renderer.graphics_pipeline, 0, 0, 4, @ptrCast(&lights), renderer.current_frame); renderer.device.draw(renderer.mesh.index_count, renderer.current_frame, renderer.mesh); renderer.render_pass.end(renderer.device, renderer.current_frame); try renderer.device.endCommand(renderer.current_frame); diff --git a/src/rendering/lights.zig b/src/rendering/lights.zig new file mode 100644 index 0000000..df9b332 --- /dev/null +++ b/src/rendering/lights.zig @@ -0,0 +1,14 @@ +pub const DirectionalLight = extern struct { + direction: [3]f32 align(16), + ambient: [3]f32 align(16), + diffuse: [3]f32 align(16), + specular: [3]f32 align(16), +}; + +pub const PointLight = extern struct { + position: [3]f32 align(16), + ambient: [3]f32 align(16), + diffuse: [3]f32 align(16), + specular: [3]f32 align(16), + data: [3]f32 align(16), +}; diff --git a/src/rendering/rendering.zig b/src/rendering/rendering.zig index 3f695ef..b3ccc07 100644 --- a/src/rendering/rendering.zig +++ b/src/rendering/rendering.zig @@ -2,3 +2,4 @@ pub const Mesh = @import("Mesh.zig"); pub const vk = @import("vulkan.zig"); pub const Camera = @import("Camera.zig"); pub const Renderer = @import("Renderer.zig"); +pub const lights = @import("lights.zig"); diff --git a/src/sideros.zig b/src/sideros.zig index 2288104..16ec8f9 100644 --- a/src/sideros.zig +++ b/src/sideros.zig @@ -48,7 +48,7 @@ fn init_mods() void { export fn sideros_init(init: api.GameInit) callconv(.c) void { resources = .{ .camera = .{ - .position = .{ -5.0, 5.0, -5.0 }, + .position = .{ 0.0, 5.0, -5.0 }, .target = .{ 0.0, 0.0, 0.0 }, }, .renderer = undefined,