#include <vkcv/Core.hpp> #include <vkcv/camera/CameraManager.hpp> #include <vkcv/gui/GUI.hpp> #include <vkcv/shader/GLSLCompiler.hpp> #include <random> struct Particle { glm::vec3 position; float size; glm::vec3 velocity; float mass; glm::vec3 pad; float weight_sum; glm::mat4 deformation; glm::mat4 mls; }; struct Physics { float lame1; float lame2; float t; float dt; float speedfactor; }; float sphere_volume(float radius) { return 4.0f * (radius * radius * radius) * M_PI / 3.0f; } float sphere_radius(float volume) { return pow(volume * 3.0f / 4.0f / M_PI, 1.0f / 3.0f); } std::random_device random_dev; std::uniform_int_distribution<int> dist(0, RAND_MAX); float randomFloat(float min, float max) { return min + (max - min) * dist(random_dev) / static_cast<float>(RAND_MAX); } void distributeParticles(Particle *particles, size_t count, const glm::vec3& center, float radius, float mass, const glm::vec3& velocity) { float volume = 0.0f; for (size_t i = 0; i < count; i++) { glm::vec3 offset ( randomFloat(-1.0f, +1.0f), randomFloat(-1.0f, +1.0f), randomFloat(-1.0f, +1.0f) ); if (glm::length(offset) > 0.0f) offset = glm::normalize(offset); offset *= randomFloat(0.0f, radius); const float size = (radius - glm::length(offset)); particles[i].position = center + offset; particles[i].size = size; particles[i].velocity = velocity; volume += sphere_volume(size); } // Keep the same densitiy as planned! mass *= (volume / sphere_volume(radius)); for (size_t i = 0; i < count; i++) { particles[i].mass = (mass * sphere_volume(particles[i].size) / volume); particles[i].deformation = glm::mat4(1.0f); particles[i].pad = glm::vec3(0.0f); particles[i].weight_sum = 1.0f; particles[i].mls = glm::mat4(0.0f); } } vkcv::ComputePipelineHandle createComputePipeline(vkcv::Core& core, vkcv::shader::GLSLCompiler& compiler, const std::string& path, std::vector<vkcv::DescriptorSetHandle>& descriptorSets) { vkcv::ShaderProgram shaderProgram; compiler.compile( vkcv::ShaderStage::COMPUTE, path, [&shaderProgram](vkcv::ShaderStage stage, const std::filesystem::path& path) { shaderProgram.addShader(stage, path); } ); const auto& descriptors = shaderProgram.getReflectedDescriptors(); size_t count = 0; for (const auto& descriptor : descriptors) { if (descriptor.first >= count) { count = (descriptor.first + 1); } } std::vector<vkcv::DescriptorSetLayoutHandle> descriptorSetLayouts; descriptorSetLayouts.resize(count); descriptorSets.resize(count); for (const auto& descriptor : descriptors) { descriptorSetLayouts[descriptor.first] = core.createDescriptorSetLayout(descriptor.second); descriptorSets[descriptor.first] = core.createDescriptorSet(descriptorSetLayouts[descriptor.first]); } vkcv::ComputePipelineConfig config { shaderProgram, descriptorSetLayouts }; return core.createComputePipeline(config); } void resetParticles(vkcv::Buffer<Particle>& particles, const glm::vec3& velocity, float density, float size) { std::vector<Particle> particles_vec (particles.getCount()); distributeParticles( particles_vec.data(), particles_vec.size(), glm::vec3(0.5f), size, density * sphere_volume(size), velocity ); particles.fill(particles_vec); } int main(int argc, const char **argv) { const char* applicationName = "Wobble Bobble"; uint32_t windowWidth = 800; uint32_t windowHeight = 600; vkcv::Features features; features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); vkcv::Core core = vkcv::Core::create( applicationName, VK_MAKE_VERSION(0, 0, 1), {vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute}, features ); vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true); vkcv::Window& window = core.getWindow(windowHandle); vkcv::camera::CameraManager cameraManager(window); vkcv::gui::GUI gui (core, windowHandle); uint32_t trackballIdx = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL); cameraManager.getCamera(trackballIdx).setCenter(glm::vec3(0.5f, 0.5f, 0.5f)); // set camera to look at the center of the particle volume cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); auto swapchainExtent = core.getSwapchain(windowHandle).getExtent(); vkcv::ImageHandle depthBuffer = core.createImage( vk::Format::eD32Sfloat, swapchainExtent.width, swapchainExtent.height ).getHandle(); glm::vec3 initialVelocity (0.0f, 0.1f, 0.0f); float density = 2500.0f; float radius = 0.1f; vkcv::Buffer<Particle> particles = core.createBuffer<Particle>( vkcv::BufferType::STORAGE, 256 ); resetParticles(particles, initialVelocity, density, radius); vkcv::Image grid = core.createImage( vk::Format::eR32G32B32A32Sfloat, 64, 64, 64, false, true ); std::vector<glm::vec4> grid_vec (grid.getWidth() * grid.getHeight() * grid.getDepth()); for (size_t i = 0; i < grid_vec.size(); i++) { grid_vec[i] = glm::vec4(0.0f); } grid.fill(grid_vec.data()); vkcv::SamplerHandle gridSampler = core.createSampler( vkcv::SamplerFilterType::LINEAR, vkcv::SamplerFilterType::LINEAR, vkcv::SamplerMipmapMode::NEAREST, vkcv::SamplerAddressMode::CLAMP_TO_BORDER, 0.0f, vkcv::SamplerBorderColor::FLOAT_ZERO_TRANSPARENT ); vkcv::shader::GLSLCompiler compiler; std::vector<vkcv::DescriptorSetHandle> initParticleWeightsSets; vkcv::ComputePipelineHandle initParticleWeightsPipeline = createComputePipeline( core, compiler, "shaders/init_particle_weights.comp", initParticleWeightsSets ); { vkcv::DescriptorWrites writes; writes.storageBufferWrites.push_back(vkcv::BufferDescriptorWrite(0, particles.getHandle())); writes.sampledImageWrites.push_back(vkcv::SampledImageDescriptorWrite(1, grid.getHandle())); writes.samplerWrites.push_back(vkcv::SamplerDescriptorWrite(2, gridSampler)); core.writeDescriptorSet(initParticleWeightsSets[0], writes); } std::vector<vkcv::DescriptorSetHandle> transformParticlesToGridSets; vkcv::ComputePipelineHandle transformParticlesToGridPipeline = createComputePipeline( core, compiler, "shaders/transform_particles_to_grid.comp", transformParticlesToGridSets ); { vkcv::DescriptorWrites writes; writes.storageBufferWrites.push_back(vkcv::BufferDescriptorWrite(0, particles.getHandle())); writes.storageImageWrites.push_back(vkcv::StorageImageDescriptorWrite(1, grid.getHandle())); core.writeDescriptorSet(transformParticlesToGridSets[0], writes); } std::vector<vkcv::DescriptorSetHandle> updateParticleVelocitiesSets; vkcv::ComputePipelineHandle updateParticleVelocitiesPipeline = createComputePipeline( core, compiler, "shaders/update_particle_velocities.comp", updateParticleVelocitiesSets ); { vkcv::DescriptorWrites writes; writes.storageBufferWrites.push_back(vkcv::BufferDescriptorWrite(0, particles.getHandle())); writes.sampledImageWrites.push_back(vkcv::SampledImageDescriptorWrite(1, grid.getHandle())); writes.samplerWrites.push_back(vkcv::SamplerDescriptorWrite(2, gridSampler)); core.writeDescriptorSet(updateParticleVelocitiesSets[0], writes); } vkcv::ShaderProgram gfxProgramGrid; compiler.compile( vkcv::ShaderStage::VERTEX, "shaders/grid.vert", [&gfxProgramGrid](vkcv::ShaderStage stage, const std::filesystem::path& path) { gfxProgramGrid.addShader(stage, path); } ); compiler.compile( vkcv::ShaderStage::FRAGMENT, "shaders/grid.frag", [&gfxProgramGrid](vkcv::ShaderStage stage, const std::filesystem::path& path) { gfxProgramGrid.addShader(stage, path); } ); vkcv::ShaderProgram gfxProgramParticles; compiler.compile( vkcv::ShaderStage::VERTEX, "shaders/particle.vert", [&gfxProgramParticles](vkcv::ShaderStage stage, const std::filesystem::path& path) { gfxProgramParticles.addShader(stage, path); } ); compiler.compile( vkcv::ShaderStage::FRAGMENT, "shaders/particle.frag", [&gfxProgramParticles](vkcv::ShaderStage stage, const std::filesystem::path& path) { gfxProgramParticles.addShader(stage, path); } ); vkcv::ShaderProgram gfxProgramLines; compiler.compile( vkcv::ShaderStage::VERTEX, "shaders/lines.vert", [&gfxProgramLines](vkcv::ShaderStage stage, const std::filesystem::path& path) { gfxProgramLines.addShader(stage, path); } ); compiler.compile( vkcv::ShaderStage::FRAGMENT, "shaders/lines.frag", [&gfxProgramLines](vkcv::ShaderStage stage, const std::filesystem::path& path) { gfxProgramLines.addShader(stage, path); } ); vkcv::PassConfig passConfigGrid ({ vkcv::AttachmentDescription( vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, core.getSwapchain(windowHandle).getFormat() ), vkcv::AttachmentDescription( vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, vk::Format::eD32Sfloat ) }); vkcv::PassConfig passConfigParticles ({ vkcv::AttachmentDescription( vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, core.getSwapchain(windowHandle).getFormat() ), vkcv::AttachmentDescription( vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, vk::Format::eD32Sfloat ) }); vkcv::PassConfig passConfigLines ({ vkcv::AttachmentDescription( vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::LOAD, core.getSwapchain(windowHandle).getFormat() ), vkcv::AttachmentDescription( vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::LOAD, vk::Format::eD32Sfloat ) }); vkcv::DescriptorSetLayoutHandle gfxSetLayoutGrid = core.createDescriptorSetLayout( gfxProgramGrid.getReflectedDescriptors().at(0) ); vkcv::DescriptorSetHandle gfxSetGrid = core.createDescriptorSet(gfxSetLayoutGrid); { vkcv::DescriptorWrites writes; writes.sampledImageWrites.push_back(vkcv::SampledImageDescriptorWrite(0, grid.getHandle())); writes.samplerWrites.push_back(vkcv::SamplerDescriptorWrite(1, gridSampler)); core.writeDescriptorSet(gfxSetGrid, writes); } vkcv::DescriptorSetLayoutHandle gfxSetLayoutParticles = core.createDescriptorSetLayout( gfxProgramParticles.getReflectedDescriptors().at(0) ); vkcv::DescriptorSetHandle gfxSetParticles = core.createDescriptorSet(gfxSetLayoutParticles); { vkcv::DescriptorWrites writes; writes.storageBufferWrites.push_back(vkcv::BufferDescriptorWrite(0, particles.getHandle())); core.writeDescriptorSet(gfxSetParticles, writes); } vkcv::PassHandle gfxPassGrid = core.createPass(passConfigGrid); vkcv::PassHandle gfxPassParticles = core.createPass(passConfigParticles); vkcv::PassHandle gfxPassLines = core.createPass(passConfigLines); vkcv::VertexLayout vertexLayoutGrid ({ vkcv::VertexBinding(0, gfxProgramGrid.getVertexAttachments()) }); vkcv::GraphicsPipelineConfig gfxPipelineConfigGrid; gfxPipelineConfigGrid.m_ShaderProgram = gfxProgramGrid; gfxPipelineConfigGrid.m_Width = windowWidth; gfxPipelineConfigGrid.m_Height = windowHeight; gfxPipelineConfigGrid.m_PassHandle = gfxPassGrid; gfxPipelineConfigGrid.m_VertexLayout = vertexLayoutGrid; gfxPipelineConfigGrid.m_DescriptorLayouts = { gfxSetLayoutGrid }; gfxPipelineConfigGrid.m_UseDynamicViewport = true; gfxPipelineConfigGrid.m_blendMode = vkcv::BlendMode::Additive; vkcv::VertexLayout vertexLayoutParticles ({ vkcv::VertexBinding(0, gfxProgramParticles.getVertexAttachments()) }); vkcv::GraphicsPipelineConfig gfxPipelineConfigParticles; gfxPipelineConfigParticles.m_ShaderProgram = gfxProgramParticles; gfxPipelineConfigParticles.m_Width = windowWidth; gfxPipelineConfigParticles.m_Height = windowHeight; gfxPipelineConfigParticles.m_PassHandle = gfxPassParticles; gfxPipelineConfigParticles.m_VertexLayout = vertexLayoutParticles; gfxPipelineConfigParticles.m_DescriptorLayouts = { gfxSetLayoutParticles }; gfxPipelineConfigParticles.m_UseDynamicViewport = true; vkcv::VertexLayout vertexLayoutLines ({ vkcv::VertexBinding(0, gfxProgramLines.getVertexAttachments()) }); vkcv::GraphicsPipelineConfig gfxPipelineConfigLines; gfxPipelineConfigLines.m_ShaderProgram = gfxProgramLines; gfxPipelineConfigLines.m_Width = windowWidth; gfxPipelineConfigLines.m_Height = windowHeight; gfxPipelineConfigLines.m_PassHandle = gfxPassLines; gfxPipelineConfigLines.m_VertexLayout = vertexLayoutLines; gfxPipelineConfigLines.m_DescriptorLayouts = {}; gfxPipelineConfigLines.m_UseDynamicViewport = true; gfxPipelineConfigLines.m_PrimitiveTopology = vkcv::PrimitiveTopology::LineList; vkcv::GraphicsPipelineHandle gfxPipelineGrid = core.createGraphicsPipeline(gfxPipelineConfigGrid); vkcv::GraphicsPipelineHandle gfxPipelineParticles = core.createGraphicsPipeline(gfxPipelineConfigParticles); vkcv::GraphicsPipelineHandle gfxPipelineLines = core.createGraphicsPipeline(gfxPipelineConfigLines); vkcv::Buffer<glm::vec2> trianglePositions = core.createBuffer<glm::vec2>(vkcv::BufferType::VERTEX, 3); trianglePositions.fill({ glm::vec2(-1.0f, -1.0f), glm::vec2(+0.0f, +1.5f), glm::vec2(+1.0f, -1.0f) }); vkcv::Buffer<uint16_t> triangleIndices = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3); triangleIndices.fill({ 0, 1, 2 }); vkcv::Mesh triangleMesh ( { vkcv::VertexBufferBinding(0, trianglePositions.getVulkanHandle()) }, triangleIndices.getVulkanHandle(), triangleIndices.getCount() ); vkcv::Buffer<glm::vec3> linesPositions = core.createBuffer<glm::vec3>(vkcv::BufferType::VERTEX, 8); linesPositions.fill({ glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(1.0f, 0.0f, +1.0f), glm::vec3(0.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f) }); vkcv::Buffer<uint16_t> linesIndices = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 24); linesIndices.fill({ 0, 1, 1, 3, 3, 2, 2, 0, 4, 5, 5, 7, 7, 6, 6, 4, 0, 4, 1, 5, 2, 6, 3, 7 }); vkcv::Mesh linesMesh ( { vkcv::VertexBufferBinding(0, linesPositions.getVulkanHandle()) }, linesIndices.getVulkanHandle(), linesIndices.getCount() ); std::vector<vkcv::DrawcallInfo> drawcallsGrid; drawcallsGrid.push_back(vkcv::DrawcallInfo( triangleMesh, { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(gfxSetGrid).vulkanHandle) }, grid.getWidth() * grid.getHeight() * grid.getDepth() )); std::vector<vkcv::DrawcallInfo> drawcallsParticles; drawcallsParticles.push_back(vkcv::DrawcallInfo( triangleMesh, { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(gfxSetParticles).vulkanHandle) }, particles.getCount() )); std::vector<vkcv::DrawcallInfo> drawcallsLines; drawcallsLines.push_back(vkcv::DrawcallInfo( linesMesh, {}, 1 )); bool renderGrid = true; float lame1 = 10.0f; float lame2 = 20.0f; float speed_factor = 1.0f; auto start = std::chrono::system_clock::now(); auto current = start; while (vkcv::Window::hasOpenWindow()) { vkcv::Window::pollEvents(); if(window.getHeight() == 0 || window.getWidth() == 0) continue; uint32_t swapchainWidth, swapchainHeight; if (!core.beginFrame(swapchainWidth, swapchainHeight,windowHandle)) { continue; } if ((swapchainWidth != swapchainExtent.width) || ((swapchainHeight != swapchainExtent.height))) { depthBuffer = core.createImage( vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight ).getHandle(); swapchainExtent.width = swapchainWidth; swapchainExtent.height = swapchainHeight; } auto next = std::chrono::system_clock::now(); auto time = std::chrono::duration_cast<std::chrono::microseconds>(next - start); auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(next - current); current = next; Physics physics; physics.lame1 = lame1; physics.lame2 = lame2; physics.t = static_cast<float>(0.000001 * static_cast<double>(time.count())); physics.dt = static_cast<float>(0.000001 * static_cast<double>(deltatime.count())); physics.speedfactor = speed_factor; vkcv::PushConstants physicsPushConstants (sizeof(physics)); physicsPushConstants.appendDrawcall(physics); cameraManager.update(physics.dt); glm::mat4 mvp = cameraManager.getActiveCamera().getMVP(); vkcv::PushConstants cameraPushConstants (sizeof(glm::mat4)); cameraPushConstants.appendDrawcall(mvp); auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); const uint32_t dispatchSizeGrid [3] = { 16, 16, 16 }; const uint32_t dispatchSizeParticles [3] = { static_cast<uint32_t>(particles.getCount() + 63) / 64, 1, 1 }; core.recordBeginDebugLabel(cmdStream, "INIT PARTICLE WEIGHTS", { 0.78f, 0.89f, 0.94f, 1.0f }); core.recordBufferMemoryBarrier(cmdStream, particles.getHandle()); core.prepareImageForSampling(cmdStream, grid.getHandle()); core.recordComputeDispatchToCmdStream( cmdStream, initParticleWeightsPipeline, dispatchSizeParticles, { vkcv::DescriptorSetUsage( 0, core.getDescriptorSet(initParticleWeightsSets[0]).vulkanHandle ) }, vkcv::PushConstants(0) ); core.recordBufferMemoryBarrier(cmdStream, particles.getHandle()); core.recordEndDebugLabel(cmdStream); core.recordBeginDebugLabel(cmdStream, "TRANSFORM PARTICLES TO GRID", { 0.47f, 0.77f, 0.85f, 1.0f }); core.recordBufferMemoryBarrier(cmdStream, particles.getHandle()); core.prepareImageForStorage(cmdStream, grid.getHandle()); core.recordComputeDispatchToCmdStream( cmdStream, transformParticlesToGridPipeline, dispatchSizeGrid, { vkcv::DescriptorSetUsage( 0, core.getDescriptorSet(transformParticlesToGridSets[0]).vulkanHandle ) }, physicsPushConstants ); core.recordImageMemoryBarrier(cmdStream, grid.getHandle()); core.recordEndDebugLabel(cmdStream); core.recordBeginDebugLabel(cmdStream, "UPDATE PARTICLE VELOCITIES", { 0.78f, 0.89f, 0.94f, 1.0f }); core.recordBufferMemoryBarrier(cmdStream, particles.getHandle()); core.prepareImageForSampling(cmdStream, grid.getHandle()); core.recordComputeDispatchToCmdStream( cmdStream, updateParticleVelocitiesPipeline, dispatchSizeParticles, { vkcv::DescriptorSetUsage( 0, core.getDescriptorSet(updateParticleVelocitiesSets[0]).vulkanHandle ) }, physicsPushConstants ); core.recordBufferMemoryBarrier(cmdStream, particles.getHandle()); core.recordEndDebugLabel(cmdStream); std::vector<vkcv::ImageHandle> renderTargets { vkcv::ImageHandle::createSwapchainImageHandle(), depthBuffer }; if (renderGrid) { core.recordBeginDebugLabel(cmdStream, "RENDER GRID", { 0.13f, 0.20f, 0.22f, 1.0f }); core.prepareImageForSampling(cmdStream, grid.getHandle()); core.recordDrawcallsToCmdStream( cmdStream, gfxPassGrid, gfxPipelineGrid, cameraPushConstants, drawcallsGrid, renderTargets, windowHandle ); core.recordEndDebugLabel(cmdStream); } else { core.recordBeginDebugLabel(cmdStream, "RENDER PARTICLES", { 0.13f, 0.20f, 0.22f, 1.0f }); core.recordBufferMemoryBarrier(cmdStream, particles.getHandle()); core.recordDrawcallsToCmdStream( cmdStream, gfxPassParticles, gfxPipelineParticles, cameraPushConstants, drawcallsParticles, renderTargets, windowHandle ); core.recordEndDebugLabel(cmdStream); } core.recordBeginDebugLabel(cmdStream, "RENDER LINES", { 0.13f, 0.20f, 0.22f, 1.0f }); core.recordDrawcallsToCmdStream( cmdStream, gfxPassLines, gfxPipelineLines, cameraPushConstants, drawcallsLines, renderTargets, windowHandle ); core.recordEndDebugLabel(cmdStream); core.prepareSwapchainImageForPresent(cmdStream); core.submitCommandStream(cmdStream); gui.beginGUI(); ImGui::Begin("Settings"); ImGui::SliderFloat("Density", &density, std::numeric_limits<float>::epsilon(), 5000.0f); ImGui::SameLine(0.0f, 10.0f); if (ImGui::SmallButton("Reset##density")) { density = 2500.0f; } ImGui::SliderFloat("Radius", &radius, 0.0f, 0.5f); ImGui::SameLine(0.0f, 10.0f); if (ImGui::SmallButton("Reset##radius")) { radius = 0.1f; } ImGui::BeginGroup(); ImGui::SliderFloat("Compression Modulus", &lame1, 0.0f, 100.0f); ImGui::EndGroup(); ImGui::BeginGroup(); ImGui::SliderFloat("Elasticity Modulus", &lame2, 0.0f, 100.0f); ImGui::EndGroup(); ImGui::Spacing(); ImGui::SliderFloat("Simulation Speed", &speed_factor, 0.0f, 2.0f); ImGui::Spacing(); ImGui::Checkbox("Render Grid", &renderGrid); ImGui::DragFloat3("Initial Velocity", reinterpret_cast<float*>(&initialVelocity), 0.001f); ImGui::SameLine(0.0f, 10.0f); if (ImGui::Button("Reset##particle_velocity")) { resetParticles(particles, initialVelocity, density, radius); } ImGui::End(); gui.endGUI(); core.endFrame(windowHandle); } return 0; }