diff --git a/assets/shaders/shader.frag b/assets/shaders/shader.frag index 7b369c5..42d00f1 100644 --- a/assets/shaders/shader.frag +++ b/assets/shaders/shader.frag @@ -78,5 +78,6 @@ void main() { 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/assets/shaders/shader.vert b/assets/shaders/shader.vert index a483346..6a82649 100644 --- a/assets/shaders/shader.vert +++ b/assets/shaders/shader.vert @@ -41,3 +41,4 @@ void main() { TexCoords = uv; gl_Position = out_vec; } + diff --git a/assets/shaders/terrain.frag b/assets/shaders/terrain.frag new file mode 100644 index 0000000..4aee9cb --- /dev/null +++ b/assets/shaders/terrain.frag @@ -0,0 +1,86 @@ +#version 450 + +#define MAX_POINT_LIGHTS 1024 + +struct PointLight { + vec3 position; + vec3 ambient; + vec3 diffuse; + vec3 specular; + vec3 data; +}; + +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 DirectionalLight { + vec3 direction; + + vec3 ambient; + vec3 diffuse; + vec3 specular; +} directional_light; +layout (binding = 5) uniform PointLights { + PointLight point_lights[MAX_POINT_LIGHTS]; +} point_lights; + +layout (binding = 3) uniform ViewUniform { + vec3 pos; +} viewPos; + +layout(push_constant) uniform pc { + int light_count; +} pushConstants; + +vec3 calc_directional_light(vec3 normal, vec3 viewDir) { + vec3 lightDir = normalize(-directional_light.direction); + float diff = max(dot(normal, lightDir), 0.0); + vec3 diffuse = directional_light.diffuse * diff; + return (directional_light.ambient + diffuse); +} + +vec3 calc_point_light(int index, vec3 normal, vec3 fragPos, vec3 viewDir) { + float constant = point_lights.point_lights[index].data[0]; + float linear = point_lights.point_lights[index].data[1]; + float quadratic = point_lights.point_lights[index].data[2]; + + vec3 lightDir = normalize(point_lights.point_lights[index].position - fragPos); + float diff = max(dot(normal, lightDir), 0.0); + vec3 reflectDir = reflect(-lightDir, normal); + float distance = length(point_lights.point_lights[index].position - fragPos); + float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance)); + vec3 ambient = point_lights.point_lights[index].ambient; + vec3 diffuse = point_lights.point_lights[index].diffuse * diff; + ambient *= attenuation; + diffuse *= attenuation; + return (ambient + diffuse); +} + +void main() { + vec3 norm = normalize(Normal); + vec3 viewDir = normalize(viewPos.pos - FragPos); + + vec3 result = calc_directional_light(norm, viewDir); + //vec3 result = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < pushConstants.light_count; i++) + result += calc_point_light(i, norm, FragPos, viewDir); + + vec3 tall = vec3(1.0, 0.0, 0.0); + vec3 mid = vec3(0.0, 1.0, 0.0); + vec3 water = vec3(0.0, 0.0, 1.0); + + vec3 color; + + if (FragPos.y < 0.05) { + color = water; + } else if (FragPos.y > 0.3) { + color = tall; + } else { + color = mid; + } + + outColor = vec4(color+result, 1.0); +} diff --git a/assets/shaders/terrain.vert b/assets/shaders/terrain.vert new file mode 100644 index 0000000..8caeabf --- /dev/null +++ b/assets/shaders/terrain.vert @@ -0,0 +1,38 @@ +#version 450 + +layout(location = 0) in vec3 vertPos; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec2 uv; + +layout (binding = 0) uniform ProjUniform { + mat4 proj; +} proj; + +layout (binding = 1) uniform ViewUniform { + mat4 view; +} view; + +layout(location = 2) out vec3 Normal; +layout(location = 3) out vec3 FragPos; +layout(location = 4) out vec2 TexCoords; + +layout (set = 1, binding = 0) uniform sampler2D diffuseSampler; + +void main() { + float texelSize = 1.0 / 200; + float hL = texture(diffuseSampler, uv - vec2(texelSize, 0.0)).r; + float hR = texture(diffuseSampler, uv + vec2(texelSize, 0.0)).r; + float hD = texture(diffuseSampler, uv - vec2(0.0, texelSize)).r; + float hU = texture(diffuseSampler, uv + vec2(0.0, texelSize)).r; + + float dX = (hR - hL) * 15.0; + float dY = (hU - hD) * 15.0; + + float y = texture(diffuseSampler, uv).x * 3; + vec4 out_vec = proj.proj * view.view * vec4(vec3(vertPos.x, y, vertPos.z), 1.0); + FragPos = vec3(vertPos.x, y, vertPos.z); + + Normal = normalize(vec3(-dX, -dY, 1.0)); + TexCoords = uv; + gl_Position = out_vec; +} diff --git a/assets/textures/heightmap.png b/assets/textures/heightmap.png new file mode 100644 index 0000000..12ef3d5 Binary files /dev/null and b/assets/textures/heightmap.png differ diff --git a/src/math.zig b/src/math.zig index 97f8265..b4f7e39 100644 --- a/src/math.zig +++ b/src/math.zig @@ -6,6 +6,8 @@ pub const rad = std.math.degreesToRadians; pub const deg = std.math.radiansToDegrees; pub const sqrt = std.math.sqrt; +pub const PerlinNoise = @import("math/PerlinNoise.zig"); + pub const Axis = struct { pub const x: [3]f32 = .{1.0, 0.0, 0.0}; pub const y: [3]f32 = .{0.0, 1.0, 0.0}; diff --git a/src/math/PerlinNoise.zig b/src/math/PerlinNoise.zig new file mode 100644 index 0000000..b5e5859 --- /dev/null +++ b/src/math/PerlinNoise.zig @@ -0,0 +1,78 @@ +const std = @import("std"); + +const Self = @This(); + +seed: u64, + +fn hash(self: Self, x: i32, y: i32) u64 { + var hasher = std.hash.Wyhash.init(self.seed); + hasher.update(std.mem.asBytes(&x)); + hasher.update(std.mem.asBytes(&y)); + return hasher.final(); +} + +fn random(self: Self, x: i32, y: i32) @Vector(2, f64) { + const h = self.hash(x, y); + var rng = std.Random.DefaultPrng.init(h); + const angle = rng.random().float(f64) * std.math.tau; + + return .{ std.math.cos(angle), std.math.sin(angle) }; +} + +fn dot(a: @Vector(2, f64), b: @Vector(2, f64)) f64 { + return @reduce(.Add, a*b); +} + +fn fade(t: f64) f64 { + return t * t * t * (t * (t * 6 - 15) + 10); +} + +fn lerp(a: f64, b: f64, t: f64) f64 { + return a + t * (b - a); +} + +fn noise(self: Self, x: f64, y: f64) f64 { + const x0 = @as(i32, @intFromFloat(std.math.floor(x))); + const y0 = @as(i32, @intFromFloat(std.math.floor(y))); + const x1 = x0 + 1; + const y1 = y0 + 1; + + const sx = fade(x - @as(f64, @floatFromInt(x0))); + const sy = fade(y - @as(f64, @floatFromInt(y0))); + + const grad00 = self.random(x0, y0); + const grad10 = self.random(x1, y0); + const grad01 = self.random(x0, y1); + const grad11 = self.random(x1, y1); + + const dx = x - @as(f64, @floatFromInt(x0)); + const dy = y - @as(f64, @floatFromInt(y0)); + + const dot00 = dot(grad00, .{ dx, dy }); + const dot10 = dot(grad10, .{ dx - 1, dy }); + const dot01 = dot(grad01, .{ dx, dy - 1 }); + const dot11 = dot(grad11, .{ dx - 1, dy - 1 }); + + const ix0 = lerp(dot00, dot10, sx); + const ix1 = lerp(dot01, dot11, sx); + + const value = lerp(ix0, ix1, sy); + + return value; +} + +pub fn fbm(self: Self, x: f64, y: f64, octaves: u32, lacunarity: f64, gain: f64) f64 { + var total: f64 = 0.0; + var amplitude: f64 = 1.0; + var frequency: f64 = 1.0; + var maxAmplitude: f64 = 1.0; + + for(0..octaves) |_| { + total += self.noise(x * frequency, y * frequency) * amplitude; + maxAmplitude += amplitude; + amplitude *= gain; + frequency *= lacunarity; + } + + return total / maxAmplitude; +} diff --git a/src/rendering/Camera.zig b/src/rendering/Camera.zig index 29e1c1c..b09f1ec 100644 --- a/src/rendering/Camera.zig +++ b/src/rendering/Camera.zig @@ -19,7 +19,7 @@ pitch: f32 = -45, yaw: f32 = 0, pub fn getProjection(width: usize, height: usize) math.Matrix { - return math.Matrix.perspective(math.rad(45.0), (@as(f32, @floatFromInt(width)) / @as(f32, @floatFromInt(height))), 0.1, 100.0); + return math.Matrix.perspective(math.rad(45.0), (@as(f32, @floatFromInt(width)) / @as(f32, @floatFromInt(height))), 0.1, 1000.0); } pub fn getView(self: *Camera) math.Matrix { diff --git a/src/rendering/Device.zig b/src/rendering/Device.zig index dd14ff0..44e3d7f 100644 --- a/src/rendering/Device.zig +++ b/src/rendering/Device.zig @@ -162,7 +162,12 @@ pub fn transitionImageLayout(self: Self, image: c.VkImage, format: c.VkFormat, o try self.endSingleTimeCommands(command_buffer); } - +pub fn drawTerrain(self: Self, indices: u32, frame: usize, vertex_buffer: vk.Buffer, index_buffer: vk.Buffer) void { + std.debug.assert(frame < frames_in_flight); + c.vkCmdBindIndexBuffer(self.command_buffers[frame], index_buffer.handle, 0, c.VK_INDEX_TYPE_UINT32); + self.bindVertexBuffer(vertex_buffer, frame); + c.vkCmdDrawIndexed(self.command_buffers[frame], indices, 1, 0, 0, 0); +} pub fn draw(self: Self, indices: u32, frame: usize, mesh: Mesh) void { std.debug.assert(frame < frames_in_flight); @@ -210,6 +215,11 @@ pub fn bindDescriptorSets(self: Self, pipeline: vk.GraphicsPipeline, frame: usiz c.vkCmdBindDescriptorSets(self.command_buffers[frame], c.VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 2, sets[0..].ptr, 0, null); } +pub fn bindTerrainSets(self: Self, pipeline: vk.TerrainPipeline, frame: usize) void { + const sets = [_]c.VkDescriptorSet {pipeline.descriptor_set, pipeline.heightmap}; + c.vkCmdBindDescriptorSets(self.command_buffers[frame], c.VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 2, sets[0..].ptr, 0, null); +} + pub fn updateBuffer(self: Self, comptime T: type, buffer: vk.Buffer, data: [*]T, frame: usize) void { c.vkCmdUpdateBuffer(self.command_buffers[frame], buffer.handle, 0, @sizeOf(T), @ptrCast(@alignCast(data))); } diff --git a/src/rendering/GraphicsPipeline.zig b/src/rendering/GraphicsPipeline.zig index f750132..a372f8a 100644 --- a/src/rendering/GraphicsPipeline.zig +++ b/src/rendering/GraphicsPipeline.zig @@ -25,11 +25,18 @@ view_buffer: vk.Buffer, view_memory: [*c]u8, transform_buffer: vk.DynamicBuffer(math.Transform), view_pos_memory: [*c]u8, +view_pos_buffer: vk.Buffer, diffuse_sampler: vk.Sampler, specular_sampler: vk.Sampler, textures: std.ArrayList(c.VkDescriptorSet), directional_light: *lights.DirectionalLight, +directional_light_buffer: vk.Buffer, point_lights: []lights.PointLight, +point_lights_buffer: vk.Buffer, + +device: vk.Device, +render_pass: vk.RenderPass, +swapchain: vk.Swapchain, const Self = @This(); @@ -638,6 +645,7 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re .view_buffer = view_buffer, .view_memory = view_data, .view_pos_memory = view_pos_data, + .view_pos_buffer = view_pos_buffer, .transform_buffer = transform_buffer, .diffuse_sampler = try vk.Sampler.init(device), .specular_sampler = try vk.Sampler.init(device), @@ -645,7 +653,13 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re .vertex_buffer = vertex_buffer, .index_buffer = index_buffer, .directional_light = directional_light, + .directional_light_buffer = directional_light_buffer, .point_lights = point_lights, + .point_lights_buffer = point_lights_buffer, + + .device = device, + .swapchain = swapchain, + .render_pass = render_pass, }; } diff --git a/src/rendering/Mesh.zig b/src/rendering/Mesh.zig index f5d58b6..b0d90b9 100644 --- a/src/rendering/Mesh.zig +++ b/src/rendering/Mesh.zig @@ -3,6 +3,7 @@ const vk = @import("vulkan.zig"); const gltf = @import("gltf.zig"); const Allocator = std.mem.Allocator; const c = vk.c; +const math = @import("math"); const Mesh = @This(); @@ -59,12 +60,99 @@ pub const Vertex = struct { } }; -pub fn init(allocator: Allocator, device: anytype) !Mesh { - const vertex_buffer = try Mesh.createVertexBuffer(allocator, device); - const index_buffer = try Mesh.createIndexBuffer(allocator, device); +fn createVertexBuffer(device: vk.Device, vertices: std.ArrayList([8]f32)) !vk.Buffer { + var data: [*c]?*anyopaque = null; - return Mesh{ - .vertex_buffer = vertex_buffer, - .index_buffer = index_buffer, - }; + const buffer = try device.initBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf([8]f32) * vertices.items.len); + + try vk.mapError(vk.c.vkMapMemory( + device.handle, + buffer.memory, + 0, + buffer.size, + 0, + @ptrCast(&data), + )); + + if (data) |ptr| { + const gpu_vertices: [*]Mesh.Vertex = @ptrCast(@alignCast(ptr)); + + @memcpy(gpu_vertices, @as([]Mesh.Vertex, @ptrCast(vertices.items[0..]))); + } + + vk.c.vkUnmapMemory(device.handle, buffer.memory); + + const vertex_buffer = try device.initBuffer(vk.BufferUsage{ .vertex_buffer = true, .transfer_dst = true, .transfer_src = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(Mesh.Vertex) * vertices.items.len); + + try buffer.copyTo(device, vertex_buffer, 0); + buffer.deinit(device.handle); + + return vertex_buffer; +} + +fn createIndexBuffer(device: vk.Device, indices: std.ArrayList(u32)) !vk.Buffer { + var data: [*c]?*anyopaque = null; + + const buffer = try device.initBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, @sizeOf(u32) * indices.items.len); + + try vk.mapError(vk.c.vkMapMemory( + device.handle, + buffer.memory, + 0, + buffer.size, + 0, + @ptrCast(&data), + )); + + if (data) |ptr| { + const gpu_indices: [*]u32 = @ptrCast(@alignCast(ptr)); + + @memcpy(gpu_indices, indices.items[0..]); + } + + vk.c.vkUnmapMemory(device.handle, buffer.memory); + + const index_buffer = try device.initBuffer(vk.BufferUsage{ .index_buffer = true, .transfer_dst = true, .transfer_src = true }, vk.BufferFlags{ .device_local = true }, @sizeOf(u32) * indices.items.len); + + try buffer.copyTo(device, index_buffer, 0); + buffer.deinit(device.handle); + + return index_buffer; +} + +pub fn terrain(allocator: std.mem.Allocator, device: vk.Device, width: usize, height: usize, resolution: f32) !struct { vk.Buffer, vk.Buffer } { + var vertices = std.ArrayList([8]f32).init(allocator); + defer vertices.deinit(); + var indices = std.ArrayList(u32).init(allocator); + defer indices.deinit(); + + for (0..width) |x| { + for (0..height) |z| { + const vertex: [8]f32 = .{@as(f32, @floatFromInt(x))/resolution, 0.0, @as(f32, @floatFromInt(z))/resolution, 0.0, 0.0, 0.0, @as(f32, @floatFromInt(x)) / @as(f32, @floatFromInt(width)), @as(f32, @floatFromInt(z)) / @as(f32, @floatFromInt(height))}; + try vertices.append(vertex); + } + } + + + for (0..width-1) |x| { + for (0..height-1) |z| { + const top_left = @as(u32, @intCast(z * width + x)); + const top_right = @as(u32, @intCast(z * width + (x+1))); + const bottom_left = @as(u32, @intCast((z+1) * width + x)); + const bottom_right = @as(u32, @intCast((z+1) * width + (x + 1))); + + try indices.append(top_left); + try indices.append(top_right); + try indices.append(bottom_left); + + try indices.append(top_right); + try indices.append(bottom_right); + try indices.append(bottom_left); + } + } + + const vertex_buffer = try createVertexBuffer(device, vertices); + const index_buffer = try createIndexBuffer(device, indices); + + return .{ vertex_buffer, index_buffer }; } diff --git a/src/rendering/Renderer.zig b/src/rendering/Renderer.zig index 5f08915..59a4896 100644 --- a/src/rendering/Renderer.zig +++ b/src/rendering/Renderer.zig @@ -16,11 +16,14 @@ device: vk.Device, render_pass: vk.RenderPass, swapchain: vk.Swapchain, graphics_pipeline: vk.GraphicsPipeline, +terrain_pipeline: vk.TerrainPipeline, current_frame: u32, mesh: Mesh, transforms: std.ArrayList(math.Transform), previous_time: std.time.Instant, current_image: usize, +terrain_vertex: vk.Buffer, +terrain_index: vk.Buffer, pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_handle: vk.c.VkSurfaceKHR) !Self { const instance: vk.Instance = .{ .handle = instance_handle }; @@ -41,13 +44,32 @@ pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_hand const mesh = try pipeline_builder.addMesh("assets/models/cube.glb"); var graphics_pipeline = try pipeline_builder.build(swapchain, render_pass, vertex_shader, fragment_shader); - // TODO: I think the renderer shouldn't have to interact with buffers. I think the API should change to - // something along the lines of - // renderer.begin() - // renderer.render(triangle); - // renderer.render(some_other_thing); - // ... - // renderer.submit() + const terrain_vertex_shader = try device.initShader("terrain_vert"); + defer device.deinitShader(terrain_vertex_shader); + const terrain_fragment_shader = try device.initShader("terrain_frag"); + defer device.deinitShader(terrain_fragment_shader); + + var terrain_pipeline = try vk.TerrainPipeline.init(graphics_pipeline, terrain_vertex_shader, terrain_fragment_shader); + + const perlin: math.PerlinNoise = .{ .seed = 54321 }; + const heightmap = try allocator.alloc(u32, 700 * 700); + defer allocator.free(heightmap); + for (0..700) |x| { + for (0..700) |y| { + const scale = 0.01; + var pixel = (perlin.fbm(@as(f64, @floatFromInt(x)) * scale, @as(f64, @floatFromInt(y)) * scale, 8, 3, 0.5) * 3); + pixel = std.math.pow(f64, pixel, 1.2); + const gray: u32 = @intFromFloat(pixel * 255); + const color: u32 = (255 << 24) | (gray << 16) | (gray << 8) | gray; + + heightmap[x*700 + y] = color; + } + } + + const terrain_vertex, const terrain_index = try Mesh.terrain(allocator, device, 700, 700, 10); + const heightmap_texture = try Texture.fromBytes(@alignCast(@ptrCast(heightmap)), device, 700, 700); + //const heightmap_texture = try Texture.init("assets/textures/heightmap.png", device); + try terrain_pipeline.setHeightmap(device, heightmap_texture); const texture = try Texture.init("assets/textures/container.png", device); const diffuse = try Texture.init("assets/textures/container_specular.png", device); @@ -79,6 +101,7 @@ pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_hand try transforms.append(math.Transform.init(.{0.0, 0.5, 1.0}, .{0.5, 0.5, 0.5}, .{0.0, 0.0, 0.0})); try transforms.append(math.Transform.init(.{0.0, 0.0, 0.0}, .{0.5, 0.5, 0.5}, .{0.0, 0.0, 0.0})); + return .{ .instance = instance, .surface = surface, @@ -87,11 +110,14 @@ pub fn init(allocator: Allocator, instance_handle: vk.c.VkInstance, surface_hand .render_pass = render_pass, .swapchain = swapchain, .graphics_pipeline = graphics_pipeline, + .terrain_pipeline = terrain_pipeline, .current_frame = 0, .transforms = transforms, .previous_time = try std.time.Instant.now(), .mesh = mesh, .current_image = undefined, + .terrain_vertex = terrain_vertex, + .terrain_index = terrain_index, }; } @@ -121,11 +147,19 @@ pub fn begin(self: *Self) !void { try self.device.resetCommand(self.current_frame); try self.device.beginCommand(self.current_frame); self.render_pass.begin(self.swapchain, self.device, image, self.current_frame); + self.current_image = image; +} + +pub fn beginGraphics(self: *Self) !void { self.graphics_pipeline.bind(self.device, self.current_frame); self.device.bindVertexBuffer(self.graphics_pipeline.vertex_buffer, self.current_frame); self.device.bindIndexBuffer(self.graphics_pipeline.index_buffer, self.current_frame); self.device.bindDescriptorSets(self.graphics_pipeline, self.current_frame, 0); - self.current_image = image; +} + +pub fn beginTerrain(self: *Self) !void { + self.terrain_pipeline.bind(self.device, self.current_frame); + self.device.bindTerrainSets(self.terrain_pipeline, self.current_frame); } pub fn setLightCount(self: *Self, count: u32) void { @@ -144,7 +178,5 @@ pub fn end(self: *Self) !void { self.current_frame = (self.current_frame + 1) % 2; - self.device.waitIdle(); -} - - + self.device.waitIdle(); + } diff --git a/src/rendering/TerrainPipeline.zig b/src/rendering/TerrainPipeline.zig new file mode 100644 index 0000000..7efb280 --- /dev/null +++ b/src/rendering/TerrainPipeline.zig @@ -0,0 +1,434 @@ +const std = @import("std"); +const Mesh = @import("Mesh.zig"); +const Camera = @import("Camera.zig"); +const vk = @import("vulkan.zig"); +const Texture = vk.Texture; +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, +//vertex_buffer: vk.Buffer, +//index_buffer: vk.Buffer, +texture_set_layout: c.VkDescriptorSetLayout, +descriptor_pool: c.VkDescriptorPool, +descriptor_set: c.VkDescriptorSet, +descriptor_set_layout: c.VkDescriptorSetLayout, +heightmap_sampler: vk.Sampler, +heightmap: c.VkDescriptorSet, + +const Self = @This(); + +pub fn init( + graphics_pipeline: vk.GraphicsPipeline, + vertex_shader: c.VkShaderModule, + fragment_shader: c.VkShaderModule) !Self { + + const device = graphics_pipeline.device; + const swapchain = graphics_pipeline.swapchain; + const render_pass = graphics_pipeline.render_pass; + + const vertex_shader_stage_info: c.VkPipelineShaderStageCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = c.VK_SHADER_STAGE_VERTEX_BIT, + .module = vertex_shader, + .pName = "main", + }; + + const fragment_shader_stage_info: c.VkPipelineShaderStageCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = c.VK_SHADER_STAGE_FRAGMENT_BIT, + .module = fragment_shader, + .pName = "main", + }; + + // TODO: shouldn't this be closer to usage? + const shader_stage_infos: []const c.VkPipelineShaderStageCreateInfo = &.{ vertex_shader_stage_info, fragment_shader_stage_info }; + + const vertex_attributes: []const c.VkVertexInputAttributeDescription = Mesh.Vertex.attributeDescriptions(); + const vertex_bindings: []const c.VkVertexInputBindingDescription = &.{Mesh.Vertex.bindingDescription()}; + + const vertex_input_info: c.VkPipelineVertexInputStateCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = vertex_bindings.ptr, + .vertexAttributeDescriptionCount = 3, + .pVertexAttributeDescriptions = vertex_attributes.ptr, + }; + + const input_assembly_info: c.VkPipelineInputAssemblyStateCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = c.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = c.VK_FALSE, + }; + + const viewport: c.VkViewport = .{ + .x = 0.0, + .y = 0.0, + .width = @floatFromInt(swapchain.extent.width), + .height = @floatFromInt(swapchain.extent.height), + .minDepth = 0.0, + .maxDepth = 1.0, + }; + + const scissor: c.VkRect2D = .{ + .offset = .{ + .x = 0.0, + .y = 0.0, + }, + .extent = swapchain.extent, + }; + + const viewport_state_info: c.VkPipelineViewportStateCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor, + }; + + const rasterizer_info: c.VkPipelineRasterizationStateCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = c.VK_FALSE, + .rasterizerDiscardEnable = c.VK_FALSE, + .polygonMode = c.VK_POLYGON_MODE_FILL, + .lineWidth = 1.0, + .cullMode = c.VK_CULL_MODE_BACK_BIT, + .frontFace = c.VK_FRONT_FACE_CLOCKWISE, + .depthBiasEnable = c.VK_FALSE, + }; + + const multisampling_info: c.VkPipelineMultisampleStateCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .sampleShadingEnable = c.VK_FALSE, + .rasterizationSamples = device.msaa_samples, + }; + + const color_blend_attachment: c.VkPipelineColorBlendAttachmentState = .{ + .colorWriteMask = c.VK_COLOR_COMPONENT_R_BIT | c.VK_COLOR_COMPONENT_G_BIT | c.VK_COLOR_COMPONENT_B_BIT | c.VK_COLOR_COMPONENT_A_BIT, + .blendEnable = c.VK_TRUE, + .srcColorBlendFactor = c.VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorBlendFactor = c.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = c.VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = c.VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = c.VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = c.VK_BLEND_OP_ADD, + }; + + const color_blend_info: c.VkPipelineColorBlendStateCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = c.VK_FALSE, + .logicOp = c.VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + .blendConstants = .{ 0.0, 0.0, 0.0, 0.0 }, + }; + + const projection_binding = c.VkDescriptorSetLayoutBinding{ + .binding = 0, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = c.VK_SHADER_STAGE_VERTEX_BIT, + }; + + const view_binding = c.VkDescriptorSetLayoutBinding{ + .binding = 1, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = c.VK_SHADER_STAGE_VERTEX_BIT, + }; + + 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 = 1, + .stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT, + }; + + const view_pos_binding = c.VkDescriptorSetLayoutBinding{ + .binding = 3, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT, + }; + + const heightmap_sampler_binding = c.VkDescriptorSetLayoutBinding{ + .binding = 0, + .descriptorType = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .stageFlags = c.VK_SHADER_STAGE_VERTEX_BIT, + }; + + const bindings = [_]c.VkDescriptorSetLayoutBinding{projection_binding, view_binding, directional_light_binding, point_lights_binding, view_pos_binding}; + const texture_bindings = [_]c.VkDescriptorSetLayoutBinding{heightmap_sampler_binding}; + + var descriptor_set_layout: c.VkDescriptorSetLayout = undefined; + var texture_descriptor_set_layout: c.VkDescriptorSetLayout = undefined; + + const descriptor_set_layout_info = c.VkDescriptorSetLayoutCreateInfo{ + .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 5, + .pBindings = bindings[0..].ptr, + }; + + const texture_descriptor_set_layout_info = c.VkDescriptorSetLayoutCreateInfo{ + .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, + .pBindings = texture_bindings[0..].ptr, + }; + + try vk.mapError(c.vkCreateDescriptorSetLayout(device.handle, &descriptor_set_layout_info, null, &descriptor_set_layout)); + try vk.mapError(c.vkCreateDescriptorSetLayout(device.handle, &texture_descriptor_set_layout_info, null, &texture_descriptor_set_layout)); + + var set_layouts = [_]c.VkDescriptorSetLayout{descriptor_set_layout, texture_descriptor_set_layout}; + + const lights_range: c.VkPushConstantRange = .{ + .stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = 4, + }; + + const range: [1]c.VkPushConstantRange = .{lights_range}; + + const layout_info: c.VkPipelineLayoutCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 2, + .pSetLayouts = set_layouts[0..].ptr, + .pushConstantRangeCount = 1, + .pPushConstantRanges = range[0..].ptr, + }; + + var layout: c.VkPipelineLayout = undefined; + + try vk.mapError(c.vkCreatePipelineLayout(device.handle, &layout_info, null, @ptrCast(&layout))); + + const depth_stencil: c.VkPipelineDepthStencilStateCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .depthTestEnable = c.VK_TRUE, + .depthWriteEnable = c.VK_TRUE, + .depthCompareOp = c.VK_COMPARE_OP_LESS, + .depthBoundsTestEnable = c.VK_FALSE, + .minDepthBounds = 0.0, + .maxDepthBounds = 1.0, + .stencilTestEnable = c.VK_FALSE, + }; + + const pipeline_info: c.VkGraphicsPipelineCreateInfo = .{ + .sType = c.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = 2, + .pStages = shader_stage_infos.ptr, + .pVertexInputState = &vertex_input_info, + .pInputAssemblyState = &input_assembly_info, + .pViewportState = &viewport_state_info, + .pRasterizationState = &rasterizer_info, + .pMultisampleState = &multisampling_info, + .pDepthStencilState = &depth_stencil, + .pColorBlendState = &color_blend_info, + .pDynamicState = null, + .layout = layout, + .renderPass = render_pass.handle, + .subpass = 0, + .basePipelineHandle = null, + .basePipelineIndex = -1, + }; + + var pipeline: c.VkPipeline = undefined; + + try vk.mapError(c.vkCreateGraphicsPipelines(device.handle, null, 1, &pipeline_info, null, @ptrCast(&pipeline))); + + const size = c.VkDescriptorPoolSize{ + .type = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 5, + }; + + const sampler_size = c.VkDescriptorPoolSize{ + .type = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + }; + + const sizes = [_]c.VkDescriptorPoolSize {size, sampler_size}; + + const descriptor_pool_info = c.VkDescriptorPoolCreateInfo{ + .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = 2, + .poolSizeCount = 2, + .pPoolSizes = sizes[0..].ptr, + }; + + var descriptor_pool: c.VkDescriptorPool = undefined; + + try vk.mapError(c.vkCreateDescriptorPool(device.handle, &descriptor_pool_info, null, &descriptor_pool)); + + const descriptor_allocate_info = c.VkDescriptorSetAllocateInfo{ + .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = set_layouts[0..].ptr, + }; + + var descriptor_set: c.VkDescriptorSet = undefined; + + try vk.mapError(c.vkAllocateDescriptorSets(device.handle, &descriptor_allocate_info, &descriptor_set)); + + const descriptor_buffer_info = c.VkDescriptorBufferInfo{ + .buffer = graphics_pipeline.projection_buffer.handle, + .offset = 0, + .range = graphics_pipeline.projection_buffer.size, + }; + + const write_descriptor_set = c.VkWriteDescriptorSet{ + .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = &descriptor_buffer_info, + }; + + c.vkUpdateDescriptorSets(device.handle, 1, &write_descriptor_set, 0, null); + + const view_descriptor_buffer_info = c.VkDescriptorBufferInfo{ + .buffer = graphics_pipeline.view_buffer.handle, + .offset = 0, + .range = graphics_pipeline.view_buffer.size, + }; + + const write_view_descriptor_set = c.VkWriteDescriptorSet{ + .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set, + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = &view_descriptor_buffer_info, + }; + + c.vkUpdateDescriptorSets(device.handle, 1, &write_view_descriptor_set, 0, null); + + const directional_light_descriptor_buffer_info = c.VkDescriptorBufferInfo{ + .buffer = graphics_pipeline.directional_light_buffer.handle, + .offset = 0, + .range = graphics_pipeline.directional_light_buffer.size, + }; + + 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 = &directional_light_descriptor_buffer_info, + }; + + c.vkUpdateDescriptorSets(device.handle, 1, &write_directional_light_descriptor_set, 0, null); + + var point_lights_descriptor_buffer_info: c.VkDescriptorBufferInfo = undefined; + point_lights_descriptor_buffer_info.buffer = graphics_pipeline.point_lights_buffer.handle; + point_lights_descriptor_buffer_info.offset = 0; + point_lights_descriptor_buffer_info.range = @sizeOf(lights.PointLight) * max_point_lights; + + const write_point_lights_descriptor_set = c.VkWriteDescriptorSet{ + .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set, + .dstBinding = 5, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = @ptrCast(&point_lights_descriptor_buffer_info), + }; + + c.vkUpdateDescriptorSets(device.handle, 1, &write_point_lights_descriptor_set, 0, null); + + const view_pos_descriptor_buffer_info = c.VkDescriptorBufferInfo{ + .buffer = graphics_pipeline.view_pos_buffer.handle, + .offset = 0, + .range = graphics_pipeline.view_pos_buffer.size, + }; + + const write_view_pos_descriptor_set = c.VkWriteDescriptorSet{ + .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set, + .dstBinding = 3, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = c.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = &view_pos_descriptor_buffer_info, + }; + + c.vkUpdateDescriptorSets(device.handle, 1, &write_view_pos_descriptor_set, 0, null); + + return .{ + .layout = layout, + .handle = pipeline, + .texture_set_layout = texture_descriptor_set_layout, + .descriptor_pool = descriptor_pool, + .descriptor_set = descriptor_set, + .descriptor_set_layout = descriptor_set_layout, + .heightmap_sampler = try vk.Sampler.init(device), + .heightmap = undefined, + }; +} + +pub fn setHeightmap(self: *Self, device: anytype, heightmap: Texture) !void { + var set_layouts = [_]c.VkDescriptorSetLayout{self.texture_set_layout}; + const descriptor_allocate_info = c.VkDescriptorSetAllocateInfo{ + .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = self.descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = set_layouts[0..].ptr, + }; + + var descriptor_set: c.VkDescriptorSet = undefined; + try vk.mapError(c.vkAllocateDescriptorSets(device.handle, &descriptor_allocate_info, &descriptor_set)); + + const texture_info: c.VkDescriptorImageInfo = .{ + .imageLayout = c.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .imageView = heightmap.image_view, + .sampler = self.heightmap_sampler.handle, + }; + + const write_texture_descriptor_set = c.VkWriteDescriptorSet{ + .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &texture_info, + }; + + c.vkUpdateDescriptorSets(device.handle, 1, &write_texture_descriptor_set, 0, null); + + self.heightmap = descriptor_set; +} + +pub fn bind(self: Self, device: vk.Device, frame: usize) void { + std.debug.assert(frame < 2); + c.vkCmdBindPipeline(device.command_buffers[frame], c.VK_PIPELINE_BIND_POINT_GRAPHICS, self.handle); +} + +pub fn deinit(self: Self, device: vk.Device) void { + self.textures.deinit(); + self.diffuse_sampler.deinit(device); + self.specular_sampler.deinit(device); + self.projection_buffer.deinit(device.handle); + c.vkDestroyDescriptorSetLayout(device.handle, self.descriptor_set_layout, null); + c.vkDestroyDescriptorPool(device.handle, self.descriptor_pool, null); + c.vkDestroyPipeline(device.handle, self.handle, null); + c.vkDestroyPipelineLayout(device.handle, self.layout, null); +} diff --git a/src/rendering/Texture.zig b/src/rendering/Texture.zig index 66bbb24..6b596d4 100644 --- a/src/rendering/Texture.zig +++ b/src/rendering/Texture.zig @@ -9,18 +9,11 @@ image: c.VkImage, image_memory: c.VkDeviceMemory, image_view: c.VkImageView, -pub fn init(path: [:0]const u8, device: anytype) !Texture { - var width: i32 = 0; - var height: i32 = 0; - var channels: i32 = 0; - - const pixels = stb.stbi_load(path, &width, &height, &channels, stb.STBI_rgb_alpha); - defer stb.stbi_image_free(pixels); - +pub fn fromBytes(data: []u8, device: anytype, width: u64, height: u64) !Texture { const size: c.VkDeviceSize = @as(u64, @intCast(width)) * @as(u64, @intCast(height)) * 4; const image_buffer = try device.initBuffer(vk.BufferUsage{ .transfer_src = true }, vk.BufferFlags{ .host_visible = true, .host_coherent = true }, size); - const pixel_bytes: [*]u8 = @ptrCast(pixels); + const pixel_bytes: [*]u8 = @ptrCast(data); var image_data: [*c]u8 = undefined; try vk.mapError(c.vkMapMemory( @@ -89,6 +82,17 @@ pub fn init(path: [:0]const u8, device: anytype) !Texture { }; } +pub fn init(path: [:0]const u8, device: anytype) !Texture { + var width: i32 = 0; + var height: i32 = 0; + var channels: i32 = 0; + + const pixels = stb.stbi_load(path, &width, &height, &channels, stb.STBI_rgb_alpha); + defer stb.stbi_image_free(pixels); + + return try Texture.fromBytes(pixels[0..(@as(usize, @intCast(width*height)))], device, @intCast(width), @intCast(height)); +} + pub fn deinit(self: Texture, device: vk.Device) void { c.vkDestroyImageView(device.handle, self.image_view, null); c.vkDestroyImage(device.handle, self.image, null); diff --git a/src/rendering/vulkan.zig b/src/rendering/vulkan.zig index 99a7c59..2864887 100644 --- a/src/rendering/vulkan.zig +++ b/src/rendering/vulkan.zig @@ -1,5 +1,6 @@ pub const Texture = @import("Texture.zig"); pub const GraphicsPipeline = @import("GraphicsPipeline.zig"); +pub const TerrainPipeline = @import("TerrainPipeline.zig"); pub const Device = @import("Device.zig"); pub const Swapchain = @import("Swapchain.zig"); pub const PhysicalDevice = @import("PhysicalDevice.zig"); @@ -112,8 +113,8 @@ pub const Sampler = struct { const create_info: c.VkSamplerCreateInfo = .{ .sType = c.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .magFilter = c.VK_FILTER_LINEAR, - .minFilter = c.VK_FILTER_LINEAR, + .magFilter = c.VK_FILTER_NEAREST, + .minFilter = c.VK_FILTER_NEAREST, .addressModeU = c.VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = c.VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = c.VK_SAMPLER_ADDRESS_MODE_REPEAT, diff --git a/src/systems.zig b/src/systems.zig index afbe7c6..d245735 100644 --- a/src/systems.zig +++ b/src/systems.zig @@ -20,6 +20,10 @@ pub fn render(pool: *ecs.Pool) anyerror!void { renderer.setLightCount(2); + try renderer.beginTerrain(); + renderer.device.drawTerrain(@as(u32, @intCast(renderer.terrain_index.size/@sizeOf(u32))), renderer.current_frame, renderer.terrain_vertex, renderer.terrain_index); + + try renderer.beginGraphics(); for (renderer.transforms.items, 0..) |transform, i| { transform_memory[i] = transform; renderer.setTransform(@intCast(i));