From a55759b31b01aef6fb0e086a1ed0dfd2190900a8 Mon Sep 17 00:00:00 2001 From: saschawillems Date: Sun, 9 Jul 2017 11:39:10 +0200 Subject: [PATCH] Compute shader cloth simulation --- computecloth/computecloth.cpp | 731 ++++++++++++++++++++++ computecloth/computecloth.vcxproj | 102 +++ computecloth/computecloth.vcxproj.filters | 62 ++ data/shaders/computecloth/cloth.comp | 148 +++++ data/shaders/computecloth/cloth.comp.spv | Bin 0 -> 15160 bytes data/shaders/computecloth/cloth.frag | 22 + data/shaders/computecloth/cloth.frag.spv | Bin 0 -> 1840 bytes data/shaders/computecloth/cloth.vert | 35 ++ data/shaders/computecloth/cloth.vert.spv | Bin 0 -> 2216 bytes data/shaders/computecloth/sphere.frag | 19 + data/shaders/computecloth/sphere.frag.spv | Bin 0 -> 1536 bytes data/shaders/computecloth/sphere.vert | 31 + data/shaders/computecloth/sphere.vert.spv | Bin 0 -> 2036 bytes vulkanExamples.sln | 7 + 14 files changed, 1157 insertions(+) create mode 100644 computecloth/computecloth.cpp create mode 100644 computecloth/computecloth.vcxproj create mode 100644 computecloth/computecloth.vcxproj.filters create mode 100644 data/shaders/computecloth/cloth.comp create mode 100644 data/shaders/computecloth/cloth.comp.spv create mode 100644 data/shaders/computecloth/cloth.frag create mode 100644 data/shaders/computecloth/cloth.frag.spv create mode 100644 data/shaders/computecloth/cloth.vert create mode 100644 data/shaders/computecloth/cloth.vert.spv create mode 100644 data/shaders/computecloth/sphere.frag create mode 100644 data/shaders/computecloth/sphere.frag.spv create mode 100644 data/shaders/computecloth/sphere.vert create mode 100644 data/shaders/computecloth/sphere.vert.spv diff --git a/computecloth/computecloth.cpp b/computecloth/computecloth.cpp new file mode 100644 index 00000000..b3aeb489 --- /dev/null +++ b/computecloth/computecloth.cpp @@ -0,0 +1,731 @@ +/* +* Vulkan Example - Compute shader sloth simulation +* +* Updated compute shader by Lukas Bergdoll (https://github.com/Voultapher) +* +* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#include +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include +#include "vulkanexamplebase.h" +#include "VulkanTexture.hpp" +#include "VulkanModel.hpp" + +#define ENABLE_VALIDATION false + +class VulkanExample : public VulkanExampleBase +{ +public: + uint32_t sceneSetup = 1; + uint32_t readSet = 0; + uint32_t indexCount; + + vks::Texture2D textureCloth; + + vks::VertexLayout vertexLayout = vks::VertexLayout({ + vks::VERTEX_COMPONENT_POSITION, + vks::VERTEX_COMPONENT_UV, + vks::VERTEX_COMPONENT_NORMAL, + }); + vks::Model modelSphere; + + // Resources for the graphics part of the example + struct { + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorSet descriptorSet; + VkPipelineLayout pipelineLayout; + struct Pipelines { + VkPipeline cloth; + VkPipeline sphere; + } pipelines; + vks::Buffer indices; + vks::Buffer uniformBuffer; + struct graphicsUBO { + glm::mat4 projection; + glm::mat4 view; + glm::vec4 lightPos = glm::vec4(-1.0f, 2.0f, -1.0f, 1.0f); + } ubo; + } graphics; + + // Resources for the compute part of the example + struct { + struct StorageBuffers { + vks::Buffer input; + vks::Buffer output; + } storageBuffers; + vks::Buffer uniformBuffer; + VkQueue queue; + VkCommandPool commandPool; + std::array commandBuffers; + VkFence fence; + VkDescriptorSetLayout descriptorSetLayout; + std::array descriptorSets; + VkPipelineLayout pipelineLayout; + VkPipeline pipeline; + struct computeUBO { + float deltaT = 0.0f; + float particleMass = 0.1f; + float springStiffness = 2000.0f; + float damping = 0.25f; + float restDistH; + float restDistV; + float restDistD; + float sphereRadius = 0.5f; + glm::vec4 spherePos = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); + glm::vec4 gravity = glm::vec4(0.0f, 9.8f, 0.0f, 0.0f); + glm::ivec2 particleCount; + } ubo; + } compute; + + // SSBO cloth grid particle declaration + struct Particle { + glm::vec4 pos; + glm::vec4 vel; + glm::vec4 uv; + glm::vec4 normal; + float pinned; + glm::vec3 _pad0; + }; + + struct Cloth { + glm::uvec2 gridsize = glm::uvec2(60, 60); + glm::vec2 size = glm::vec2(2.5f, 2.5f); + } cloth; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + enableTextOverlay = true; + title = "Vulkan Example - Compute shader cloth simulation"; + camera.type = Camera::CameraType::lookat; + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f); + camera.setRotation(glm::vec3(-30.0f, -45.0f, 0.0f)); + camera.setTranslation(glm::vec3(0.0f, 0.0f, -3.5f)); + paused = true; + } + + ~VulkanExample() + { + // Graphics + graphics.uniformBuffer.destroy(); + vkDestroyPipeline(device, graphics.pipelines.cloth, nullptr); + vkDestroyPipeline(device, graphics.pipelines.sphere, nullptr); + vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); + textureCloth.destroy(); + modelSphere.destroy(); + + // Compute + compute.storageBuffers.input.destroy(); + compute.storageBuffers.output.destroy(); + compute.uniformBuffer.destroy(); + vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); + vkDestroyPipeline(device, compute.pipeline, nullptr); + vkDestroyFence(device, compute.fence, nullptr); + vkDestroyCommandPool(device, compute.commandPool, nullptr); + } + + // Enable physical device features required for this example + virtual void getEnabledFeatures() + { + if (deviceFeatures.samplerAnisotropy) { + enabledFeatures.samplerAnisotropy = VK_TRUE; + } + }; + + void loadAssets() + { + textureCloth.loadFromFile(getAssetPath() + "textures/vulkan_cloth_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); + modelSphere.loadFromFile(ASSET_PATH "models/geosphere.obj", vertexLayout, compute.ubo.sphereRadius * 0.05f, vulkanDevice, queue); + } + + void buildCommandBuffers() + { + // Destroy command buffers if already present + if (!checkCommandBuffers()) + { + destroyCommandBuffers(); + createCommandBuffers(); + } + + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + + VkClearValue clearValues[2]; + clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };; + clearValues[1].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.renderArea.offset.x = 0; + renderPassBeginInfo.renderArea.offset.y = 0; + renderPassBeginInfo.renderArea.extent.width = width; + renderPassBeginInfo.renderArea.extent.height = height; + renderPassBeginInfo.clearValueCount = 2; + renderPassBeginInfo.pClearValues = clearValues; + + for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) + { + // Set target frame buffer + renderPassBeginInfo.framebuffer = frameBuffers[i]; + + VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); + + // Draw the particle system using the update vertex buffer + + vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); + + VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); + vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); + + VkDeviceSize offsets[1] = { 0 }; + + // Render sphere + if (sceneSetup == 0) { + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelines.sphere); + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL); + vkCmdBindIndexBuffer(drawCmdBuffers[i], modelSphere.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &modelSphere.vertices.buffer, offsets); + vkCmdDrawIndexed(drawCmdBuffers[i], modelSphere.indexCount, 1, 0, 0, 0); + } + + // Render cloth + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelines.cloth); + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL); + vkCmdBindIndexBuffer(drawCmdBuffers[i], graphics.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &compute.storageBuffers.output.buffer, offsets); + vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + + } + + // todo: check barriers (validation, separate compute queue) + void buildComputeCommandBuffer() + { + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + + for (uint32_t i = 0; i < 2; i++) { + + VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffers[i], &cmdBufInfo)); + + VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier(); + bufferBarrier.srcAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; + bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; + bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; + bufferBarrier.size = VK_WHOLE_SIZE; + + std::vector bufferBarriers; + bufferBarrier.buffer = compute.storageBuffers.input.buffer; + bufferBarriers.push_back(bufferBarrier); + bufferBarrier.buffer = compute.storageBuffers.output.buffer; + bufferBarriers.push_back(bufferBarrier); + + vkCmdPipelineBarrier(compute.commandBuffers[i], + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_FLAGS_NONE, + 0, nullptr, + static_cast(bufferBarriers.size()), bufferBarriers.data(), + 0, nullptr); + + vkCmdBindPipeline(compute.commandBuffers[i], VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline); + + // Dispatch the compute job + for (uint32_t j = 0; j < 64; j++) { + readSet = 1 - readSet; + vkCmdBindDescriptorSets(compute.commandBuffers[i], VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSets[readSet], 0, 0); + + vkCmdDispatch(compute.commandBuffers[i], cloth.gridsize.x / 10, cloth.gridsize.y / 10, 1); + + for (auto &barrier : bufferBarriers) { + barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; + barrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; + } + + vkCmdPipelineBarrier( + compute.commandBuffers[i], + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_FLAGS_NONE, + 0, nullptr, + static_cast(bufferBarriers.size()), bufferBarriers.data(), + 0, nullptr); + + } + + for (auto &barrier : bufferBarriers) { + barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; + barrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; + barrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; + } + + vkCmdPipelineBarrier( + compute.commandBuffers[i], + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + VK_FLAGS_NONE, + 0, nullptr, + static_cast(bufferBarriers.size()), bufferBarriers.data(), + 0, nullptr); + + vkEndCommandBuffer(compute.commandBuffers[i]); + } + } + + // Setup and fill the compute shader storage buffers containing the particles + void prepareStorageBuffers() + { + std::vector particleBuffer(cloth.gridsize.x * cloth.gridsize.y); + + float dx = cloth.size.x / (cloth.gridsize.x - 1); + float dy = cloth.size.y / (cloth.gridsize.y - 1); + float du = 1.0f / (cloth.gridsize.x - 1); + float dv = 1.0f / (cloth.gridsize.y - 1); + + switch (sceneSetup) { + case 0 : + { + // Horz. cloth falls onto sphere + glm::mat4 transM = glm::translate(glm::mat4(), glm::vec3(- cloth.size.x / 2.0f, -2.0f, - cloth.size.y / 2.0f)); + for (uint32_t i = 0; i < cloth.gridsize.y; i++) { + for (uint32_t j = 0; j < cloth.gridsize.x; j++) { + particleBuffer[i + j * cloth.gridsize.y].pos = transM * glm::vec4(dx * j, 0.0f, dy * i, 1.0f); + particleBuffer[i + j * cloth.gridsize.y].vel = glm::vec4(0.0f); + particleBuffer[i + j * cloth.gridsize.y].uv = glm::vec4(1.0f - du * i, dv * j, 0.0f, 0.0f); + } + } + break; + } + case 1: + { + // Vert. Pinned cloth + glm::mat4 transM = glm::translate(glm::mat4(), glm::vec3(- cloth.size.x / 2.0f, - cloth.size.y / 2.0f, 0.0f)); + for (uint32_t i = 0; i < cloth.gridsize.y; i++) { + for (uint32_t j = 0; j < cloth.gridsize.x; j++) { + particleBuffer[i + j * cloth.gridsize.y].pos = transM * glm::vec4(dx * j, dy * i, 0.0f, 1.0f); + particleBuffer[i + j * cloth.gridsize.y].vel = glm::vec4(0.0f); + particleBuffer[i + j * cloth.gridsize.y].uv = glm::vec4(du * j, dv * i, 0.0f, 0.0f); + // Pin some particles + particleBuffer[i + j * cloth.gridsize.y].pinned = (i == 0) && ((j == 0) || (j == cloth.gridsize.x / 3) || (j == cloth.gridsize.x - cloth.gridsize.x / 3) || (j == cloth.gridsize.x - 1)); + // Remove sphere + compute.ubo.spherePos.z = -10.0f; + } + } + break; + } + } + + VkDeviceSize storageBufferSize = particleBuffer.size() * sizeof(Particle); + + // Staging + // SSBO won't be changed on the host after upload so copy to device local memory + + vks::Buffer stagingBuffer; + + vulkanDevice->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &stagingBuffer, + storageBufferSize, + particleBuffer.data()); + + vulkanDevice->createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &compute.storageBuffers.input, + storageBufferSize); + + vulkanDevice->createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &compute.storageBuffers.output, + storageBufferSize); + + // Copy from staging buffer + VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + VkBufferCopy copyRegion = {}; + copyRegion.size = storageBufferSize; + vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.input.buffer, 1, ©Region); + vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.output.buffer, 1, ©Region); + VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true); + + stagingBuffer.destroy(); + + // Indices + std::vector indices; + for (uint32_t y = 0; y < cloth.gridsize.y - 1; y++) { + for (uint32_t x = 0; x < cloth.gridsize.x; x++) { + indices.push_back((y + 1) * cloth.gridsize.x + x); + indices.push_back((y)* cloth.gridsize.x + x); + } + // Primitive restart (signlaed by special value 0xFFFFFFFF) + indices.push_back(0xFFFFFFFF); + } + uint32_t indexBufferSize = static_cast(indices.size()) * sizeof(uint32_t); + indexCount = static_cast(indices.size()); + + vulkanDevice->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &stagingBuffer, + indexBufferSize, + indices.data()); + + vulkanDevice->createBuffer( + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &graphics.indices, + indexBufferSize); + + // Copy from staging buffer + copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + copyRegion = {}; + copyRegion.size = indexBufferSize; + vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, graphics.indices.buffer, 1, ©Region); + VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true); + + stagingBuffer.destroy(); + } + + void setupDescriptorPool() + { + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2) + }; + + VkDescriptorPoolCreateInfo descriptorPoolInfo = + vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); + + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + } + + void setupLayoutsAndDescriptors() + { + // Set layout + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1) + }; + VkDescriptorSetLayoutCreateInfo descriptorLayout = + vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = + vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); + + // Set + VkDescriptorSetAllocateInfo allocInfo = + vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); + + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &graphics.uniformBuffer.descriptor), + vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureCloth.descriptor) + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + } + + void preparePipelines() + { + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = + vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0, VK_TRUE); + + VkPipelineRasterizationStateCreateInfo rasterizationState = + vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); + + VkPipelineColorBlendAttachmentState blendAttachmentState = + vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + + VkPipelineColorBlendStateCreateInfo colorBlendState = + vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + + VkPipelineDepthStencilStateCreateInfo depthStencilState = + vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); + + VkPipelineViewportStateCreateInfo viewportState = + vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + + VkPipelineMultisampleStateCreateInfo multisampleState = + vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0); + + std::vector dynamicStateEnables = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState = + vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0); + + // Rendering pipeline + std::array shaderStages; + + shaderStages[0] = loadShader(getAssetPath() + "shaders/computecloth/cloth.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getAssetPath() + "shaders/computecloth/cloth.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + + VkGraphicsPipelineCreateInfo pipelineCreateInfo = + vks::initializers::pipelineCreateInfo( + graphics.pipelineLayout, + renderPass, + 0); + + // Input attributes + + // Binding description + std::vector inputBindings = { + vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX) + }; + + // Attribute descriptions + std::vector inputAttributes = { + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Particle, pos)), + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Particle, uv)), + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Particle, normal)) + }; + + // Assign to vertex buffer + VkPipelineVertexInputStateCreateInfo inputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + inputState.vertexBindingDescriptionCount = static_cast(inputBindings.size()); + inputState.pVertexBindingDescriptions = inputBindings.data(); + inputState.vertexAttributeDescriptionCount = static_cast(inputAttributes.size()); + inputState.pVertexAttributeDescriptions = inputAttributes.data(); + + pipelineCreateInfo.pVertexInputState = &inputState; + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; + pipelineCreateInfo.pRasterizationState = &rasterizationState; + pipelineCreateInfo.pColorBlendState = &colorBlendState; + pipelineCreateInfo.pMultisampleState = &multisampleState; + pipelineCreateInfo.pViewportState = &viewportState; + pipelineCreateInfo.pDepthStencilState = &depthStencilState; + pipelineCreateInfo.pDynamicState = &dynamicState; + pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); + pipelineCreateInfo.pStages = shaderStages.data(); + pipelineCreateInfo.renderPass = renderPass; + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipelines.cloth)); + + // Sphere rendering pipeline + inputBindings = { + vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX) + }; + inputAttributes = { + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 3), + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 5) + }; + inputState.vertexAttributeDescriptionCount = static_cast(inputAttributes.size()); + inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; + shaderStages[0] = loadShader(getAssetPath() + "shaders/computecloth/sphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getAssetPath() + "shaders/computecloth/sphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipelines.sphere)); + } + + void prepareCompute() + { + // Create a compute capable device queue + vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue); + + // Create compute pipeline + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 0), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 1), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 2), + }; + + VkDescriptorSetLayoutCreateInfo descriptorLayout = + vks::initializers::descriptorSetLayoutCreateInfo( + setLayoutBindings.data(), + static_cast(setLayoutBindings.size())); + + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); + + VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = + vks::initializers::pipelineLayoutCreateInfo( + &compute.descriptorSetLayout, + 1); + + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); + + VkDescriptorSetAllocateInfo allocInfo = + vks::initializers::descriptorSetAllocateInfo( + descriptorPool, + &compute.descriptorSetLayout, + 1); + + // Create two descriptor sets with input and output buffers switched + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSets[0])); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSets[1])); + + std::vector computeWriteDescriptorSets = { + vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &compute.storageBuffers.input.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &compute.storageBuffers.output.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &compute.uniformBuffer.descriptor), + + vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &compute.storageBuffers.output.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &compute.storageBuffers.input.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &compute.uniformBuffer.descriptor) + }; + + vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, NULL); + + // Create pipeline + VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0); + computePipelineCreateInfo.stage = loadShader(getAssetPath() + "shaders/computecloth/cloth.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); + VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline)); + + // Separate command pool as queue family for compute may be different than graphics + VkCommandPoolCreateInfo cmdPoolInfo = {}; + cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; + cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool)); + + // Create a command buffer for compute operations + VkCommandBufferAllocateInfo cmdBufAllocateInfo = + vks::initializers::commandBufferAllocateInfo(compute.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 2); + + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffers[0])); + + // Fence for compute CB sync + VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT); + VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &compute.fence)); + + // Build a single command buffer containing the compute dispatch commands + buildComputeCommandBuffer(); + } + + // Prepare and initialize uniform buffer containing shader uniforms + void prepareUniformBuffers() + { + // Compute shader uniform buffer block + vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &compute.uniformBuffer, + sizeof(compute.ubo)); + VK_CHECK_RESULT(compute.uniformBuffer.map()); + + // Initial values + float dx = cloth.size.x / (cloth.gridsize.x - 1); + float dy = cloth.size.y / (cloth.gridsize.y - 1); + + compute.ubo.restDistH = dx; + compute.ubo.restDistV = dy; + compute.ubo.restDistD = sqrtf(dx * dx + dy * dy); + compute.ubo.particleCount = cloth.gridsize; + + updateComputeUBO(); + + // Vertex shader uniform buffer block + vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &graphics.uniformBuffer, + sizeof(graphics.ubo)); + VK_CHECK_RESULT(graphics.uniformBuffer.map()); + + updateGraphicsUBO(); + } + + void updateComputeUBO() + { + if (!paused) { + compute.ubo.deltaT = 0.000005f; + // todo: base on frametime + //compute.ubo.deltaT = frameTimer * 0.0075f; + } + else { + compute.ubo.deltaT = 0.0f; + } + memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo)); + } + + void updateGraphicsUBO() + { + graphics.ubo.projection = camera.matrices.perspective; + graphics.ubo.view = camera.matrices.view; + memcpy(graphics.uniformBuffer.mapped, &graphics.ubo, sizeof(graphics.ubo)); + } + + void draw() + { + // Submit graphics commands + VulkanExampleBase::prepareFrame(); + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + + VulkanExampleBase::submitFrame(); + + vkWaitForFences(device, 1, &compute.fence, VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &compute.fence); + + VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo(); + computeSubmitInfo.commandBufferCount = 1; + computeSubmitInfo.pCommandBuffers = &compute.commandBuffers[readSet]; + + VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, compute.fence)); + } + + void prepare() + { + VulkanExampleBase::prepare(); + loadAssets(); + prepareStorageBuffers(); + prepareUniformBuffers(); + setupDescriptorPool(); + setupLayoutsAndDescriptors(); + preparePipelines(); + prepareCompute(); + buildCommandBuffers(); + prepared = true; + } + + virtual void render() + { + if (!prepared) + return; + draw(); + + updateComputeUBO(); + } + + virtual void viewChanged() + { + updateGraphicsUBO(); + } + + virtual void getOverlayText(VulkanTextOverlay *textOverlay) + { + textOverlay->addText(std::to_string(frameTimer * 0.0075f), 5.0f, 85.0f, VulkanTextOverlay::alignLeft); + } +}; + +VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/computecloth/computecloth.vcxproj b/computecloth/computecloth.vcxproj new file mode 100644 index 00000000..26e510f8 --- /dev/null +++ b/computecloth/computecloth.vcxproj @@ -0,0 +1,102 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {12ACF921-90B1-44D9-AF06-B5C0F0B8191A} + Win32Proj + 8.1 + + + + Application + true + v140 + + + Application + false + v140 + + + + + + + + + + + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + + WIN32;_DEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + true + Windows + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + WIN32;NDEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + + + true + Windows + true + true + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/computecloth/computecloth.vcxproj.filters b/computecloth/computecloth.vcxproj.filters new file mode 100644 index 00000000..68a12b56 --- /dev/null +++ b/computecloth/computecloth.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {e28680da-cc95-413d-b6f0-0e1f9967ee88} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + Shaders + + + \ No newline at end of file diff --git a/data/shaders/computecloth/cloth.comp b/data/shaders/computecloth/cloth.comp new file mode 100644 index 00000000..edd97488 --- /dev/null +++ b/data/shaders/computecloth/cloth.comp @@ -0,0 +1,148 @@ +#version 450 + +struct Particle { + vec4 pos; + vec4 vel; + vec4 uv; + vec4 normal; + float pinned; +}; + +layout(std430, binding = 0) buffer ParticleIn { + Particle particleIn[ ]; +}; + +layout(std430, binding = 1) buffer ParticleOut { + Particle particleOut[ ]; +}; + +// todo: use shared memory to speed up calculation + +layout (local_size_x = 10, local_size_y = 10) in; + +layout (binding = 2) uniform UBO +{ + float deltaT; + float particleMass; + float springStiffness; + float damping; + float restDistH; + float restDistV; + float restDistD; + float sphereRadius; + vec4 spherePos; + vec4 gravity; + ivec2 particleCount; +} params; + +vec3 springForce(vec3 p0, vec3 p1, float restDist) +{ + vec3 dist = p0 - p1; + return normalize(dist) * params.springStiffness * (length(dist) - restDist); +} + +void main() +{ + uvec3 id = gl_GlobalInvocationID; + + uint index = id.y * params.particleCount.x + id.x; + if (index > params.particleCount.x * params.particleCount.y) + return; + + // Pinned? + if (particleIn[index].pinned == 1.0) { + particleOut[index].pos = particleOut[index].pos; + particleOut[index].vel = vec4(0.0); + return; + } + + // Initial force from gravity + vec3 force = params.gravity.xyz * params.particleMass; + + vec3 pos = particleIn[index].pos.xyz; + vec3 vel = particleIn[index].vel.xyz; + + // Spring forces from neighboring particles + // left + if (id.x > 0) { + force += springForce(particleIn[index-1].pos.xyz, pos, params.restDistH); + } + // right + if (id.x < params.particleCount.x - 1) { + force += springForce(particleIn[index + 1].pos.xyz, pos, params.restDistH); + } + // upper + if (id.y < params.particleCount.y - 1) { + force += springForce(particleIn[index + params.particleCount.x].pos.xyz, pos, params.restDistV); + } + // lower + if (id.y > 0) { + force += springForce(particleIn[index - params.particleCount.x].pos.xyz, pos, params.restDistV); + } + // upper-left + if ((id.x > 0) && (id.y < params.particleCount.y - 1)) { + force += springForce(particleIn[index + params.particleCount.x - 1].pos.xyz, pos, params.restDistD); + } + // lower-left + if ((id.x > 0) && (id.y > 0)) { + force += springForce(particleIn[index - params.particleCount.x - 1].pos.xyz, pos, params.restDistD); + } + // upper-right + if ((id.x < params.particleCount.x - 1) && (id.y < params.particleCount.y - 1)) { + force += springForce(particleIn[index + params.particleCount.x + 1].pos.xyz, pos, params.restDistD); + } + // lower-right + if ((id.x < params.particleCount.x - 1) && (id.y > 0)) { + force += springForce(particleIn[index - params.particleCount.x + 1].pos.xyz, pos, params.restDistD); + } + + force += (-params.damping * vel); + + // Integrate + vec3 f = force * (1.0 / params.particleMass); + particleOut[index].pos = vec4(pos + vel * params.deltaT + 0.5 * f * params.deltaT * params.deltaT, 1.0); + particleOut[index].vel = vec4(vel + f * params.deltaT, 0.0); + + // Sphere collision + vec3 sphereDist = particleOut[index].pos.xyz - params.spherePos.xyz; + if (length(sphereDist) < params.sphereRadius + 0.01) { + // If the particle is inside the sphere, push it to the outer radius + particleOut[index].pos.xyz = params.spherePos.xyz + normalize(sphereDist) * (params.sphereRadius + 0.01); + // Cancel out velocity + particleOut[index].vel = vec4(0.0); + } + + // Normals + // todo: Only once (use push const to check) + vec3 normal = vec3(0.0); + vec3 a, b, c; + if (id.y > 0) { + if (id.x > 0) { + a = particleIn[index - 1].pos.xyz - pos; + b = particleIn[index - params.particleCount.x - 1].pos.xyz - pos; + c = particleIn[index - params.particleCount.x].pos.xyz - pos; + normal += cross(a,b) + cross(b,c); + } + if (id.x < params.particleCount.x - 1) { + a = particleIn[index - params.particleCount.x].pos.xyz - pos; + b = particleIn[index - params.particleCount.x + 1].pos.xyz - pos; + c = particleIn[index + 1].pos.xyz - pos; + normal += cross(a,b) + cross(b,c); + } + } + if (id.y < params.particleCount.y - 1) { + if (id.x > 0) { + a = particleIn[index + params.particleCount.x].pos.xyz - pos; + b = particleIn[index + params.particleCount.x - 1].pos.xyz - pos; + c = particleIn[index - 1].pos.xyz - pos; + normal += cross(a,b) + cross(b,c); + } + if (id.x < params.particleCount.x - 1) { + a = particleIn[index + 1].pos.xyz - pos; + b = particleIn[index + params.particleCount.x + 1].pos.xyz - pos; + c = particleIn[index + params.particleCount.x].pos.xyz - pos; + normal += cross(a,b) + cross(b,c); + } + } + particleOut[index].normal = vec4(normalize(normal), 0.0f); +} \ No newline at end of file diff --git a/data/shaders/computecloth/cloth.comp.spv b/data/shaders/computecloth/cloth.comp.spv new file mode 100644 index 0000000000000000000000000000000000000000..98bba249a204fd95703431a19442c9ed14bc5548 GIT binary patch literal 15160 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 literal 0 HcmV?d00001 diff --git a/data/shaders/computecloth/cloth.frag b/data/shaders/computecloth/cloth.frag new file mode 100644 index 00000000..e5f30db5 --- /dev/null +++ b/data/shaders/computecloth/cloth.frag @@ -0,0 +1,22 @@ +#version 450 + +layout (binding = 1) uniform sampler2D samplerColor; + +layout (location = 0) in vec2 inUV; +layout (location = 1) in vec3 inNormal; +layout (location = 2) in vec3 inViewVec; +layout (location = 3) in vec3 inLightVec; + +layout (location = 0) out vec4 outFragColor; + +void main () +{ + vec3 color = texture(samplerColor, inUV).rgb; + vec3 N = normalize(inNormal); + vec3 L = normalize(inLightVec); + vec3 V = normalize(inViewVec); + vec3 R = reflect(-L, N); + vec3 diffuse = max(dot(N, L), 0.15) * vec3(1.0); + vec3 specular = pow(max(dot(R, V), 0.0), 8.0) * vec3(0.2); + outFragColor = vec4(diffuse * color.rgb + specular, 1.0); +} diff --git a/data/shaders/computecloth/cloth.frag.spv b/data/shaders/computecloth/cloth.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..03f2ff3051bb2957dbc6762616bc09837690660f GIT binary patch literal 1840 zcmZ9MOH)%p5QWEtM1;sg6pX0c5Z@xc08u1>DA}a&RmOEH1iV!tq4Fq~>cXunH~Mp2 z>;G|MmFJsGTeqB4*Yr94nC_XrNvDd(oja!Auq(U4nsg~)=SE%D@8!zt%3`;-x45)? z2ahqA_Kx_ByAd~}9XYJUEew;Avy$_YNU|=OPz*jgOj=qeIQK&dGGddl;1jad+s$@I zdqy>!)ZVQf9yJ@C?LhlD&WT%Zs@jCpZd&`U*E9b7xV78v9M+m@UQN&`lFwDoU{xVr0Y67rmu?<7Iy9uY<6k~^9tCbfVopJ_igc0x@q`=zjf#J zY3W&Ukz_{xya||mk?^o~I3%yv7}1WM^OMW)tRhEcz>oM`aDrP4n4VdjH?Ez0)P?_q zc5=bv59SWw@SoC-KXZcrjCO3)hd-D*f-}q=C2WKQWlMmm#g!jvZyFVu#|7Q|x#Ly$W#axii8(WNhAH-lIhI@dwn1b|3 zVlng5?Hp{xa98jab4faRK7?8?ONTT273tKLQz!ItRYK0P1Yhjr<<7BL+;!>18NMN% zbH4h24(7`_eBjB=%-)iaoA<)b41bY7=kmwQ%;Lj&_`q5Id(x@F@O|m@YUeFU#|Pfd zTaj+(VQ2Quwi;}_34VMBR?C`n<{lqvVV>aZ?2>fqGrS?4nfM%f*_4hCy!mWNC(ozg z^F;cl1pZ~Xvu)|{EaRR^hocYUm=U;?7+sqzVYC7;S0 ztNgz?(;-f(GyVU)%yjo8`Hrz;&gJy!cB?L1{jNjUxgOW$_j2WZWn#a%Jux*siAS%? zdq;fw+$pbj>U(!HsRNEndL(_4e#vDCfiEPlC3$^1wEtFu$aU&-6bmLkJ!o!JwS&o( z)IAiAd|ugC)5h**&HF_DoTT2OeB7XH@%MN=-6v1p4GV89uc@jouM4`pM*2D4Y9^^} zZaj7ErrUAtOA>#TZajCcB|D#*K4X^ioVbIJsXvc8lX_;16g45dQ~R(UH>z=?8GjS> zx!_sY(*49A&i#CI#iP$?-_ymZe?fY!b!O!IsQN2hr5bO^C-Qw2SJjQ7TsicypjZ}| zK7y$a%zWB0vjS7+itO}@6-id~ZFtPH|8sgo?zH#I;y9x{4ouvl#B#wM#bx<98^1$I zIm`@>`_k)C3CnOPgC5g=tCusZ&(zH#ADrdmJ>sADwoczetA9XV_?2YEjy@!1ZczBV_M(I{#{<3)F!2|qXT8hf zxg&GJpZKD7ghhN&dkLUL{HXSvggVTAHE@P+2y+HF^Am7~3k7wGc~dXB?rNuZaMS{x z3>>l4a!(;{#6~=Ke^++aM3;fbk;-{d_E; zj(75>7f%AGt;Nj-F4x9A4IFRA>YbBLJpS0Jmou3+HtxjRT9#1TqQq)>CLMlF|32L7 zxdaYjaW-d>Z$UeDtKp?|ILlR*&Y6~LMLHb9vRprPAI^FuAqRfs=dHt8zBkg_^Hqf5 X5SHcp5qkPo96sdYcLM$^uNlcdx1N0_ literal 0 HcmV?d00001 diff --git a/data/shaders/computecloth/sphere.frag b/data/shaders/computecloth/sphere.frag new file mode 100644 index 00000000..45be5319 --- /dev/null +++ b/data/shaders/computecloth/sphere.frag @@ -0,0 +1,19 @@ +#version 450 + +layout (location = 0) in vec3 inNormal; +layout (location = 1) in vec3 inViewVec; +layout (location = 2) in vec3 inLightVec; + +layout (location = 0) out vec4 outFragColor; + +void main () +{ + vec3 color = vec3(0.5); + vec3 N = normalize(inNormal); + vec3 L = normalize(inLightVec); + vec3 V = normalize(inViewVec); + vec3 R = reflect(-L, N); + vec3 diffuse = max(dot(N, L), 0.15) * vec3(1.0); + vec3 specular = pow(max(dot(R, V), 0.0), 32.0) * vec3(1.0); + outFragColor = vec4(diffuse * color.rgb + specular, 1.0); +} diff --git a/data/shaders/computecloth/sphere.frag.spv b/data/shaders/computecloth/sphere.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..aa279158491f6ae0a3bd90d52be3c5a42e7302e3 GIT binary patch literal 1536 zcmYk5+fNfw5XMK!7LdwC6vS%UXs2yrQjx|HNC_~^}$!4e9=G0 zzseUAzu(z2W_L3=^L;aO=CZSevDFE)Q+g-unPtzsjR~7gTQQuq`uloyFg&boZau(b z#tOj^pIMu-ah=FXGwouSk(`w*N;V{O^2e@3(h9oW?6-mwvH3Xglx(eDr`Ok+$hV~P zAm~ZBH|-wu`X|kfs9>MhS&w#drrmma{9)K=w=(;AagAuFp0wLY+n<6t#4n3`7wqG9 zRp()PbTk^YWlO9gJowmdjXF)r5mOBP^hU$i{pRt@Sf2Z87gD>ch#i9rHfjS@Rq*r+A z0y8tXZG8#mg)a}>A2Eqee3=b<pu~ z5@uS8-(gNV^Lr)1Mhx}9d(47#=I$})q=8cXWBzb{KQYwc_>OdX^?Y}w;{)&c)}?zs?Chi4s?o+Q@MAZ< zmiyAF1s`gu=!Em!kEBzd<4x)8#JAYXmUMjJ-Dg`m_k4{$kEP=W|2EESM>;%@b5Eqh i(T8(SBga4SM$^mw88hPX$IibAuli@60LZdh}<&ZMkd>yIyhU&bofTSE_HT#dc?}xKMh4 z$Dqr3M|_6l#p)B9JIMj|s4y!$7s|pbVN=NIPp|f03XBxd9RokDv-n$FOWU01eCBr> z+p3~J+~V9}EBzGjc9K*#nNA(ebT4jvPU0`JP3Nvgvj4H;GiEu@OFR0I`unLfImiwp zMcokJZ@jO?t$N(*#9u{yE_4=WX*=lHEADGxB{%k2+OKsmHtW ziF{wA)m1T)*XWb9*J-P{)9`sC-?!x8MAD4|rzUFV{E~KJ(a8^|k7(o{)1DVtuV#+RISel=*ut!Hrj3W9UTYm=&idC(7Pxv8DH(5MAI8#H2h>zu&C|E_lYEq)=` zyv6(%W%GXgp9I_dm&J*hkM}ok%Kwj7kI}T`O^#jBSB-C_E0t&b2IHx(3qjU_pxl^ z@#mcPawl`+jGFkSmIU6$w`Om78uV>Vp1Ph1Xn@7t+(o{UcFye$E3(ln*Q#vpv|MYl z(E!VG{ZKvJwJwk&^UL{fI@7W~Z|JN$-wSaxz_NVbLr-5y!-rbS+Tm7fMK-?y&iHM> J|0u(f@E3I