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.
This commit is contained in:
Lorenzo Torres 2025-08-08 03:18:08 +02:00
parent 214317e0bf
commit 503ed33aec
7 changed files with 181 additions and 48 deletions

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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,
};
}

View file

@ -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);

14
src/rendering/lights.zig Normal file
View file

@ -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),
};

View file

@ -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");

View file

@ -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,