Implemented terrain texturing

This commit is contained in:
Lorenzo Torres 2025-08-14 22:53:53 +02:00
parent 1475fd2101
commit 9fdd47ea6e
19 changed files with 1977 additions and 87 deletions

View file

@ -12,6 +12,8 @@ struct PointLight {
layout(location = 0) out vec4 outColor; layout(location = 0) out vec4 outColor;
in vec4 gl_FragCoord;
layout(location = 2) in vec3 Normal; layout(location = 2) in vec3 Normal;
layout(location = 3) in vec3 FragPos; layout(location = 3) in vec3 FragPos;
layout(location = 4) in vec2 TexCoords; layout(location = 4) in vec2 TexCoords;
@ -35,14 +37,19 @@ layout(push_constant) uniform pc {
int light_count; int light_count;
} pushConstants; } pushConstants;
vec3 calc_directional_light(vec3 normal, vec3 viewDir) { layout (set = 1, binding = 1) uniform sampler2D sand;
layout (set = 1, binding = 2) uniform sampler2D grass;
layout (set = 1, binding = 3) uniform sampler2D rock;
vec3 calc_directional_light(vec3 normal, vec3 viewDir, vec3 diffuse) {
vec3 lightDir = normalize(-directional_light.direction); vec3 lightDir = normalize(-directional_light.direction);
float diff = max(dot(normal, lightDir), 0.0); float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = directional_light.diffuse * diff; vec3 ambient = directional_light.ambient * diffuse;
return (directional_light.ambient + diffuse); vec3 d = directional_light.diffuse * diff * diffuse;
return (ambient + d);
} }
vec3 calc_point_light(int index, vec3 normal, vec3 fragPos, vec3 viewDir) { vec3 calc_point_light(int index, vec3 normal, vec3 fragPos, vec3 viewDir, vec3 diffuse) {
float constant = point_lights.point_lights[index].data[0]; float constant = point_lights.point_lights[index].data[0];
float linear = point_lights.point_lights[index].data[1]; float linear = point_lights.point_lights[index].data[1];
float quadratic = point_lights.point_lights[index].data[2]; float quadratic = point_lights.point_lights[index].data[2];
@ -52,35 +59,43 @@ vec3 calc_point_light(int index, vec3 normal, vec3 fragPos, vec3 viewDir) {
vec3 reflectDir = reflect(-lightDir, normal); vec3 reflectDir = reflect(-lightDir, normal);
float distance = length(point_lights.point_lights[index].position - fragPos); float distance = length(point_lights.point_lights[index].position - fragPos);
float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance)); float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance));
vec3 ambient = point_lights.point_lights[index].ambient; vec3 ambient = point_lights.point_lights[index].ambient * diffuse;
vec3 diffuse = point_lights.point_lights[index].diffuse * diff; vec3 d = point_lights.point_lights[index].diffuse * diff * diffuse;
ambient *= attenuation; ambient *= attenuation;
diffuse *= attenuation; d *= attenuation;
return (ambient + diffuse); return (ambient + d);
} }
void main() { void main() {
vec3 norm = normalize(Normal); vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos.pos - FragPos); vec3 viewDir = normalize(viewPos.pos - FragPos);
vec3 result = calc_directional_light(norm, viewDir);
float height = FragPos.y;
float sandWeight = 1.0 - smoothstep(0.0, 0.035, height);
float grassWeight = smoothstep(0.035, 0.15, height) - smoothstep(0.25, 0.4, height);
float rockWeight = smoothstep(0.25, 0.4, height);
float total = sandWeight + grassWeight + rockWeight;
sandWeight /= total;
grassWeight /= total;
rockWeight /= total;
vec4 sandColor = texture(sand, TexCoords);
vec4 grassColor = texture(grass, TexCoords);
vec4 rockColor = texture(rock, TexCoords);
vec4 finalColor = sandColor * sandWeight +
grassColor * grassWeight +
rockColor * rockWeight;
vec3 result = calc_directional_light(norm, viewDir, vec3(finalColor));
//vec3 result = vec3(0.0, 0.0, 0.0); //vec3 result = vec3(0.0, 0.0, 0.0);
for(int i = 0; i < pushConstants.light_count; i++) for(int i = 0; i < pushConstants.light_count; i++)
result += calc_point_light(i, norm, FragPos, viewDir); result += calc_point_light(i, norm, FragPos, viewDir, vec3(finalColor));
vec3 tall = vec3(1.0, 0.0, 0.0); outColor = vec4(result, 1.0);
vec3 mid = vec3(0.0, 1.0, 0.0); //outColor = vec4(finalColor);
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);
} }

