Visual Update for computeparticles

Instead of using a small amount of large textured particles, use a large
amount of small monochrome particles.
Which uses a new vulkanexamplebase functionality of creating and
updating a only device visible buffer via a temporary staging buffer.
This commit is contained in:
Voultapher 2016-04-21 11:21:48 +02:00
parent eb428db92d
commit 5d7014b221
9 changed files with 203 additions and 209 deletions

View file

@ -283,6 +283,97 @@ VkPipelineShaderStageCreateInfo VulkanExampleBase::loadShader(std::string fileNa
return shaderStage;
}
VkBool32 VulkanExampleBase::createDeviceBuffer(
const VkBufferUsageFlags usage,
const VkDeviceSize size,
VkBuffer& buffer,
VkDeviceMemory& memory,
VkDescriptorBufferInfo& descriptor)
{
VkMemoryRequirements memReqs;
VkMemoryAllocateInfo memAlloc = vkTools::initializers::memoryAllocateInfo();
VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo(usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, size);
VkResult err = vkCreateBuffer(device, &bufferCreateInfo, nullptr, &buffer);
assert(!err);
vkGetBufferMemoryRequirements(device, buffer, &memReqs);
memAlloc.allocationSize = memReqs.size;
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex);
err = vkAllocateMemory(device, &memAlloc, nullptr, &memory);
assert(!err);
err = vkBindBufferMemory(device, buffer, memory, 0);
assert(!err);
descriptor.offset = 0;
descriptor.buffer = buffer;
descriptor.range = size;
return VK_TRUE;
}
VkBool32 VulkanExampleBase::updateDeviceBuffer(
const VkDeviceSize size,
VkBuffer& deviceBuffer,
void* data)
{
//todo check that size is not larger than memory size
// create staging buffer and copy data to it
VkMemoryRequirements memReqs;
VkMemoryAllocateInfo memAlloc = vkTools::initializers::memoryAllocateInfo();
VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, size);
VkBuffer stagingBuffer;
VkDeviceMemory stagingMemory;
VkResult err = vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer);
assert(!err);
vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
memAlloc.allocationSize = memReqs.size;
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex);
err = vkAllocateMemory(device, &memAlloc, nullptr, &stagingMemory);
assert(!err);
if (data != nullptr)
{
void* mapped;
err = vkMapMemory(device, stagingMemory, 0, size, 0, &mapped);
assert(!err);
memcpy(mapped, data, size);
vkUnmapMemory(device, stagingMemory);
}
err = vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0);
assert(!err);
// create cmdbuffer to copy staging buffer to device local buffer
createSetupCommandBuffer();
VkBufferCopy copyRegion = {};
copyRegion.size = size;
vkCmdCopyBuffer(
setupCmdBuffer,
stagingBuffer,
deviceBuffer,
1,
&copyRegion);
flushSetupCommandBuffer();
// free staging memory
vkDestroyBuffer(device, stagingBuffer, nullptr);
vkFreeMemory(device, stagingMemory, nullptr);
return VK_TRUE;
}
VkBool32 VulkanExampleBase::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, void * data, VkBuffer * buffer, VkDeviceMemory * memory)
{
VkMemoryRequirements memReqs;

View file

@ -271,6 +271,20 @@ public:
// Load a SPIR-V shader
VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage);
// create device local buffer and use staging to access
VkBool32 createDeviceBuffer(
const VkBufferUsageFlags usage,
const VkDeviceSize size,
VkBuffer& buffer,
VkDeviceMemory& memory,
VkDescriptorBufferInfo& descriptor);
// update loacal buffer memory by creating a host visible staging buffer
VkBool32 updateDeviceBuffer(
const VkDeviceSize size,
VkBuffer& deviceBuffer,
void* data);
// Create a buffer, fill it with data (if != NULL) and bind buffer memory
VkBool32 createBuffer(
VkBufferUsageFlags usageFlags,

View file

@ -11,6 +11,7 @@
#include <string.h>
#include <assert.h>
#include <vector>
#include <random>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
@ -22,15 +23,15 @@
#define VERTEX_BUFFER_BIND_ID 0
#define ENABLE_VALIDATION false
#define PARTICLE_COUNT 8 * 1024
#define PARTICLE_COUNT 3000 * 1024
class VulkanExample : public VulkanExampleBase
{
private:
vkTools::VulkanTexture textureColorMap;
public:
float timer = 0.0f;
float animStart = 50.0f;
float timer = 0.f;
float animStart = 20.0f;
bool animate = true;
struct {
@ -68,13 +69,11 @@ public:
} uniformData;
struct Particle {
glm::vec4 pos;
glm::vec4 col;
glm::vec4 vel;
glm::vec2 pos;
glm::vec2 vel;
};
VkPipelineLayout pipelineLayout;
VkDescriptorSet descriptorSetPostCompute;
VkDescriptorSetLayout descriptorSetLayout;
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
@ -103,17 +102,6 @@ public:
vkDestroyPipelineLayout(device, computePipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, computeDescriptorSetLayout, nullptr);
vkDestroyPipeline(device, pipelines.compute, nullptr);
textureLoader->destroyTexture(textureColorMap);
}
void loadTextures()
{
textureLoader->loadTexture(
getAssetPath() + "textures/particle01_rgba.ktx",
VK_FORMAT_R8G8B8A8_UNORM,
&textureColorMap,
false);
}
void buildCommandBuffers()
@ -151,7 +139,7 @@ public:
assert(!err);
// 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
// in the vertex shader
VkBufferMemoryBarrier bufferBarrier = vkTools::initializers::bufferMemoryBarrier();
// Source access : Compute shader buffer write
@ -191,7 +179,6 @@ public:
);
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSetPostCompute, 0, NULL);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.postCompute);
VkDeviceSize offsets[1] = { 0 };
@ -243,9 +230,6 @@ public:
err = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete);
assert(!err);
err = vkQueueWaitIdle(queue);
assert(!err);
// Compute
VkSubmitInfo computeSubmitInfo = vkTools::initializers::submitInfo();
computeSubmitInfo.commandBufferCount = 1;
@ -254,6 +238,9 @@ public:
err = vkQueueSubmit(computeQueue, 1, &computeSubmitInfo, VK_NULL_HANDLE);
assert(!err);
err = vkQueueWaitIdle(queue);
assert(!err);
err = vkQueueWaitIdle(computeQueue);
assert(!err);
}
@ -262,93 +249,25 @@ public:
// vertex positions and velocities
void prepareStorageBuffers()
{
float destPosX = 0.0f;
float destPosY = 0.0f;
std::mt19937 rGenerator;
std::uniform_real_distribution<float> rDistribution(-1.f, 1.f);
// Initial particle positions
std::vector<Particle> particleBuffer;
for (int i = 0; i < PARTICLE_COUNT; ++i)
std::vector<Particle> particleBuffer(PARTICLE_COUNT);
for (auto& element : particleBuffer)
{
// Position
float aspectRatio = (float)height / (float)width;
float rndVal = (float)rand() / (float)(RAND_MAX / (360.0f * 3.14f * 2.0f));
float rndRad = (float)rand() / (float)(RAND_MAX) * 0.5f;
Particle p;
p.pos = glm::vec4(
destPosX + cos(rndVal) * rndRad * aspectRatio,
destPosY + sin(rndVal) * rndRad,
0.0f,
1.0f);
p.col = glm::vec4(
(float)(rand() % 255) / 255.0f,
(float)(rand() % 255) / 255.0f,
(float)(rand() % 255) / 255.0f,
1.0f);
p.vel = glm::vec4(0.0f);
particleBuffer.push_back(p);
element.pos = glm::vec2(rDistribution(rGenerator), rDistribution(rGenerator));
element.vel = glm::vec2(0.f);
}
// Buffer size is the same for all storage buffers
uint32_t storageBufferSize = particleBuffer.size() * sizeof(Particle);
VkMemoryAllocateInfo memAlloc = vkTools::initializers::memoryAllocateInfo();
VkMemoryRequirements memReqs;
createDeviceBuffer(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, storageBufferSize, computeStorageBuffer.buffer,
computeStorageBuffer.memory, computeStorageBuffer.descriptor);
VkResult err;
void *data;
struct StagingBuffer {
VkDeviceMemory memory;
VkBuffer buffer;
} stagingBuffer;
// Allocate and fill host-visible staging storage buffer object
// Allocate and fill storage buffer object
VkBufferCreateInfo vBufferInfo =
vkTools::initializers::bufferCreateInfo(
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
storageBufferSize);
vkTools::checkResult(vkCreateBuffer(device, &vBufferInfo, nullptr, &stagingBuffer.buffer));
vkGetBufferMemoryRequirements(device, stagingBuffer.buffer, &memReqs);
memAlloc.allocationSize = memReqs.size;
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex);
vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffer.memory));
vkTools::checkResult(vkMapMemory(device, stagingBuffer.memory, 0, storageBufferSize, 0, &data));
memcpy(data, particleBuffer.data(), storageBufferSize);
vkUnmapMemory(device, stagingBuffer.memory);
vkTools::checkResult(vkBindBufferMemory(device, stagingBuffer.buffer, stagingBuffer.memory, 0));
// Allocate device local storage buffer ojbect
vBufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
vkTools::checkResult(vkCreateBuffer(device, &vBufferInfo, nullptr, &computeStorageBuffer.buffer));
vkGetBufferMemoryRequirements(device, computeStorageBuffer.buffer, &memReqs);
memAlloc.allocationSize = memReqs.size;
getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex);
vkTools::checkResult(vkAllocateMemory(device, &memAlloc, nullptr, &computeStorageBuffer.memory));
vkTools::checkResult(vkBindBufferMemory(device, computeStorageBuffer.buffer, computeStorageBuffer.memory, 0));
// Copy from host to device
createSetupCommandBuffer();
VkBufferCopy copyRegion = {};
copyRegion.size = storageBufferSize;
vkCmdCopyBuffer(
setupCmdBuffer,
stagingBuffer.buffer,
computeStorageBuffer.buffer,
1,
&copyRegion);
flushSetupCommandBuffer();
// Destroy staging buffer
vkDestroyBuffer(device, stagingBuffer.buffer, nullptr);
vkFreeMemory(device, stagingBuffer.memory, nullptr);
computeStorageBuffer.descriptor.buffer = computeStorageBuffer.buffer;
computeStorageBuffer.descriptor.offset = 0;
computeStorageBuffer.descriptor.range = storageBufferSize;
updateDeviceBuffer(storageBufferSize, computeStorageBuffer.buffer, particleBuffer.data());
// Binding description
vertices.bindingDescriptions.resize(1);
@ -360,7 +279,7 @@ public:
// Attribute descriptions
// Describes memory layout and shader positions
vertices.attributeDescriptions.resize(2);
vertices.attributeDescriptions.resize(1);
// Location 0 : Position
vertices.attributeDescriptions[0] =
vkTools::initializers::vertexInputAttributeDescription(
@ -368,13 +287,6 @@ public:
0,
VK_FORMAT_R32G32B32A32_SFLOAT,
0);
// Location 1 : Color
vertices.attributeDescriptions[1] =
vkTools::initializers::vertexInputAttributeDescription(
VERTEX_BUFFER_BIND_ID,
1,
VK_FORMAT_R32G32B32A32_SFLOAT,
sizeof(float) * 4);
// Assign to vertex buffer
vertices.inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo();
@ -389,15 +301,14 @@ public:
std::vector<VkDescriptorPoolSize> poolSizes =
{
vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1),
vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1)
};
VkDescriptorPoolCreateInfo descriptorPoolInfo =
vkTools::initializers::descriptorPoolCreateInfo(
poolSizes.size(),
poolSizes.data(),
3);
2);
VkResult vkRes = vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool);
assert(!vkRes);
@ -405,63 +316,26 @@ public:
void setupDescriptorSetLayout()
{
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
{
// Binding 0 : Fragment shader image sampler
vkTools::initializers::descriptorSetLayoutBinding(
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_SHADER_STAGE_FRAGMENT_BIT,
0)
};
VkDescriptorSetLayoutCreateInfo descriptorLayout =
vkTools::initializers::descriptorSetLayoutCreateInfo(
setLayoutBindings.data(),
setLayoutBindings.size());
VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo;
descriptorLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorLayoutInfo.pNext = NULL;
descriptorLayoutInfo.flags = 0;
descriptorLayoutInfo.bindingCount = 0;
descriptorLayoutInfo.pBindings = nullptr;
VkResult err = vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout);
VkResult err = vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayout);
assert(!err);
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
vkTools::initializers::pipelineLayoutCreateInfo(
&descriptorSetLayout,
1);
0);
err = vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout);
assert(!err);
}
void setupDescriptorSet()
{
VkDescriptorSetAllocateInfo allocInfo =
vkTools::initializers::descriptorSetAllocateInfo(
descriptorPool,
&descriptorSetLayout,
1);
VkResult vkRes = vkAllocateDescriptorSets(device, &allocInfo, &descriptorSetPostCompute);
assert(!vkRes);
// Image descriptor for the color map texture
VkDescriptorImageInfo texDescriptor =
vkTools::initializers::descriptorImageInfo(
textureColorMap.sampler,
textureColorMap.view,
VK_IMAGE_LAYOUT_GENERAL);
std::vector<VkWriteDescriptorSet> writeDescriptorSets =
{
// Binding 1 : Fragment shader image sampler
vkTools::initializers::writeDescriptorSet(
descriptorSetPostCompute,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
0,
&texDescriptor)
};
vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
}
// Create a separate command buffer for compute commands
void createComputeCommandBuffer()
{
@ -504,16 +378,16 @@ public:
VkPipelineDepthStencilStateCreateInfo depthStencilState =
vkTools::initializers::pipelineDepthStencilStateCreateInfo(
VK_TRUE,
VK_TRUE,
VK_COMPARE_OP_LESS_OR_EQUAL);
VK_FALSE,
VK_FALSE,
VK_COMPARE_OP_ALWAYS);
VkPipelineViewportStateCreateInfo viewportState =
vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState =
vkTools::initializers::pipelineMultisampleStateCreateInfo(
VK_SAMPLE_COUNT_1_BIT,
VK_SAMPLE_COUNT_4_BIT,
0);
std::vector<VkDynamicState> dynamicStateEnables = {
@ -650,27 +524,34 @@ public:
void prepareUniformBuffers()
{
// Compute shader uniform buffer block
createBuffer(
createDeviceBuffer(
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
sizeof(computeUbo),
&computeUbo,
&uniformData.computeShader.ubo.buffer,
&uniformData.computeShader.ubo.memory,
&uniformData.computeShader.ubo.descriptor);
uniformData.computeShader.ubo.buffer,
uniformData.computeShader.ubo.memory,
uniformData.computeShader.ubo.descriptor);
updateDeviceBuffer(sizeof(computeUbo), uniformData.computeShader.ubo.buffer, &computeUbo);
updateUniformBuffers();
}
void updateUniformBuffers()
{
computeUbo.deltaT = frameTimer * 5.0f;
computeUbo.destX = sin(glm::radians(timer*360.0)) * 0.75f;
computeUbo.destY = 0;
uint8_t *pData;
VkResult err = vkMapMemory(device, uniformData.computeShader.ubo.memory, 0, sizeof(computeUbo), 0, (void **)&pData);
assert(!err);
memcpy(pData, &computeUbo, sizeof(computeUbo));
vkUnmapMemory(device, uniformData.computeShader.ubo.memory);
computeUbo.deltaT = frameTimer * 4.0f;
if (animate) // tmp
{
computeUbo.destX = sin(glm::radians(timer*360.0)) * 0.75f;
computeUbo.destY = 0.f;
}
else
{
float normalizedMx = (mousePos.x - static_cast<float>(width / 2)) / static_cast<float>(width / 2);
float normalizedMy = (mousePos.y - static_cast<float>(height / 2)) / static_cast<float>(height / 2);
computeUbo.destX = normalizedMx;
computeUbo.destY = normalizedMy;
}
updateDeviceBuffer(sizeof(computeUbo), uniformData.computeShader.ubo.buffer, &computeUbo);
}
// Find and create a compute capable device queue
@ -693,6 +574,8 @@ public:
assert(queueIndex < queueCount);
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.pNext = NULL;
queueCreateInfo.queueFamilyIndex = queueIndex;
queueCreateInfo.queueCount = 1;
vkGetDeviceQueue(device, queueIndex, 0, &computeQueue);
@ -701,7 +584,6 @@ public:
void prepare()
{
VulkanExampleBase::prepare();
loadTextures();
getComputeQueue();
createComputeCommandBuffer();
prepareStorageBuffers();
@ -709,7 +591,6 @@ public:
setupDescriptorSetLayout();
preparePipelines();
setupDescriptorPool();
setupDescriptorSet();
prepareCompute();
buildCommandBuffers();
buildComputeCommandBuffer();
@ -723,16 +604,18 @@ public:
vkDeviceWaitIdle(device);
draw();
vkDeviceWaitIdle(device);
if (animStart > 0.0f)
if (animate)
{
animStart -= frameTimer * 5.0f;
}
if ((animate) & (animStart <= 0.0f))
{
timer += frameTimer * 0.1f;
if (timer > 1.0)
if (animStart > 0.0f)
{
timer -= 1.0f;
animStart -= frameTimer * 5.0f;
}
else if (animStart <= 0.0f)
{
timer += frameTimer * 0.04f;
if (timer > 1.f)
timer = 0.f;
}
}
updateUniformBuffers();

View file

@ -5,9 +5,8 @@
struct Particle
{
vec4 pos;
vec4 col;
vec4 vel;
vec2 pos;
vec2 vel;
};
// Binding 0 : Position storage buffer
@ -26,14 +25,21 @@ layout (binding = 1) uniform UBO
int particleCount;
} ubo;
vec3 attraction(vec3 pos, vec3 attractPos)
vec2 attraction(vec2 pos, vec2 attractPos)
{
vec3 delta = attractPos - pos;
vec2 delta = attractPos - pos;
const float damp = 0.5;
float dDampedDot = dot(delta, delta) + damp;
float invDist = 1.0f / sqrt(dDampedDot);
float invDistCubed = invDist*invDist*invDist;
return delta * invDistCubed * 0.00035;
return delta * invDistCubed * 0.0035;
}
vec2 repulsion(vec2 pos, vec2 attractPos)
{
vec2 delta = attractPos - pos;
float targetDistance = sqrt(dot(delta, delta));
return delta * (1.0 / (targetDistance * targetDistance * targetDistance)) * -0.000035;
}
void main()
@ -45,22 +51,25 @@ void main()
return;
// Read position and velocity
vec3 vPos = particles[index].pos.xyz;
vec3 vVel = particles[index].vel.xyz;
vec3 destPos = vec3(ubo.destX, ubo.destY, 0.0);
vec2 vVel = particles[index].vel.xy;
vec2 vPos = particles[index].pos.xy;
vec2 destPos = vec2(ubo.destX, ubo.destY);
vec2 delta = destPos - vPos;
float targetDistance = sqrt(dot(delta, delta));
vVel += repulsion(vPos, destPos.xy) * 0.05;
// Calculate new velocity depending on attraction point
vVel += attraction(vPos, destPos.xyz);
// Move by velocity
vPos += vVel * ubo.deltaT;
if ((vPos.x < -1.0) || (vPos.x > 1.0))
vVel.x -= vVel.x;
// collide with boundary
if ((vPos.x < -1.0) || (vPos.x > 1.0) || (vPos.y < -1.0) || (vPos.y > 1.0))
vVel = (-vVel * 0.1) + attraction(vPos, destPos) * 12;
else
particles[index].pos.xy = vPos;
// Write back
particles[index].pos.xyz = vPos;
particles[index].vel.xyz = vVel;
particles[index].vel.xy = vVel;
}

View file

@ -3,13 +3,11 @@
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout (binding = 0) uniform sampler2D sColorMap;
layout (location = 0) in vec4 inColor;
layout (location = 0) out vec4 outFragColor;
void main ()
{
outFragColor = texture(sColorMap, gl_PointCoord) * inColor;
outFragColor = inColor;
}

View file

@ -4,13 +4,12 @@
#extension GL_ARB_shading_language_420pack : enable
layout (location = 0) in vec4 inPos;
layout (location = 1) in vec4 inColor;
layout (location = 0) out vec4 outColor;
void main ()
{
gl_PointSize = 32.0;
outColor = inColor;
gl_Position = vec4(inPos.xyz, 1.0);
gl_PointSize = 1.0;
outColor = vec4(0.035);
gl_Position = vec4(inPos.xy, 1.0, 1.0);
}