First part of terrain generation (hopefully) done, next part is to make the tesselation shaders

This commit is contained in:
connellpaxton 2024-02-05 07:53:37 -05:00
parent 6870cb61f9
commit a778a406d0
11 changed files with 211 additions and 36 deletions

View File

@ -18,8 +18,9 @@ struct Buffer {
inline void upload(const uint8_t* data) { inline void upload(const uint8_t* data) {
upload(data, size); upload(data, size);
} }
inline void upload(const std::vector<uint8_t>& data) { template<typename T>
upload(data.data(), size); inline void upload(const std::vector<T>& data) {
upload(reinterpret_cast<uint8_t*>(data.data()), data.size()*sizeof(T));
} }
operator vk::Buffer& () { operator vk::Buffer& () {

View File

@ -2,6 +2,8 @@
#include <Renderer/Pipeline.hpp> #include <Renderer/Pipeline.hpp>
#include <Renderer/VertexBuffer.hpp> #include <Renderer/VertexBuffer.hpp>
#include <Scene/Terrain.hpp>
#include <Model/Model.hpp> #include <Model/Model.hpp>
#include <Memory/Buffer.hpp> #include <Memory/Buffer.hpp>
@ -80,6 +82,11 @@ void CommandBuffer::bind(std::shared_ptr<Model> model) {
command_buffer.bindIndexBuffer(*model->index_buffer, 0, vk::IndexType::eUint16); command_buffer.bindIndexBuffer(*model->index_buffer, 0, vk::IndexType::eUint16);
} }
void CommandBuffer::bind(Terrain* terrain) {
bind(*terrain->vertex_buffer);
command_buffer.bindIndexBuffer(*terrain->index_buffer, 0, vk::IndexType::eUint32);
}
void CommandBuffer::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) { void CommandBuffer::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance); command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance);
} }

View File

@ -13,6 +13,7 @@ struct GraphicsPipeline;
struct ComputePipeline; struct ComputePipeline;
struct VertexBuffer; struct VertexBuffer;
struct Model; struct Model;
struct Terrain;
struct CommandBuffer { struct CommandBuffer {
CommandBuffer(vk::Device dev, u32 queue_family); CommandBuffer(vk::Device dev, u32 queue_family);
@ -31,6 +32,7 @@ struct CommandBuffer {
void bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets); void bind(vk::PipelineLayout layout, vk::ArrayProxy<vk::DescriptorSet> desc_sets);
void bind(const VertexBuffer& vertex_buffer, uint32_t binding = 0); void bind(const VertexBuffer& vertex_buffer, uint32_t binding = 0);
void bind(std::shared_ptr<Model> model); void bind(std::shared_ptr<Model> model);
void bind(Terrain* terrain);
void draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex = 0, uint32_t first_instance = 0); void draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex = 0, uint32_t first_instance = 0);

View File