View file

@ -1,5 +1,6 @@
#version 450 #version 450
layout(location = 0) in vec3 vertPos; layout(location = 0) in vec3 vertPos;
layout(location = 1) in vec3 normal; layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 uv; layout(location = 2) in vec2 uv;
@ -19,17 +20,18 @@ layout(location = 4) out vec2 TexCoords;
layout (set = 1, binding = 0) uniform sampler2D diffuseSampler; layout (set = 1, binding = 0) uniform sampler2D diffuseSampler;
void main() { void main() {
float texelSize = 1.0 / 200; float texelSize = 1.0 / (50*4);
float hL = texture(diffuseSampler, uv - vec2(texelSize, 0.0)).r; float hL = texture(diffuseSampler, uv - vec2(texelSize, 0.0)).r * 10;
float hR = texture(diffuseSampler, uv + vec2(texelSize, 0.0)).r; float hR = texture(diffuseSampler, uv + vec2(texelSize, 0.0)).r * 10;
float hD = texture(diffuseSampler, uv - vec2(0.0, texelSize)).r; float hD = texture(diffuseSampler, uv - vec2(0.0, texelSize)).r * 10;
float hU = texture(diffuseSampler, uv + vec2(0.0, texelSize)).r; float hU = texture(diffuseSampler, uv + vec2(0.0, texelSize)).r * 10;
float dX = (hR - hL) * 15.0; float dX = (hR - hL) * 15.0;
float dY = (hU - hD) * 15.0; float dY = (hU - hD) * 15.0;
float y = texture(diffuseSampler, uv).x * 3; float y = texture(diffuseSampler, uv).x;
vec4 out_vec = proj.proj * view.view * vec4(vec3(vertPos.x, y, vertPos.z), 1.0);
vec4 out_vec = proj.proj * view.view * vec4(vec3(vertPos.x, y * 10, vertPos.z), 1.0);
FragPos = vec3(vertPos.x, y, vertPos.z); FragPos = vec3(vertPos.x, y, vertPos.z);
Normal = normalize(vec3(-dX, -dY, 1.0)); Normal = normalize(vec3(-dX, -dY, 1.0));

BIN
assets/textures/grass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
assets/textures/rock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

BIN
assets/textures/sand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

BIN
colormap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

2
ext/stb_image.c vendored
View file

@ -1,2 +1,4 @@
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h" #include "stb_image.h"
#include "stb_image_write.h"

1724
ext/stb_image_write.h vendored Normal file

File diff suppressed because it is too large Load diff

BIN
heightmap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

View file

@ -279,3 +279,7 @@ pub fn normalize(a: @Vector(3, f32)) @Vector(3, f32) {
pub inline fn scaleVector(a: @Vector(3, f32), s: f32) @Vector(3, f32) { pub inline fn scaleVector(a: @Vector(3, f32), s: f32) @Vector(3, f32) {
return a * @as(@Vector(3, f32), @splat(s)); return a * @as(@Vector(3, f32), @splat(s));
} }
pub inline fn inverseLerp(a: f64, b: f64, t: f64) f64 {
return (t - a) / (b - a);
}

View file

@ -62,17 +62,16 @@ fn noise(self: Self, x: f64, y: f64) f64 {
} }
pub fn fbm(self: Self, x: f64, y: f64, octaves: u32, lacunarity: f64, gain: f64) f64 { 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 amplitude: f64 = 1.0;
var frequency: f64 = 1.0; var frequency: f64 = 1.0;
var maxAmplitude: f64 = 1.0; var maxAmplitude: f64 = 0.0;
for(0..octaves) |_| { for(0..octaves) |_| {
total += self.noise(x * frequency, y * frequency) * amplitude; const value = (self.noise(x * frequency, y * frequency) * 2) - 1;
maxAmplitude += amplitude; maxAmplitude += amplitude * value;
amplitude *= gain; amplitude *= gain;
frequency *= lacunarity; frequency *= lacunarity;
} }
return total / maxAmplitude; return maxAmplitude;
} }

View file

@ -216,7 +216,7 @@ pub fn bindDescriptorSets(self: Self, pipeline: vk.GraphicsPipeline, frame: usiz
} }
pub fn bindTerrainSets(self: Self, pipeline: vk.TerrainPipeline, frame: usize) void { pub fn bindTerrainSets(self: Self, pipeline: vk.TerrainPipeline, frame: usize) void {
const sets = [_]c.VkDescriptorSet {pipeline.descriptor_set, pipeline.heightmap}; const sets = [_]c.VkDescriptorSet {pipeline.descriptor_set, pipeline.map};
c.vkCmdBindDescriptorSets(self.command_buffers[frame], c.VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 2, sets[0..].ptr, 0, null); c.vkCmdBindDescriptorSets(self.command_buffers[frame], c.VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 2, sets[0..].ptr, 0, null);
} }

