Updated compute shader particle system with point sprites and gradient coloring

This commit is contained in:
saschawillems 2016-05-14 23:09:17 +02:00
parent 72af27a49a
commit 3ae4f26901
9 changed files with 131 additions and 77 deletions

View file

@ -9,6 +9,7 @@ if %ERRORLEVEL% EQU 0 (
mkdir "assets\textures" mkdir "assets\textures"
xcopy "..\..\data\textures\particle01_rgba.ktx" "assets\textures" /Y xcopy "..\..\data\textures\particle01_rgba.ktx" "assets\textures" /Y
xcopy "..\..\data\textures\particle_gradient_rgba.ktx" "assets\textures" /Y
mkdir "res\drawable" mkdir "res\drawable"
xcopy "..\..\android\images\icon.png" "res\drawable" /Y xcopy "..\..\android\images\icon.png" "res\drawable" /Y

View file

@ -1,6 +1,8 @@
/* /*
* Vulkan Example - Attraction based compute shader particle system * Vulkan Example - Attraction based compute shader particle system
* *
* Updated compute shader by Lukas Bergdoll (https://github.com/Voultapher)
*
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de * Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
* *
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
@ -25,20 +27,23 @@
#define ENABLE_VALIDATION false #define ENABLE_VALIDATION false
#if defined(__ANDROID__) #if defined(__ANDROID__)
// Lower particle count on Android for performance reasons // Lower particle count on Android for performance reasons
#define PARTICLE_COUNT 512 * 1024 #define PARTICLE_COUNT 64 * 1024
#else #else
#define PARTICLE_COUNT 2048 * 1024 #define PARTICLE_COUNT 256 * 1024
#endif #endif
class VulkanExample : public VulkanExampleBase class VulkanExample : public VulkanExampleBase
{ {
private:
vkTools::VulkanTexture textureColorMap;
public: public:
float timer = 0.f; float timer = 0.f;
float animStart = 20.0f; float animStart = 20.0f;
bool animate = true; bool animate = true;
struct {
vkTools::VulkanTexture particle;
vkTools::VulkanTexture gradient;
} textures;
struct { struct {
VkPipelineVertexInputStateCreateInfo inputState; VkPipelineVertexInputStateCreateInfo inputState;
std::vector<VkVertexInputBindingDescription> bindingDescriptions; std::vector<VkVertexInputBindingDescription> bindingDescriptions;
@ -76,9 +81,11 @@ public:
struct Particle { struct Particle {
glm::vec2 pos; glm::vec2 pos;
glm::vec2 vel; glm::vec2 vel;
glm::vec4 gradientPos;
}; };
VkPipelineLayout pipelineLayout; VkPipelineLayout pipelineLayout;
VkDescriptorSet descriptorSetPostCompute;
VkDescriptorSetLayout descriptorSetLayout; VkDescriptorSetLayout descriptorSetLayout;
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
@ -105,6 +112,15 @@ public:
vkDestroyPipelineLayout(device, computePipelineLayout, nullptr); vkDestroyPipelineLayout(device, computePipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, computeDescriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(device, computeDescriptorSetLayout, nullptr);
vkDestroyPipeline(device, pipelines.compute, nullptr); vkDestroyPipeline(device, pipelines.compute, nullptr);
textureLoader->destroyTexture(textures.particle);
textureLoader->destroyTexture(textures.gradient);
}
void loadTextures()
{
textureLoader->loadTexture(getAssetPath() + "textures/particle01_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, &textures.particle, false);
textureLoader->loadTexture(getAssetPath() + "textures/particle_gradient_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, &textures.gradient, false);
} }
void buildCommandBuffers() void buildCommandBuffers()
@ -131,15 +147,12 @@ public:
renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues; renderPassBeginInfo.pClearValues = clearValues;
VkResult err;
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
{ {
// Set target frame buffer // Set target frame buffer
renderPassBeginInfo.framebuffer = frameBuffers[i]; renderPassBeginInfo.framebuffer = frameBuffers[i];
err = vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo); VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
assert(!err);
// Buffer memory barrier to make sure that compute shader // Buffer memory barrier to make sure that compute shader
// writes are finished before using the storage buffer // writes are finished before using the storage buffer
@ -166,23 +179,15 @@ public:
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = vkTools::initializers::viewport( VkViewport viewport = vkTools::initializers::viewport((float)width, (float)height, 0.0f, 1.0f
(float)width,
(float)height,
0.0f,
1.0f
); );
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vkTools::initializers::rect2D( VkRect2D scissor = vkTools::initializers::rect2D(width, height, 0, 0);
width,
height,
0,
0
);
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.postCompute); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.postCompute);
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSetPostCompute, 0, NULL);
VkDeviceSize offsets[1] = { 0 }; VkDeviceSize offsets[1] = { 0 };
vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &computeStorageBuffer.buffer, offsets); vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &computeStorageBuffer.buffer, offsets);
@ -190,8 +195,7 @@ public:
vkCmdEndRenderPass(drawCmdBuffers[i]); vkCmdEndRenderPass(drawCmdBuffers[i]);
err = vkEndCommandBuffer(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
assert(!err);
} }
} }
@ -212,11 +216,8 @@ public:
void draw() void draw()
{ {
VkResult err;
// Get next image in the swap chain (back/front buffer) // Get next image in the swap chain (back/front buffer)
err = swapChain.acquireNextImage(semaphores.presentComplete, &currentBuffer); VK_CHECK_RESULT(swapChain.acquireNextImage(semaphores.presentComplete, &currentBuffer));
assert(!err);
submitPostPresentBarrier(swapChain.buffers[currentBuffer].image); submitPostPresentBarrier(swapChain.buffers[currentBuffer].image);
@ -225,27 +226,22 @@ public:
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
// Submit to queue // Submit to queue
err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
assert(!err);
submitPrePresentBarrier(swapChain.buffers[currentBuffer].image); submitPrePresentBarrier(swapChain.buffers[currentBuffer].image);
err = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete); VK_CHECK_RESULT(swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete));
assert(!err);
// Compute // Compute
VkSubmitInfo computeSubmitInfo = vkTools::initializers::submitInfo(); VkSubmitInfo computeSubmitInfo = vkTools::initializers::submitInfo();
computeSubmitInfo.commandBufferCount = 1; computeSubmitInfo.commandBufferCount = 1;
computeSubmitInfo.pCommandBuffers = &computeCmdBuffer; computeSubmitInfo.pCommandBuffers = &computeCmdBuffer;
err = vkQueueSubmit(computeQueue, 1, &computeSubmitInfo, VK_NULL_HANDLE); VK_CHECK_RESULT(vkQueueSubmit(computeQueue, 1, &computeSubmitInfo, VK_NULL_HANDLE));
assert(!err);
err = vkQueueWaitIdle(queue); VK_CHECK_RESULT(vkQueueWaitIdle(queue));
assert(!err);
err = vkQueueWaitIdle(computeQueue); VK_CHECK_RESULT(vkQueueWaitIdle(computeQueue));
assert(!err);
} }
// Setup and fill the compute shader storage buffers for // Setup and fill the compute shader storage buffers for
@ -262,6 +258,7 @@ public:
{ {
particle.pos = glm::vec2(rDistribution(rGenerator), rDistribution(rGenerator)); particle.pos = glm::vec2(rDistribution(rGenerator), rDistribution(rGenerator));
particle.vel = glm::vec2(0.0f); particle.vel = glm::vec2(0.0f);
particle.gradientPos.x = particle.pos.x / 2.0f;
} }
uint32_t storageBufferSize = particleBuffer.size() * sizeof(Particle); uint32_t storageBufferSize = particleBuffer.size() * sizeof(Particle);
@ -319,7 +316,7 @@ public:
// Attribute descriptions // Attribute descriptions
// Describes memory layout and shader positions // Describes memory layout and shader positions
vertices.attributeDescriptions.resize(1); vertices.attributeDescriptions.resize(2);
// Location 0 : Position // Location 0 : Position
vertices.attributeDescriptions[0] = vertices.attributeDescriptions[0] =
vkTools::initializers::vertexInputAttributeDescription( vkTools::initializers::vertexInputAttributeDescription(
@ -327,6 +324,13 @@ public:
0, 0,
VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32_SFLOAT,
0); 0);
// Location 1 : Gradient position
vertices.attributeDescriptions[1] =
vkTools::initializers::vertexInputAttributeDescription(
VERTEX_BUFFER_BIND_ID,
1,
VK_FORMAT_R32G32B32A32_SFLOAT,
4 * sizeof(float));
// Assign to vertex buffer // Assign to vertex buffer
vertices.inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo(); vertices.inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo();
@ -341,7 +345,8 @@ public:
std::vector<VkDescriptorPoolSize> poolSizes = std::vector<VkDescriptorPoolSize> poolSizes =
{ {
vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1) vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1),
vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
}; };
VkDescriptorPoolCreateInfo descriptorPoolInfo = VkDescriptorPoolCreateInfo descriptorPoolInfo =
@ -350,30 +355,74 @@ public:
poolSizes.data(), poolSizes.data(),
2); 2);
VkResult vkRes = vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
assert(!vkRes);
} }
void setupDescriptorSetLayout() void setupDescriptorSetLayout()
{ {
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
// Binding 0 : Particle color map
setLayoutBindings.push_back(vkTools::initializers::descriptorSetLayoutBinding(
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_SHADER_STAGE_FRAGMENT_BIT,
0));
// Binding 1 : Particle gradient ramp
setLayoutBindings.push_back(vkTools::initializers::descriptorSetLayoutBinding(
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_SHADER_STAGE_FRAGMENT_BIT,
1));
VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo; VkDescriptorSetLayoutCreateInfo descriptorLayout =
descriptorLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; vkTools::initializers::descriptorSetLayoutCreateInfo(
descriptorLayoutInfo.pNext = NULL; setLayoutBindings.data(),
descriptorLayoutInfo.flags = 0; setLayoutBindings.size());
descriptorLayoutInfo.bindingCount = 0;
descriptorLayoutInfo.pBindings = nullptr;
VkResult err = vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayout); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
assert(!err);
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
vkTools::initializers::pipelineLayoutCreateInfo( vkTools::initializers::pipelineLayoutCreateInfo(
&descriptorSetLayout, &descriptorSetLayout,
0); 1);
err = vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
assert(!err); }
void setupDescriptorSet()
{
VkDescriptorSetAllocateInfo allocInfo =
vkTools::initializers::descriptorSetAllocateInfo(
descriptorPool,
&descriptorSetLayout,
1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSetPostCompute));
// Image descriptor for the color map texture
std::vector<VkDescriptorImageInfo> texDescriptors;
texDescriptors.push_back(vkTools::initializers::descriptorImageInfo(
textures.particle.sampler,
textures.particle.view,
VK_IMAGE_LAYOUT_GENERAL));
texDescriptors.push_back(vkTools::initializers::descriptorImageInfo(
textures.gradient.sampler,
textures.gradient.view,
VK_IMAGE_LAYOUT_GENERAL));
std::vector<VkWriteDescriptorSet> writeDescriptorSets;
// Binding 0 : Particle color map
writeDescriptorSets.push_back(vkTools::initializers::writeDescriptorSet(
descriptorSetPostCompute,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
0,
&texDescriptors[0]));
// Binding 1 : Particle gradient ramp
writeDescriptorSets.push_back(vkTools::initializers::writeDescriptorSet(
descriptorSetPostCompute,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1,
&texDescriptors[1]));
vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
} }
// Create a separate command buffer for compute commands // Create a separate command buffer for compute commands
@ -385,14 +434,11 @@ public:
VK_COMMAND_BUFFER_LEVEL_PRIMARY, VK_COMMAND_BUFFER_LEVEL_PRIMARY,
1); 1);
VkResult vkRes = vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &computeCmdBuffer); VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &computeCmdBuffer));
assert(!vkRes);
} }
void preparePipelines() void preparePipelines()
{ {
VkResult err;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
vkTools::initializers::pipelineInputAssemblyStateCreateInfo( vkTools::initializers::pipelineInputAssemblyStateCreateInfo(
VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
@ -475,8 +521,7 @@ public:
blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA; blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.postCompute); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.postCompute));
assert(!err);
} }
void prepareCompute() void prepareCompute()
@ -503,12 +548,7 @@ public:
setLayoutBindings.data(), setLayoutBindings.data(),
setLayoutBindings.size()); setLayoutBindings.size());
VkResult err = vkCreateDescriptorSetLayout( VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &computeDescriptorSetLayout));
device,
&descriptorLayout,
nullptr,
&computeDescriptorSetLayout);
assert(!err);
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
@ -516,12 +556,7 @@ public:
&computeDescriptorSetLayout, &computeDescriptorSetLayout,
1); 1);
err = vkCreatePipelineLayout( VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &computePipelineLayout));
device,
&pPipelineLayoutCreateInfo,
nullptr,
&computePipelineLayout);
assert(!err);
VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo allocInfo =
vkTools::initializers::descriptorSetAllocateInfo( vkTools::initializers::descriptorSetAllocateInfo(
@ -529,8 +564,7 @@ public:
&computeDescriptorSetLayout, &computeDescriptorSetLayout,
1); 1);
err = vkAllocateDescriptorSets(device, &allocInfo, &computeDescriptorSet); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &computeDescriptorSet));
assert(!err);
std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets =
{ {
@ -556,8 +590,7 @@ public:
computePipelineLayout, computePipelineLayout,
0); 0);
computePipelineCreateInfo.stage = loadShader(getAssetPath() + "shaders/computeparticles/particle.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); computePipelineCreateInfo.stage = loadShader(getAssetPath() + "shaders/computeparticles/particle.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
err = vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipelines.compute); VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipelines.compute));
assert(!err);
} }
// Prepare and initialize uniform buffer containing shader uniforms // Prepare and initialize uniform buffer containing shader uniforms
@ -580,7 +613,7 @@ public:
void updateUniformBuffers() void updateUniformBuffers()
{ {
computeUbo.deltaT = frameTimer * 4.0f; computeUbo.deltaT = frameTimer * 2.5f;
if (animate) if (animate)
{ {
computeUbo.destX = sin(glm::radians(timer*360.0)) * 0.75f; computeUbo.destX = sin(glm::radians(timer*360.0)) * 0.75f;
@ -627,6 +660,7 @@ public:
void prepare() void prepare()
{ {
VulkanExampleBase::prepare(); VulkanExampleBase::prepare();
loadTextures();
getComputeQueue(); getComputeQueue();
createComputeCommandBuffer(); createComputeCommandBuffer();
prepareStorageBuffers(); prepareStorageBuffers();
@ -634,8 +668,9 @@ public:
setupDescriptorSetLayout(); setupDescriptorSetLayout();
preparePipelines(); preparePipelines();
setupDescriptorPool(); setupDescriptorPool();
setupDescriptorSet();
prepareCompute(); prepareCompute();
buildCommandBuffers(); buildCommandBuffers();
buildComputeCommandBuffer(); buildComputeCommandBuffer();
prepared = true; prepared = true;
} }

View file

@ -7,6 +7,7 @@ struct Particle
{ {
vec2 pos; vec2 pos;
vec2 vel; vec2 vel;
vec4 gradientPos;
}; };
// Binding 0 : Position storage buffer // Binding 0 : Position storage buffer
@ -71,5 +72,8 @@ void main()
// Write back // Write back
particles[index].vel.xy = vVel; particles[index].vel.xy = vVel;
particles[index].gradientPos.x += 0.02 * ubo.deltaT;
if (particles[index].gradientPos.x > 1.0)
particles[index].gradientPos.x -= 1.0;
} }

View file

@ -3,11 +3,16 @@
#extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable #extension GL_ARB_shading_language_420pack : enable
layout (binding = 0) uniform sampler2D samplerColorMap;
layout (binding = 1) uniform sampler2D samplerGradientRamp;
layout (location = 0) in vec4 inColor; layout (location = 0) in vec4 inColor;
layout (location = 1) in float inGradientPos;
layout (location = 0) out vec4 outFragColor; layout (location = 0) out vec4 outFragColor;
void main () void main ()
{ {
outFragColor = inColor; vec3 color = texture(samplerGradientRamp, vec2(inGradientPos, 0.0)).rgb;
outFragColor.rgb = texture(samplerColorMap, gl_PointCoord).rgb * color;
} }

View file

@ -4,12 +4,21 @@
#extension GL_ARB_shading_language_420pack : enable #extension GL_ARB_shading_language_420pack : enable
layout (location = 0) in vec2 inPos; layout (location = 0) in vec2 inPos;
layout (location = 1) in vec4 inGradientPos;
layout (location = 0) out vec4 outColor; layout (location = 0) out vec4 outColor;
layout (location = 1) out float outGradientPos;
out gl_PerVertex
{
vec4 gl_Position;
float gl_PointSize;
};
void main () void main ()
{ {
gl_PointSize = 1.0; gl_PointSize = 8.0;
outColor = vec4(0.035); outColor = vec4(0.035);
outGradientPos = inGradientPos.x;
gl_Position = vec4(inPos.xy, 1.0, 1.0); gl_Position = vec4(inPos.xy, 1.0, 1.0);
} }

Binary file not shown.