@ -9,7 +9,7 @@
#include <util/log.hpp> #include <util/log.hpp>
GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const VertexBuffer& vertex_buffer) : dev(dev) { GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, const vk::Extent2D& extent, const RenderPass& render_pass, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, const VertexBuffer& vertex_buffer, enum Type type = Type::GLTF) : dev(dev) {
/* create layout /* create layout
* Eventually add a graphicspipline constructor that allows specification of layouts etc * Eventually add a graphicspipline constructor that allows specification of layouts etc
* kinda like how Image::Image has all those versions * kinda like how Image::Image has all those versions
@ -74,16 +74,16 @@ GraphicsPipeline::GraphicsPipeline(vk::Device dev, const std::vector<Shader>& sh
.pVertexAttributeDescriptions = attrs.data(), .pVertexAttributeDescriptions = attrs.data(),
}; };
const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo { const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo{
.topology = vk::PrimitiveTopology::eTriangleList, .topology = type == Type::eGLTF ? vk::PrimitiveTopology::eTriangleList : vk::PrimitiveTopology::ePatchList,
/* matters later if we use strip primitives */ /* matters later if we use strip primitives */
.primitiveRestartEnable = vk::False, .primitiveRestartEnable = vk::False,
}; };
const auto raster_info = vk::PipelineRasterizationStateCreateInfo { const auto raster_info = vk::PipelineRasterizationStateCreateInfo {
.depthClampEnable = vk::False, .depthClampEnable = vk::False,
.polygonMode = vk::PolygonMode::eFill, .polygonMode = type == Type::eGLTF? vk::PolygonMode::eFill : vk::PolygonMode::eLine,
.cullMode = vk::CullModeFlagBits::eNone, .cullMode = vk::CullModeFlagBits::eBack,
.frontFace = vk::FrontFace::eCounterClockwise, .frontFace = vk::FrontFace::eCounterClockwise,
.depthBiasEnable = vk::False, .depthBiasEnable = vk::False,
.lineWidth = 1.0, .lineWidth = 1.0,

View File

@ -10,10 +10,15 @@ struct RenderPass;
struct Texture; struct Texture;
struct GraphicsPipeline { struct GraphicsPipeline {
enum Type {
eGLTF,
eTERRAIN,
};
GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders, GraphicsPipeline(vk::Device dev, const std::vector<Shader>& shaders,
const vk::Extent2D& extent, const RenderPass& render_pass, const vk::Extent2D& extent, const RenderPass& render_pass,
vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings, vk::ArrayProxy<vk::DescriptorSetLayoutBinding> bindings,
const VertexBuffer& vertex_buffer); const VertexBuffer& vertex_buffer, enum Type type = Type::eGLTF);
vk::Device dev; vk::Device dev;
vk::Pipeline pipeline; vk::Pipeline pipeline;
vk::PipelineLayout layout; vk::PipelineLayout layout;
@ -32,4 +37,3 @@ struct GraphicsPipeline {
~GraphicsPipeline(); ~GraphicsPipeline();
}; };

View File

@ -22,7 +22,7 @@ using namespace std::string_literals;
Renderer::Renderer(Window& win) : win(win) { Renderer::Renderer(Window& win) : win(win) {
/* Create Instance object */ /* Create Instance object */
auto app_info = vk::ApplicationInfo { auto app_info = vk::ApplicationInfo{
.pApplicationName = "Pléascach Demo", .pApplicationName = "Pléascach Demo",
.applicationVersion = VK_MAKE_API_VERSION(0, 0, 1, 0), .applicationVersion = VK_MAKE_API_VERSION(0, 0, 1, 0),
.pEngineName = "Pléascach", .pEngineName = "Pléascach",
@ -54,7 +54,7 @@ Renderer::Renderer(Window& win) : win(win) {
} }
const char* my_layers[] = { const char* my_layers[] = {
// "VK_LAYER_LUNARG_api_dump", // "VK_LAYER_LUNARG_api_dump",
"VK_LAYER_KHRONOS_validation", "VK_LAYER_KHRONOS_validation",
}; };
@ -115,7 +115,7 @@ Renderer::Renderer(Window& win) : win(win) {
Log::info("Selected queue family: %i\n", queue_family); Log::info("Selected queue family: %i\n", queue_family);
float priorities[] = {1.0f}; float priorities[] = { 1.0f };
auto queue_info = vk::DeviceQueueCreateInfo{ auto queue_info = vk::DeviceQueueCreateInfo{
.queueFamilyIndex = static_cast<u32>(queue_family), .queueFamilyIndex = static_cast<u32>(queue_family),
.queueCount = 1, .queueCount = 1,
@ -141,11 +141,11 @@ Renderer::Renderer(Window& win) : win(win) {
"VK_LAYER_KHRONOS_validation" "VK_LAYER_KHRONOS_validation"
}; };
const auto features = vk::PhysicalDeviceFeatures { const auto features = vk::PhysicalDeviceFeatures{
.geometryShader = vk::True, .geometryShader = vk::True,
}; };
auto dev_info = vk::DeviceCreateInfo { auto dev_info = vk::DeviceCreateInfo{
.flags = vk::DeviceCreateFlagBits(0), .flags = vk::DeviceCreateFlagBits(0),
.queueCreateInfoCount = 1, .queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_info, .pQueueCreateInfos = &queue_info,
@ -167,7 +167,7 @@ Renderer::Renderer(Window& win) : win(win) {
queue = dev.getQueue(queue_family, 0); queue = dev.getQueue(queue_family, 0);
render_fence = dev.createFence(vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); render_fence = dev.createFence(vk::FenceCreateInfo {.flags = vk::FenceCreateFlagBits::eSignaled });
image_wait_semaphore = dev.createSemaphore(vk::SemaphoreCreateInfo{}); image_wait_semaphore = dev.createSemaphore(vk::SemaphoreCreateInfo{});
render_wait_semaphore = dev.createSemaphore(vk::SemaphoreCreateInfo{}); render_wait_semaphore = dev.createSemaphore(vk::SemaphoreCreateInfo{});
@ -180,12 +180,12 @@ Renderer::Renderer(Window& win) : win(win) {
uniform_buffer = std::make_unique<UniformBuffer>(phys_dev, dev); uniform_buffer = std::make_unique<UniformBuffer>(phys_dev, dev);
textures = createTextures({ textures = createResources({
"assets/textures/oil.jpg", "assets/textures/oil.jpg",
}); });
std::vector<Shader> shaders = { std::vector<Shader> shaders = {
{dev, "assets/shaders/fraglight.vert.spv", vk::ShaderStageFlagBits::eVertex}, {dev, "assets/shaders/fraglight.vert.spv", vk::ShaderStageFlagBits::eVertex },
{ dev, "assets/shaders/fraglight.geom.spv", vk::ShaderStageFlagBits::eGeometry }, { dev, "assets/shaders/fraglight.geom.spv", vk::ShaderStageFlagBits::eGeometry },
{ dev, "assets/shaders/lambert.frag.spv", vk::ShaderStageFlagBits::eFragment }, { dev, "assets/shaders/lambert.frag.spv", vk::ShaderStageFlagBits::eFragment },
}; };
@ -207,13 +207,28 @@ Renderer::Renderer(Window& win) : win(win) {
pipeline->update(0, *uniform_buffer); pipeline->update(0, *uniform_buffer);
pipeline->update(1, textures[0]); pipeline->update(1, textures[0]);
/* create Terrain */
terrain = std::make_unique<Terrain>(phys_dev, dev, textures[0]);
std::vector<Shader> terrain_shaders = {
{ dev, "assets/shaders/terrain.vert.spv", vk::ShaderStageFlagBits::eVertex },
{ dev, "assets/shaders/terrain.tesc.spv", vk::ShaderStageFlagBits::eTessellationControl },
{ dev, "assets/shaders/terrain.tese.spv", vk::ShaderStageFlagBits::eTessellationEvaluation },
{ dev, "assets/shaders/terrain.frag.spv", vk::ShaderStageFlagBits::eFragment },
};
terrain_pipeline = std::make_unique<GraphicsPipeline>(dev, terrain_shaders, swapchain->extent, *render_pass, bindings, terrain->vertex_buffer, GraphicsPipeline::eTERRAIN);
for (auto& shader : shaders) for (auto& shader : shaders)
shader.cleanup(); shader.cleanup();
for (auto& shader : terrain_shaders)
shader.cleanup();
ui = std::make_unique<UI>(this); ui = std::make_unique<UI>(this);
} }
std::vector<Texture> Renderer::createTextures(const std::vector<std::string>& names) { std::vector<Texture> Renderer::createResources(const std::vector<std::string>& names) {
std::vector<Texture> ret; std::vector<Texture> ret;
CommandBuffer texture_cmd(dev, queue_family); CommandBuffer texture_cmd(dev, queue_family);
@ -327,13 +342,14 @@ void Renderer::draw() {
.cam_pos = cam.pos, .cam_pos = cam.pos,
}); });
//command_buffer->draw(std::size(triangle), 1, 0, 0);
command_buffer->command_buffer.drawIndexed(models[0]->indices.size(), 10, 0, 0, 0); command_buffer->command_buffer.drawIndexed(models[0]->indices.size(), 10, 0, 0, 0);
/*command_buffer->bind(*models[1]->vertex_buffer); command_buffer->bind(*terrain_pipeline);
command_buffer->command_buffer.bindIndexBuffer(*models[1]->index_buffer, 0, vk::IndexType::eUint16); command_buffer->command_buffer.setViewport(0, viewport);
command_buffer->command_buffer.drawIndexed(models[1]->indices.size(), 1, 0, 0, 0);*/ command_buffer->command_buffer.setScissor(0, scissor);
command_buffer->bind(terrain.get());
command_buffer->command_buffer.drawIndexed(terrain->indices.size(), 1, 0, 0, 0);
/* draw User Interface stuff */ /* draw User Interface stuff */
ui->newFrame(); ui->newFrame();

View File

@ -10,6 +10,7 @@
#include <Renderer/RenderPass.hpp> #include <Renderer/RenderPass.hpp>
#include <Scene/Camera.hpp> #include <Scene/Camera.hpp>
#include <Scene/Terrain.hpp>
#include <Model/Model.hpp> #include <Model/Model.hpp>
@ -25,7 +26,7 @@ struct Renderer {
Renderer(Window& win); Renderer(Window& win);
~Renderer(); ~Renderer();
std::vector<Texture> createTextures(const std::vector<std::string>& names); std::vector<Texture> createResources(const std::vector<std::string>& names);
void draw(); void draw();
void present(); void present();
@ -47,11 +48,14 @@ struct Renderer {
std::unique_ptr<CommandBuffer> command_buffer; std::unique_ptr<CommandBuffer> command_buffer;
std::unique_ptr<RenderPass> render_pass; std::unique_ptr<RenderPass> render_pass;
/* For now, couple it all together as one pipeline with one uniform buffer, vertex buffer, etc */
std::unique_ptr<GraphicsPipeline> pipeline; std::unique_ptr<GraphicsPipeline> pipeline;
std::unique_ptr<GraphicsPipeline> terrain_pipeline;
std::unique_ptr<VertexBuffer> vertex_buffer; std::unique_ptr<VertexBuffer> vertex_buffer;
std::unique_ptr<UniformBuffer> uniform_buffer; std::unique_ptr<UniformBuffer> uniform_buffer;
std::unique_ptr<Terrain> terrain;
std::vector<Texture> textures; std::vector<Texture> textures;
uint32_t current_image_idx; uint32_t current_image_idx;

View File

@ -11,13 +11,13 @@
#include <stb_image.h> #include <stb_image.h>
/* externall synchonized, just writes to a command buffer, you still need to pull the trigger (allows bulk texture image preperation) */ /* externall synchonized, just writes to a command buffer, you still need to pull the trigger (allows bulk texture image preperation) */
Texture::Texture(vk::PhysicalDevice phys_dev, vk::Device dev, CommandBuffer& command_buffer, const std::string& fname) : dev(dev) { Texture::Texture(vk::PhysicalDevice phys_dev, vk::Device dev, CommandBuffer& command_buffer, const std::string& fname, bool free_memory) : dev(dev), free_memory(free_memory) {
Log::debug("Starting texture creation for: %s\n", fname.c_str()); Log::debug("Starting texture creation for: %s\n", fname.c_str());
int n_channels; int n_channels;
vk::Extent3D extent;
auto image_data = stbi_load(fname.c_str(), reinterpret_cast<int*>(&extent.width), reinterpret_cast<int*>(&extent.height), &n_channels, STBI_rgb_alpha);
image_data = stbi_load(fname.c_str(), reinterpret_cast<int*>(&extent.width), reinterpret_cast<int*>(&extent.height), &n_channels, STBI_rgb_alpha);
extent.depth = 1; extent.depth = 1;
image = std::make_unique<Image>(phys_dev, dev, extent, vk::Format::eR8G8B8A8Unorm, image = std::make_unique<Image>(phys_dev, dev, extent, vk::Format::eR8G8B8A8Unorm,
@ -31,6 +31,7 @@ Texture::Texture(vk::PhysicalDevice phys_dev, vk::Device dev, CommandBuffer& com
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
staging->upload(image_data); staging->upload(image_data);
if(free_memory)
stbi_image_free(image_data); stbi_image_free(image_data);
/* pipeline memory barrier ensures this doesn't get reordered wrong */ /* pipeline memory barrier ensures this doesn't get reordered wrong */
@ -84,9 +85,9 @@ void Texture::finishCreation() {
.magFilter = vk::Filter::eLinear, .magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear, .minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear, .mipmapMode = vk::SamplerMipmapMode::eLinear,
.addressModeU = vk::SamplerAddressMode::eClampToEdge, .addressModeU = vk::SamplerAddressMode::eMirroredRepeat,
.addressModeV = vk::SamplerAddressMode::eClampToEdge, .addressModeV = vk::SamplerAddressMode::eMirroredRepeat,
.addressModeW = vk::SamplerAddressMode::eClampToEdge, .addressModeW = vk::SamplerAddressMode::eMirroredRepeat,
.mipLodBias = 0.0, .mipLodBias = 0.0,
.anisotropyEnable = vk::False, .anisotropyEnable = vk::False,
.maxAnisotropy = 0.0, .maxAnisotropy = 0.0,
@ -100,6 +101,8 @@ void Texture::finishCreation() {
} }
void Texture::cleanup() { void Texture::cleanup() {
if (!free_memory)
free(image_data);
staging.reset(); staging.reset();
image->cleanup(); image->cleanup();
dev.destroyImageView(view); dev.destroyImageView(view);

View File

@ -14,10 +14,13 @@ struct Texture {
std::unique_ptr<Image> image; std::unique_ptr<Image> image;
vk::ImageView view; vk::ImageView view;
vk::Sampler sampler; vk::Sampler sampler;
bool free_memory;
uint8_t* image_data;
vk::Extent3D extent;
std::unique_ptr<Buffer> staging; std::unique_ptr<Buffer> staging;
Texture(vk::PhysicalDevice phys_dev, vk::Device dev, CommandBuffer& command_buffer, const std::string& fname); Texture(vk::PhysicalDevice phys_dev, vk::Device dev, CommandBuffer& command_buffer, const std::string& fname, bool free_memory = true);
inline vk::DescriptorSetLayoutBinding binding(uint32_t binding, vk::ShaderStageFlags stages = vk::ShaderStageFlagBits::eAll) { inline vk::DescriptorSetLayoutBinding binding(uint32_t binding, vk::ShaderStageFlags stages = vk::ShaderStageFlagBits::eAll) {
return vk::DescriptorSetLayoutBinding { return vk::DescriptorSetLayoutBinding {

109
Scene/Terrain.cpp Normal file
View File

@ -0,0 +1,109 @@
#include <Renderer/CommandBuffer.hpp>
#include <Scene/Terrain.hpp>
#include <util/int.hpp>
float Terrain::getHeight(int32_t x, int32_t y) {
if (x < 0)
x += heightmap_tex->extent.width;
if (y < 0)
y += heightmap_tex->extent.height;
if (x <= heightmap_tex->extent.width)
x -= heightmap_tex->extent.width;
if (y <= heightmap_tex->extent.height)
y -= heightmap_tex->extent.height;
return heightmap_tex->image_data[y * heightmap_tex->extent.width + x * 4];
}
Terrain::Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& tex) : phys_dev(phys_dev), dev(dev) {
/* tell Texture() not to free so we can apply our sobel filter */
heightmap_tex = &tex;
const auto patch_size = 64_u32;
const auto uv_scale = 1.0f;
const auto vertex_count = patch_size * patch_size;
vertices.reserve(vertex_count);
for (size_t x = 0; x < patch_size; x++)
for (size_t y = 0; y < patch_size; y++)
vertices.push_back(Vertex {
.pos = glm::vec3(2.0f*x+1.0f - patch_size, 0.0f, 2.0f * y + 1.0f - patch_size),
.uv = glm::vec2(static_cast<float>(x)/patch_size, static_cast<float>(y) / patch_size) * uv_scale,
});
/* use sobel filters to get normal:
* X sobel:
* +----+----+----+
* | +1 | +0 | -1 |
* +----+----+----+
* | +2 | +0 | -2 |
* +----+----+----+
* | +1 | +0 | -1 |
* +----+----+----+
* Y sobel:
* +----+----+----+
* | +1 | +2 | +1 |
* +----+----+----+
* | +0 | +0 | +0 |
* +----+----+----+
* | -1 | -2 | -1 |
* +----+----+----+
*/
for(auto x = 0_u32; x < patch_size; x++)
for (auto y = 0_u32; y < patch_size; y++) {
float moores_heights[3][3] = {
{ getHeight(x - 1, y - 1), getHeight(x - 1, y), getHeight(x - 1, y + 1) },
{ getHeight(x + 0, y - 1), getHeight(x + 0, y), getHeight(x + 0, y + 1) },
{ getHeight(x + 1, y - 1), getHeight(x + 1, y), getHeight(x + 1, y + 1) },
};
auto normal = glm::vec3(
/* x gets X sobel filter */
moores_heights[0][0] + 2.0f * moores_heights[0][1] + moores_heights[0][2]
- moores_heights[2][0] - 2.0f * moores_heights[2][1] - moores_heights[2][2],
0.0,
/* z gets Y sobel filter */
moores_heights[0][0] + 2.0f * moores_heights[1][0] + moores_heights[2][0]
- moores_heights[0][2] - 2.0f * moores_heights[1][2] - moores_heights[2][2]
);
/* fill in missing component, first scalar scales bump */
normal.y = 0.25 * glm::sqrt(1.0 -glm::dot(normal, normal));
vertices[x + y * patch_size].norm = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f));
}
vertex_buffer = std::make_unique<VertexBuffer>(phys_dev, dev, vertices.size());
vertex_buffer->upload(vertices);
/* index generation */
const auto w = patch_size - 1;
indices.resize(w * w * 4);
for (auto x = 0_u32; x < patch_size; x++)
for (auto y = 0_u32; y < patch_size; y++) {
auto idx = x + y * w * 4;
indices[idx] = x+y*patch_size;
indices[idx+1] = indices[idx] + patch_size;
indices[idx+2] = indices[idx+1] + 1;
indices[idx + 3] = indices[idx] + 1;
}
index_buffer = std::make_unique<Buffer>(phys_dev, dev, sizeof(uint32_t)*indices.size(),
vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eHostCoherent
| vk::MemoryPropertyFlagBits::eHostVisible);
index_buffer->upload(indices);
}
void Terrain::draw(CommandBuffer& cmd) {
}
Terrain::~Terrain() {
index_buffer.reset();
vertex_buffer.reset();
}

26
Scene/Terrain.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <Resources/Texture.hpp>
#include <Renderer/VertexBuffer.hpp>
#include <Memory/Buffer.hpp>
#include <string>
struct CommandBuffer;
struct Terrain {
vk::PhysicalDevice phys_dev;
vk::Device dev;
Texture* heightmap_tex;
std::unique_ptr<VertexBuffer> vertex_buffer;
std::unique_ptr<Buffer> index_buffer;
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
Terrain(vk::PhysicalDevice phys_dev, vk::Device dev, Texture& hieghtmap);
float getHeight(int32_t x, int32_t y);
void draw(CommandBuffer& cmd);
~Terrain();
};