View file

@ -647,8 +647,8 @@ pub fn init(allocator: Allocator, device: vk.Device, swapchain: vk.Swapchain, re
.view_pos_memory = view_pos_data, .view_pos_memory = view_pos_data,
.view_pos_buffer = view_pos_buffer, .view_pos_buffer = view_pos_buffer,
.transform_buffer = transform_buffer, .transform_buffer = transform_buffer,
.diffuse_sampler = try vk.Sampler.init(device), .diffuse_sampler = try vk.Sampler.init(device, .linear),
.specular_sampler = try vk.Sampler.init(device), .specular_sampler = try vk.Sampler.init(device, .linear),
.textures = std.ArrayList(c.VkDescriptorSet).init(allocator), .textures = std.ArrayList(c.VkDescriptorSet).init(allocator),
.vertex_buffer = vertex_buffer, .vertex_buffer = vertex_buffer,
.index_buffer = index_buffer, .index_buffer = index_buffer,

View file

@ -120,26 +120,29 @@ fn createIndexBuffer(device: vk.Device, indices: std.ArrayList(u32)) !vk.Buffer
return index_buffer; return index_buffer;
} }
pub fn terrain(allocator: std.mem.Allocator, device: vk.Device, width: usize, height: usize, resolution: f32) !struct { vk.Buffer, vk.Buffer } { pub fn terrain(allocator: std.mem.Allocator, device: vk.Device, width: usize, height: usize, resolution: usize) !struct { vk.Buffer, vk.Buffer } {
var vertices = std.ArrayList([8]f32).init(allocator); var vertices = std.ArrayList([8]f32).init(allocator);
defer vertices.deinit(); defer vertices.deinit();
var indices = std.ArrayList(u32).init(allocator); var indices = std.ArrayList(u32).init(allocator);
defer indices.deinit(); defer indices.deinit();
for (0..width) |x| { for (0..width*resolution) |x| {
for (0..height) |z| { for (0..height*resolution) |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))}; const offset_x = @as(f32, @floatFromInt(x)) / @as(f32, @floatFromInt(width*resolution - 1)) * @as(f32, @floatFromInt(width));
const offset_z = @as(f32, @floatFromInt(z)) / @as(f32, @floatFromInt(height*resolution - 1)) * @as(f32, @floatFromInt(height));
const vertex: [8]f32 = .{offset_x, 0.0, offset_z, 0.0, 0.0, 0.0, @as(f32, @floatFromInt(x)) / @as(f32, @floatFromInt(width*resolution - 1)), @as(f32, @floatFromInt(z)) / @as(f32, @floatFromInt(height*resolution - 1))};
try vertices.append(vertex); try vertices.append(vertex);
} }
} }
for (0..width-1) |x| { for (0..width*resolution-1) |x| {
for (0..height-1) |z| { for (0..height*resolution-1) |z| {
const top_left = @as(u32, @intCast(z * width + x)); const top_left = @as(u32, @intCast(z * width*resolution + x));
const top_right = @as(u32, @intCast(z * width + (x+1))); const top_right = @as(u32, @intCast(z * width*resolution + (x+1)));
const bottom_left = @as(u32, @intCast((z+1) * width + x)); const bottom_left = @as(u32, @intCast((z+1) * width*resolution + x));
const bottom_right = @as(u32, @intCast((z+1) * width + (x + 1))); const bottom_right = @as(u32, @intCast((z+1) * width*resolution + (x + 1)));
try indices.append(top_left); try indices.append(top_left);
try indices.append(top_right); try indices.append(top_right);

View file

@ -2,6 +2,7 @@ const std = @import("std");
const vk = @import("vulkan.zig"); const vk = @import("vulkan.zig");
const Mesh = @import("Mesh.zig"); const Mesh = @import("Mesh.zig");
const math = @import("math"); const math = @import("math");
const stb = vk.Texture.stb;
const Self = @This(); const Self = @This();
pub const Generator = struct { pub const Generator = struct {
@ -15,7 +16,7 @@ pub const Generator = struct {
width: usize, width: usize,
height: usize, height: usize,
seed: u64, seed: u64,
resolution: f32 = 1.0, resolution: usize = 1,
}; };
heightmap: []f64, heightmap: []f64,
@ -27,25 +28,56 @@ texture: vk.Texture,
vertex_buffer: vk.Buffer, vertex_buffer: vk.Buffer,
index_buffer: vk.Buffer, index_buffer: vk.Buffer,
const layers: [5][3]u32 = .{
.{0, 94, 255},
.{222, 208, 20},
.{14, 122, 41},
.{64, 20, 20},
.{253, 253, 253},
};
pub fn init(allocator: std.mem.Allocator, device: vk.Device, generator: Generator) !Self { pub fn init(allocator: std.mem.Allocator, device: vk.Device, generator: Generator) !Self {
const perlin: math.PerlinNoise = .{ .seed = generator.seed }; const perlin: math.PerlinNoise = .{ .seed = generator.seed };
const heightmap = try allocator.alloc(f64, generator.width * generator.height); const heightmap = try allocator.alloc(f64, generator.width*generator.resolution * generator.height*generator.resolution);
const heightmap_data = try allocator.alloc(u32, generator.width * generator.height); const heightmap_data = try allocator.alloc(u32, generator.width*generator.resolution * generator.height*generator.resolution);
defer allocator.free(heightmap_data); defer allocator.free(heightmap_data);
for (0..generator.width) |x| {
for (0..generator.height) |y| {
var pixel = (perlin.fbm(@as(f64, @floatFromInt(x)) * generator.scale, @as(f64, @floatFromInt(y)) * generator.scale, generator.octaves, generator.lacunarity, generator.gain) * generator.multiplier);
pixel = std.math.pow(f64, pixel, generator.exponent);
const gray: u32 = @intFromFloat(pixel * 255);
const color: u32 = (255 << 24) | (gray << 16) | (gray << 8) | gray;
heightmap[x*generator.width + y] = pixel; var max_noise_height = std.math.floatMin(f64);
heightmap_data[x*generator.width + y] = color; var min_noise_height = std.math.floatMax(f64);
const columns = generator.width*generator.resolution;
const rows = generator.height*generator.resolution;
for (0..columns) |x| {
for (0..rows) |y| {
const u = @as(f64, @floatFromInt(x)) / @as(f64, @floatFromInt(columns - 1));
const v = @as(f64, @floatFromInt(y)) / @as(f64, @floatFromInt(rows - 1));
const h_x = u * @as(f64, @floatFromInt(generator.width));
const h_y = v * @as(f64, @floatFromInt(generator.height));
var pixel = (perlin.fbm(h_x / generator.scale, h_y / generator.scale, generator.octaves, generator.lacunarity, generator.gain) * generator.multiplier);
if (pixel > max_noise_height) {
max_noise_height = pixel;
} else if (pixel < min_noise_height) {
min_noise_height = pixel;
}
pixel = std.math.pow(f64, pixel, generator.exponent);
pixel = math.inverseLerp(min_noise_height, max_noise_height, pixel);
heightmap[x*columns + y] = pixel;
const gray: u32 = @intFromFloat(pixel * 255);
const grayscale: u32 = (255 << 24) | (gray << 16) | (gray << 8) | gray;
heightmap_data[x*columns + y] = grayscale;
} }
} }
const vertex_buffer, const index_buffer = try Mesh.terrain(allocator, device, generator.width, generator.height, generator.resolution); const vertex_buffer, const index_buffer = try Mesh.terrain(allocator, device, generator.width, generator.height, generator.resolution);
const heightmap_texture = try vk.Texture.fromBytes(@alignCast(@ptrCast(heightmap_data)), device, generator.width, generator.height); const heightmap_texture = try vk.Texture.fromBytes(@alignCast(@ptrCast(heightmap_data)), device, generator.width*generator.resolution, generator.height*generator.resolution);
return .{ return .{
.heightmap = heightmap, .heightmap = heightmap,
@ -68,3 +100,21 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator, device: vk.Device) !voi
pub fn getHeight(self: Self, x: usize, y: usize) f64 { pub fn getHeight(self: Self, x: usize, y: usize) f64 {
return self.heightmap[x*self.width + y]; return self.heightmap[x*self.width + y];
} }
fn pixelToNoise(
x: usize,
y: usize,
width: usize,
height: usize,
world_min: @Vector(2, f64),
world_max: @Vector(2, f64),
scale: f64
) struct { f64, f64 }{
const u = (@as(f64, @floatFromInt(x)) + 0.5) / @as(f64, @floatFromInt(width));
const v = (@as(f64, @floatFromInt(y)) + 0.5) / @as(f64, @floatFromInt(height));
const world_x = world_min[0] + u * (world_max[0] - world_min[0]);
const world_y = world_min[1] + v * (world_max[1] - world_min[1]);
return .{ world_x / scale, world_y / scale };
}

View file

@ -21,7 +21,10 @@ descriptor_pool: c.VkDescriptorPool,
descriptor_set: c.VkDescriptorSet, descriptor_set: c.VkDescriptorSet,
descriptor_set_layout: c.VkDescriptorSetLayout, descriptor_set_layout: c.VkDescriptorSetLayout,
heightmap_sampler: vk.Sampler, heightmap_sampler: vk.Sampler,
heightmap: c.VkDescriptorSet, sand_sampler: vk.Sampler,
grass_sampler: vk.Sampler,
rock_sampler: vk.Sampler,
map: c.VkDescriptorSet,
const Self = @This(); const Self = @This();
@ -172,8 +175,29 @@ pub fn init(
.stageFlags = c.VK_SHADER_STAGE_VERTEX_BIT, .stageFlags = c.VK_SHADER_STAGE_VERTEX_BIT,
}; };
const sand_sampler_binding = c.VkDescriptorSetLayoutBinding{
.binding = 1,
.descriptorType = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT,
};
const grass_sampler_binding = c.VkDescriptorSetLayoutBinding{
.binding = 2,
.descriptorType = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT,
};
const stone_sampler_binding = c.VkDescriptorSetLayoutBinding{
.binding = 3,
.descriptorType = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = c.VK_SHADER_STAGE_FRAGMENT_BIT,
};
const bindings = [_]c.VkDescriptorSetLayoutBinding{projection_binding, view_binding, directional_light_binding, point_lights_binding, view_pos_binding}; 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}; const texture_bindings = [_]c.VkDescriptorSetLayoutBinding{heightmap_sampler_binding, sand_sampler_binding, grass_sampler_binding, stone_sampler_binding};
var descriptor_set_layout: c.VkDescriptorSetLayout = undefined; var descriptor_set_layout: c.VkDescriptorSetLayout = undefined;
var texture_descriptor_set_layout: c.VkDescriptorSetLayout = undefined; var texture_descriptor_set_layout: c.VkDescriptorSetLayout = undefined;
@ -186,7 +210,7 @@ pub fn init(
const texture_descriptor_set_layout_info = c.VkDescriptorSetLayoutCreateInfo{ const texture_descriptor_set_layout_info = c.VkDescriptorSetLayoutCreateInfo{
.sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1, .bindingCount = 4,
.pBindings = texture_bindings[0..].ptr, .pBindings = texture_bindings[0..].ptr,
}; };
@ -256,7 +280,7 @@ pub fn init(
const sampler_size = c.VkDescriptorPoolSize{ const sampler_size = c.VkDescriptorPoolSize{
.type = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .type = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1, .descriptorCount = 4,
}; };
const sizes = [_]c.VkDescriptorPoolSize {size, sampler_size}; const sizes = [_]c.VkDescriptorPoolSize {size, sampler_size};
@ -379,12 +403,15 @@ pub fn init(
.descriptor_pool = descriptor_pool, .descriptor_pool = descriptor_pool,
.descriptor_set = descriptor_set, .descriptor_set = descriptor_set,
.descriptor_set_layout = descriptor_set_layout, .descriptor_set_layout = descriptor_set_layout,
.heightmap_sampler = try vk.Sampler.init(device), .heightmap_sampler = try vk.Sampler.init(device, .nearest),
.heightmap = undefined, .sand_sampler = try vk.Sampler.init(device, .linear),
.grass_sampler = try vk.Sampler.init(device, .linear),
.rock_sampler = try vk.Sampler.init(device, .linear),
.map = undefined,
}; };
} }
pub fn setHeightmap(self: *Self, device: anytype, heightmap: Texture) !void { pub fn setMaps(self: *Self, device: anytype, heightmap: Texture) !void {
var set_layouts = [_]c.VkDescriptorSetLayout{self.texture_set_layout}; var set_layouts = [_]c.VkDescriptorSetLayout{self.texture_set_layout};
const descriptor_allocate_info = c.VkDescriptorSetAllocateInfo{ const descriptor_allocate_info = c.VkDescriptorSetAllocateInfo{
.sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .sType = c.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
@ -396,25 +423,79 @@ pub fn setHeightmap(self: *Self, device: anytype, heightmap: Texture) !void {
var descriptor_set: c.VkDescriptorSet = undefined; var descriptor_set: c.VkDescriptorSet = undefined;
try vk.mapError(c.vkAllocateDescriptorSets(device.handle, &descriptor_allocate_info, &descriptor_set)); try vk.mapError(c.vkAllocateDescriptorSets(device.handle, &descriptor_allocate_info, &descriptor_set));
const texture_info: c.VkDescriptorImageInfo = .{ const height_info: c.VkDescriptorImageInfo = .{
.imageLayout = c.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageLayout = c.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.imageView = heightmap.image_view, .imageView = heightmap.image_view,
.sampler = self.heightmap_sampler.handle, .sampler = self.heightmap_sampler.handle,
}; };
const write_texture_descriptor_set = c.VkWriteDescriptorSet{ const sand = try Texture.init("assets/textures/sand.png", device);
const grass = try Texture.init("assets/textures/grass.png", device);
const rock = try Texture.init("assets/textures/rock.png", device);
const sand_info: c.VkDescriptorImageInfo = .{
.imageLayout = c.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.imageView = sand.image_view,
.sampler = self.sand_sampler.handle,
};
const grass_info: c.VkDescriptorImageInfo = .{
.imageLayout = c.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.imageView = grass.image_view,
.sampler = self.grass_sampler.handle,
};
const rock_info: c.VkDescriptorImageInfo = .{
.imageLayout = c.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.imageView = rock.image_view,
.sampler = self.rock_sampler.handle,
};
const write_height_descriptor_set = c.VkWriteDescriptorSet{
.sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .sType = c.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = descriptor_set, .dstSet = descriptor_set,
.dstBinding = 0, .dstBinding = 0,
.dstArrayElement = 0, .dstArrayElement = 0,
.descriptorCount = 1, .descriptorCount = 1,
.descriptorType = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorType = c.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &texture_info, .pImageInfo = &height_info,
}; };
c.vkUpdateDescriptorSets(device.handle, 1, &write_texture_descriptor_set, 0, null); const write_sand_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_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &sand_info,
};
self.heightmap = descriptor_set; const write_grass_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_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &grass_info,
};
const write_rock_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_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &rock_info,
};
const writes = [_]c.VkWriteDescriptorSet {write_height_descriptor_set, write_sand_descriptor_set, write_grass_descriptor_set, write_rock_descriptor_set};
c.vkUpdateDescriptorSets(device.handle, 4, writes[0..].ptr, 0, null);
self.map = descriptor_set;
} }
pub fn bind(self: Self, device: vk.Device, frame: usize) void { pub fn bind(self: Self, device: vk.Device, frame: usize) void {

View file

@ -3,6 +3,7 @@ const vk = @import("vulkan.zig");
const c = vk.c; const c = vk.c;
pub const stb = @cImport({ pub const stb = @cImport({
@cInclude("stb_image.h"); @cInclude("stb_image.h");
@cInclude("stb_image_write.h");
}); });
image: c.VkImage, image: c.VkImage,

View file

@ -32,6 +32,8 @@ const validation_layers: []const [*c]const u8 = if (!debug) &[0][*c]const u8{} e
pub const Error = error{ pub const Error = error{
out_of_host_memory, out_of_host_memory,
out_of_device_memory, out_of_device_memory,
out_of_pool_memory,
fragmented_pool,
initialization_failed, initialization_failed,
layer_not_present, layer_not_present,
extension_not_present, extension_not_present,
@ -50,6 +52,8 @@ pub fn mapError(result: c_int) !void {
c.VK_ERROR_LAYER_NOT_PRESENT => Error.layer_not_present, c.VK_ERROR_LAYER_NOT_PRESENT => Error.layer_not_present,
c.VK_ERROR_EXTENSION_NOT_PRESENT => Error.extension_not_present, c.VK_ERROR_EXTENSION_NOT_PRESENT => Error.extension_not_present,
c.VK_ERROR_INCOMPATIBLE_DRIVER => Error.incompatible_driver, c.VK_ERROR_INCOMPATIBLE_DRIVER => Error.incompatible_driver,
c.VK_ERROR_OUT_OF_POOL_MEMORY => Error.out_of_pool_memory,
c.VK_ERROR_FRAGMENTED_POOL => Error.fragmented_pool,
else => Error.unknown_error, else => Error.unknown_error,
}; };
} }
@ -105,16 +109,21 @@ pub const Buffer = struct {
} }
}; };
pub const SamplerType = enum {
linear,
nearest,
};
pub const Sampler = struct { pub const Sampler = struct {
handle: c.VkSampler, handle: c.VkSampler,
pub fn init(device: anytype) !Sampler { pub fn init(device: anytype, filter: SamplerType) !Sampler {
var sampler: c.VkSampler = undefined; var sampler: c.VkSampler = undefined;
const create_info: c.VkSamplerCreateInfo = .{ const create_info: c.VkSamplerCreateInfo = .{
.sType = c.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .sType = c.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = c.VK_FILTER_NEAREST, .magFilter = if (filter == .linear) c.VK_FILTER_LINEAR else c.VK_FILTER_NEAREST,
.minFilter = c.VK_FILTER_NEAREST, .minFilter = if (filter == .linear) c.VK_FILTER_LINEAR else c.VK_FILTER_NEAREST,
.addressModeU = c.VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeU = c.VK_SAMPLER_ADDRESS_MODE_REPEAT,
.addressModeV = c.VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = c.VK_SAMPLER_ADDRESS_MODE_REPEAT,
.addressModeW = c.VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = c.VK_SAMPLER_ADDRESS_MODE_REPEAT,

View file

@ -146,20 +146,20 @@ export fn sideros_init(init: api.GameInit) callconv(.c) void {
renderer = Renderer.init(allocator, @ptrCast(init.instance), @ptrCast(init.surface)) catch @panic("TODO: Gracefully handle error"); renderer = Renderer.init(allocator, @ptrCast(init.instance), @ptrCast(init.surface)) catch @panic("TODO: Gracefully handle error");
resources.terrain = rendering.Terrain.init(allocator, renderer.device, .{ resources.terrain = rendering.Terrain.init(allocator, renderer.device, .{
.octaves = 8, .octaves = 4,
.lacunarity = 3.0, .lacunarity = 2.0,
.gain = 0.5, .gain = 0.5,
.scale = 0.01, .scale = 30,
.multiplier = 3.0, .multiplier = 1.0,
.exponent = 1.2, .exponent = 1.0,
.width = 700, .width = 100,
.height = 700, .height = 100,
.seed = 12345678, .seed = 2497852058242342,
.resolution = 10.0, .resolution = 1,
}) catch @panic("TODO: handle this"); }) catch @panic("TODO: handle this");
renderer.terrain_pipeline.setHeightmap(renderer.device, resources.terrain.texture) catch @panic("TODO: handle this"); renderer.terrain_pipeline.setMaps(renderer.device, resources.terrain.texture) catch @panic("TODO: handle this");
pool.addSystemGroup(&[_]ecs.System{systems.render, systems.moveCamera}, true) catch @panic("TODO: Gracefuly handle error"); pool.addSystemGroup(&[_]ecs.System{systems.render, systems.moveCamera}, true) catch @panic("TODO: Gracefuly handle error");
pool.resources.renderer = &renderer; pool.resources.renderer = &renderer;