First part of terrain generation (hopefully) done, next part is to make the tesselation shaders
This commit is contained in:
parent
6870cb61f9
commit
a778a406d0
@ -18,8 +18,9 @@ struct Buffer {
|
||||
inline void upload(const uint8_t* data) {
|
||||
upload(data, size);
|
||||
}
|
||||
inline void upload(const std::vector<uint8_t>& data) {
|
||||
upload(data.data(), size);
|
||||
template<typename T>
|
||||
inline void upload(const std::vector<T>& data) {
|
||||
upload(reinterpret_cast<uint8_t*>(data.data()), data.size()*sizeof(T));
|
||||
}
|
||||
|
||||
operator vk::Buffer& () {
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#include <Renderer/Pipeline.hpp>
|
||||
#include <Renderer/VertexBuffer.hpp>
|
||||
|
||||
#include <Scene/Terrain.hpp>
|
||||
|
||||
#include <Model/Model.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);
|
||||
}
|
||||
|
||||
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) {
|
||||
command_buffer.draw(vertex_count, instance_count, first_vertex, first_instance);
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ struct GraphicsPipeline;
|
||||
struct ComputePipeline;
|
||||
struct VertexBuffer;
|
||||
struct Model;
|
||||
struct Terrain;
|
||||
|
||||
struct CommandBuffer {
|
||||
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(const VertexBuffer& vertex_buffer, uint32_t binding = 0);
|
||||
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);
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
#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
|
||||
* Eventually add a graphicspipline constructor that allows specification of layouts etc
|
||||
* 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(),
|
||||
};
|
||||
|
||||
const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo {
|
||||
.topology = vk::PrimitiveTopology::eTriangleList,
|
||||
const auto input_asm_info = vk::PipelineInputAssemblyStateCreateInfo{
|
||||
.topology = type == Type::eGLTF ? vk::PrimitiveTopology::eTriangleList : vk::PrimitiveTopology::ePatchList,
|
||||
/* matters later if we use strip primitives */
|
||||
.primitiveRestartEnable = vk::False,
|
||||
};
|
||||
|
||||
const auto raster_info = vk::PipelineRasterizationStateCreateInfo {
|
||||
.depthClampEnable = vk::False,
|
||||
.polygonMode = vk::PolygonMode::eFill,
|
||||
.cullMode = vk::CullModeFlagBits::eNone,
|
||||
.polygonMode = type == Type::eGLTF? vk::PolygonMode::eFill : vk::PolygonMode::eLine,
|
||||
.cullMode = vk::CullModeFlagBits::eBack,
|
||||
.frontFace = vk::FrontFace::eCounterClockwise,
|
||||
.depthBiasEnable = vk::False,
|
||||
.lineWidth = 1.0,
|
||||
|
||||
@ -10,10 +10,15 @@ struct RenderPass;
|
||||
struct Texture;
|
||||
|
||||
struct GraphicsPipeline {
|
||||
enum Type {
|
||||
eGLTF,
|
||||
eTERRAIN,
|
||||
};
|
||||
|
||||
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);
|
||||
const VertexBuffer& vertex_buffer, enum Type type = Type::eGLTF);
|
||||
vk::Device dev;
|
||||
vk::Pipeline pipeline;
|
||||
vk::PipelineLayout layout;
|
||||
@ -32,4 +37,3 @@ struct GraphicsPipeline {
|
||||
~GraphicsPipeline();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ using namespace std::string_literals;
|
||||
|
||||
Renderer::Renderer(Window& win) : win(win) {
|
||||
/* Create Instance object */
|
||||
auto app_info = vk::ApplicationInfo {
|
||||
auto app_info = vk::ApplicationInfo{
|
||||
.pApplicationName = "Pléascach Demo",
|
||||
.applicationVersion = VK_MAKE_API_VERSION(0, 0, 1, 0),
|
||||
.pEngineName = "Pléascach",
|
||||
@ -54,7 +54,7 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
}
|
||||
|
||||
const char* my_layers[] = {
|
||||
// "VK_LAYER_LUNARG_api_dump",
|
||||
// "VK_LAYER_LUNARG_api_dump",
|
||||
"VK_LAYER_KHRONOS_validation",
|
||||
};
|
||||
|
||||
@ -115,7 +115,7 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
|
||||
Log::info("Selected queue family: %i\n", queue_family);
|
||||
|
||||
float priorities[] = {1.0f};
|
||||
float priorities[] = { 1.0f };
|
||||
auto queue_info = vk::DeviceQueueCreateInfo{
|
||||
.queueFamilyIndex = static_cast<u32>(queue_family),
|
||||
.queueCount = 1,
|
||||
@ -141,11 +141,11 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
"VK_LAYER_KHRONOS_validation"
|
||||
};
|
||||
|
||||
const auto features = vk::PhysicalDeviceFeatures {
|
||||
const auto features = vk::PhysicalDeviceFeatures{
|
||||
.geometryShader = vk::True,
|
||||
};
|
||||
|
||||
auto dev_info = vk::DeviceCreateInfo {
|
||||
auto dev_info = vk::DeviceCreateInfo{
|
||||
.flags = vk::DeviceCreateFlagBits(0),
|
||||
.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &queue_info,
|
||||
@ -167,7 +167,7 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
|
||||
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{});
|
||||
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);
|
||||
|
||||
textures = createTextures({
|
||||
textures = createResources({
|
||||
"assets/textures/oil.jpg",
|
||||
});
|
||||
|
||||
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/lambert.frag.spv", vk::ShaderStageFlagBits::eFragment },
|
||||
};
|
||||
@ -207,13 +207,28 @@ Renderer::Renderer(Window& win) : win(win) {
|
||||
pipeline->update(0, *uniform_buffer);
|
||||
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)
|
||||
shader.cleanup();
|
||||
for (auto& shader : terrain_shaders)
|
||||
shader.cleanup();
|
||||
|
||||
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;
|
||||
|
||||
CommandBuffer texture_cmd(dev, queue_family);
|
||||
@ -327,13 +342,14 @@ void Renderer::draw() {
|
||||
.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->bind(*models[1]->vertex_buffer);
|
||||
command_buffer->command_buffer.bindIndexBuffer(*models[1]->index_buffer, 0, vk::IndexType::eUint16);
|
||||
command_buffer->command_buffer.drawIndexed(models[1]->indices.size(), 1, 0, 0, 0);*/
|
||||
command_buffer->bind(*terrain_pipeline);
|
||||
command_buffer->command_buffer.setViewport(0, viewport);
|
||||
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 */
|
||||
ui->newFrame();
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <Renderer/RenderPass.hpp>
|
||||
|
||||
#include <Scene/Camera.hpp>
|
||||
#include <Scene/Terrain.hpp>
|
||||
|
||||
#include <Model/Model.hpp>
|
||||
|
||||
@ -25,7 +26,7 @@ struct Renderer {
|
||||
Renderer(Window& win);
|
||||
~Renderer();
|
||||
|
||||
std::vector<Texture> createTextures(const std::vector<std::string>& names);
|
||||
std::vector<Texture> createResources(const std::vector<std::string>& names);
|
||||
|
||||
void draw();
|
||||
void present();
|
||||
@ -47,11 +48,14 @@ struct Renderer {
|
||||
std::unique_ptr<CommandBuffer> command_buffer;
|
||||
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> terrain_pipeline;
|
||||
std::unique_ptr<VertexBuffer> vertex_buffer;
|
||||
std::unique_ptr<UniformBuffer> uniform_buffer;
|
||||
|
||||
std::unique_ptr<Terrain> terrain;
|
||||
|
||||
std::vector<Texture> textures;
|
||||
|
||||
uint32_t current_image_idx;
|
||||
|
||||
@ -11,13 +11,13 @@
|
||||
#include <stb_image.h>
|
||||
|
||||
/* 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());
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
staging->upload(image_data);
|
||||
if(free_memory)
|
||||
stbi_image_free(image_data);
|
||||
|
||||
/* pipeline memory barrier ensures this doesn't get reordered wrong */
|
||||
@ -84,9 +85,9 @@ void Texture::finishCreation() {
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.mipmapMode = vk::SamplerMipmapMode::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeW = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeU = vk::SamplerAddressMode::eMirroredRepeat,
|
||||
.addressModeV = vk::SamplerAddressMode::eMirroredRepeat,
|
||||
.addressModeW = vk::SamplerAddressMode::eMirroredRepeat,
|
||||
.mipLodBias = 0.0,
|
||||
.anisotropyEnable = vk::False,
|
||||
.maxAnisotropy = 0.0,
|
||||
@ -100,6 +101,8 @@ void Texture::finishCreation() {
|
||||
}
|
||||
|
||||
void Texture::cleanup() {
|
||||
if (!free_memory)
|
||||
free(image_data);
|
||||
staging.reset();
|
||||
image->cleanup();
|
||||
dev.destroyImageView(view);
|
||||
|
||||
@ -14,10 +14,13 @@ struct Texture {
|
||||
std::unique_ptr<Image> image;
|
||||
vk::ImageView view;
|
||||
vk::Sampler sampler;
|
||||
bool free_memory;
|
||||
uint8_t* image_data;
|
||||
vk::Extent3D extent;
|
||||
|
||||
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) {
|
||||
return vk::DescriptorSetLayoutBinding {
|
||||
|
||||
109
Scene/Terrain.cpp
Normal file
109
Scene/Terrain.cpp
Normal 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
26
Scene/Terrain.hpp
Normal 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();
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user