/// <inheritdoc /> public unsafe override void PatchVertexBuffer(ParticleVertexBuilder vertexBuilder, Vector3 invViewX, Vector3 invViewY, ParticleSorter sorter) { // If you want, you can implement the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(vertexBuilder, invViewX, invViewY, sorter); // The UV Builder, if present, animates the basic (0, 0, 1, 1) uv coordinates of each billboard UVBuilder?.BuildUVCoordinates(vertexBuilder, sorter, vertexBuilder.DefaultTexCoords); vertexBuilder.RestartBuffer(); // If the particles have color field, the base class should have already passed the information if (HasColorField) { return; } // If the particles don't have color field but there is no color stream either we don't need to fill anything var colAttribute = vertexBuilder.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) { return; } // Since the particles don't have their own color field, set the default color to white var color = 0xFFFFFFFF; foreach (var particle in sorter) { vertexBuilder.SetAttributePerParticle(colAttribute, (IntPtr)(&color)); vertexBuilder.NextParticle(); } vertexBuilder.RestartBuffer(); }
/// <summary> /// Build the vertex buffer from particle data /// Should come before <see cref="KickVertexBuffer"/> /// </summary> /// <param name="device">The graphics device, used to rebuild vertex layouts and shaders if needed</param> /// <param name="invViewMatrix">The current camera's inverse view matrix</param> public void BuildVertexBuffer(CommandList commandList, ref Matrix invViewMatrix) { // Get camera-space X and Y axes for billboard expansion and sort the particles if needed var unitX = new Vector3(invViewMatrix.M11, invViewMatrix.M12, invViewMatrix.M13); var unitY = new Vector3(invViewMatrix.M21, invViewMatrix.M22, invViewMatrix.M23); depthSortVector = Vector3.Cross(unitX, unitY); ParticleSorter.Sort(); // If the particles are in world space they don't need to be fixed as their coordinates are already in world space // If the particles are in local space they need to be drawn in world space using the emitter's current location matrix var posIdentity = new Vector3(0, 0, 0); var rotIdentity = new Quaternion(0, 0, 0, 1); var scaleIdentity = 1f; if (simulationSpace == EmitterSimulationSpace.Local) { posIdentity = drawPosition; rotIdentity = drawRotation; scaleIdentity = drawScale; } VertexBuilder.MapBuffer(commandList); ShapeBuilder.BuildVertexBuffer(VertexBuilder, unitX, unitY, ref posIdentity, ref rotIdentity, scaleIdentity, ParticleSorter); VertexBuilder.RestartBuffer(); ShapeBuilder.SetRequiredQuads(ShapeBuilder.QuadsPerParticle, pool.LivingParticles, pool.ParticleCapacity); Material.PatchVertexBuffer(VertexBuilder, unitX, unitY, ParticleSorter); VertexBuilder.UnmapBuffer(commandList); }
/// <inheritdoc /> public override unsafe void PatchVertexBuffer(ParticleVertexBuilder vertexBuilder, Vector3 invViewX, Vector3 invViewY, ParticleSorter sorter) { // If you want, you can integrate the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(vertexBuilder, invViewX, invViewY, sorter); var colorField = sorter.GetField(ParticleFields.Color); if (!colorField.IsValid()) { return; } var colAttribute = vertexBuilder.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) { return; } foreach (var particle in sorter) { // Set the vertex color attribute to the particle's color field var color = (uint)(*(Color4 *)particle[colorField]).ToRgba(); vertexBuilder.SetAttributePerParticle(colAttribute, (IntPtr)(&color)); vertexBuilder.NextParticle(); } vertexBuilder.RestartBuffer(); }
public unsafe override void PatchVertexBuffer(ParticleVertexBuilder vertexBuilder, Vector3 invViewX, Vector3 invViewY, ParticleSorter sorter) { // If you want, you can integrate the base builder here and not call it. It should result in slight speed up base.PatchVertexBuffer(vertexBuilder, invViewX, invViewY, sorter); // Update the non-default coordinates first, because they update off the default ones UVBuilder1?.BuildUVCoordinates(vertexBuilder, sorter, texCoord1); // Update the default coordinates last UVBuilder0?.BuildUVCoordinates(vertexBuilder, sorter, texCoord0); // If the particles have color field, the base class should have already passed the information if (HasColorField) { return; } // If there is no color stream we don't need to fill anything var colAttribute = vertexBuilder.GetAccessor(VertexAttributes.Color); if (colAttribute.Size <= 0) { return; } // Since the particles don't have their own color field, set the default color to white var color = 0xFFFFFFFF; vertexBuilder.RestartBuffer(); foreach (var particle in sorter) { vertexBuilder.SetAttributePerParticle(colAttribute, (IntPtr)(&color)); vertexBuilder.NextParticle(); } vertexBuilder.RestartBuffer(); }
/// <inheritdoc /> public unsafe override void BuildUVCoordinates(ParticleVertexBuilder vertexBuilder, ParticleSorter sorter, AttributeDescription texCoordsDescription) { var lifeField = sorter.GetField(ParticleFields.RemainingLife); if (!lifeField.IsValid()) { return; } var texAttribute = vertexBuilder.GetAccessor(texCoordsDescription); if (texAttribute.Size == 0 && texAttribute.Offset == 0) { return; } var texDefault = vertexBuilder.GetAccessor(vertexBuilder.DefaultTexCoords); if (texDefault.Size == 0 && texDefault.Offset == 0) { return; } foreach (var particle in sorter) { var normalizedTimeline = 1f - *(float *)(particle[lifeField]); var spriteId = startingFrame + (int)(normalizedTimeline * animationSpeedOverLife); var uvTransform = new Vector4((spriteId % xDivisions) * xStep, (spriteId / yDivisions) * yStep, xStep, yStep); ParticleVertexBuilder.TransformAttributeDelegate <Vector2> transformCoords = (ref Vector2 value) => { value.X = uvTransform.X + uvTransform.Z * value.X; value.Y = uvTransform.Y + uvTransform.W * value.Y; }; vertexBuilder.TransformAttributePerParticle(texDefault, texAttribute, transformCoords); vertexBuilder.NextParticle(); } vertexBuilder.RestartBuffer(); }
/// <inheritdoc /> public unsafe override void BuildUVCoordinates(ParticleVertexBuilder vertexBuilder, ParticleSorter sorter, AttributeDescription texCoordsDescription) { var lifeField = sorter.GetField(ParticleFields.RemainingLife); if (!lifeField.IsValid()) { return; } var texAttribute = vertexBuilder.GetAccessor(texCoordsDescription); if (texAttribute.Size == 0 && texAttribute.Offset == 0) { return; } var texDefault = vertexBuilder.GetAccessor(vertexBuilder.DefaultTexCoords); if (texDefault.Size == 0 && texDefault.Offset == 0) { return; } foreach (var particle in sorter) { var normalizedTimeline = 1f - *(float *)(particle[lifeField]);; var uvTransform = Vector4.Lerp(StartFrame, EndFrame, normalizedTimeline); uvTransform.Z -= uvTransform.X; uvTransform.W -= uvTransform.Y; ParticleVertexBuilder.TransformAttributeDelegate <Vector2> transformCoords = (ref Vector2 value) => { value.X = uvTransform.X + uvTransform.Z * value.X; value.Y = uvTransform.Y + uvTransform.W * value.Y; }; vertexBuilder.TransformAttributePerParticle(texDefault, texAttribute, transformCoords); vertexBuilder.NextParticle(); } vertexBuilder.RestartBuffer(); }
/// <inheritdoc /> public unsafe override int BuildVertexBuffer(ParticleVertexBuilder vtxBuilder, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ParticleSorter sorter) { // Get all the required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) { return(0); } var sizeField = sorter.GetField(ParticleFields.Size); var orderField = sorter.GetField(ParticleFields.Order); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; vtxBuilder.RestartBuffer(); uint oldOrderValue = 0; foreach (var particle in sorter) { if (orderField.IsValid()) { var orderValue = (*((uint *)particle[orderField])); if ((orderValue >> 16) != (oldOrderValue >> 16)) { ribbonizer.Ribbonize(vtxBuilder, invViewX, invViewY, QuadsPerParticle); ribbonizer.RibbonSplit(); } oldOrderValue = orderValue; } var centralPos = particle.Get(positionField); var particleSize = sizeField.IsValid() ? particle.Get(sizeField) : 1f; if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; particleSize *= spaceScale; } ribbonizer.AddParticle(ref centralPos, particleSize); renderedParticles++; } ribbonizer.Ribbonize(vtxBuilder, invViewX, invViewY, QuadsPerParticle); var vtxPerShape = 4 * QuadsPerParticle; return(renderedParticles * vtxPerShape); }
/// <inheritdoc /> public unsafe override int BuildVertexBuffer(ParticleVertexBuilder vtxBuilder, Vector3 invViewX, Vector3 invViewY, ref Vector3 spaceTranslation, ref Quaternion spaceRotation, float spaceScale, ParticleSorter sorter) { // Get all the required particle fields var positionField = sorter.GetField(ParticleFields.Position); if (!positionField.IsValid()) return 0; var sizeField = sorter.GetField(ParticleFields.Size); var directionField = sorter.GetField(ParticleFields.Direction); // Check if the draw space is identity - in this case we don't need to transform the position, scale and rotation vectors var trsIdentity = (spaceScale == 1f); trsIdentity = trsIdentity && (spaceTranslation.Equals(new Vector3(0, 0, 0))); trsIdentity = trsIdentity && (spaceRotation.Equals(Quaternion.Identity)); var renderedParticles = 0; vtxBuilder.RestartBuffer(); uint oldOrderValue = 0; var orderField = sorter.GetField(ParticleFields.Order); foreach (var particle in sorter) { if (orderField.IsValid()) { var orderValue = (*((uint*)particle[orderField])); if ((orderValue >> 16) != (oldOrderValue >> 16)) { ribbonizer.Ribbonize(vtxBuilder, QuadsPerParticle); ribbonizer.RibbonSplit(); } oldOrderValue = orderValue; } var centralPos = particle.Get(positionField); var particleSize = sizeField.IsValid() ? particle.Get(sizeField) : 1f; var particleDirection = directionField.IsValid() ? particle.Get(directionField) * particleSize : new Vector3(0f, particleSize, 0f); if (!trsIdentity) { spaceRotation.Rotate(ref centralPos); centralPos = centralPos * spaceScale + spaceTranslation; // Direction spaceRotation.Rotate(ref particleDirection); particleDirection *= spaceScale; } ribbonizer.AddParticle(ref centralPos, ref particleDirection); renderedParticles++; } ribbonizer.Ribbonize(vtxBuilder, QuadsPerParticle); var vtxPerShape = 4 * QuadsPerParticle; return renderedParticles * vtxPerShape; }
/// <summary> /// Constructs the ribbon by outputting vertex stream based on the positions and sizes specified previously /// </summary> /// <param name="vtxBuilder">Target <see cref="ParticleVertexBuilder"/></param> to use /// <param name="invViewX">Unit vector X in clip space as calculated from the inverse view matrix</param> /// <param name="invViewY">Unit vector Y in clip space as calculated from the inverse view matrix</param> /// <param name="quadsPerParticle">The required number of quads per each particle</param> /// <param name="texPolicy">Texture coordinates stretching and stitching policy</param> /// <param name="texFactor">Texture coordinates stretching and stitching coefficient</param> /// <param name="uvRotate">Texture coordinates rotate and flip policy</param> public unsafe void Ribbonize(ParticleVertexBuilder vtxBuilder, Vector3 invViewX, Vector3 invViewY, int quadsPerParticle) { if (lastParticle <= 0) { return; } vtxBuilder.RestartBuffer(); var posAttribute = vtxBuilder.GetAccessor(VertexAttributes.Position); var texAttribute = vtxBuilder.GetAccessor(vtxBuilder.DefaultTexCoords); if (lastParticle <= sections) { // Optional - connect first particle to the origin/emitter // Draw a dummy quad for the first particle var particlePos = new Vector3(0, 0, 0); var uvCoord = new Vector2(0, 0); for (var particleIdx = 0; particleIdx < lastParticle; particleIdx++) { for (var vtxIdx = 0; vtxIdx < 4; vtxIdx++) { vtxBuilder.SetAttribute(posAttribute, (IntPtr)(&particlePos)); vtxBuilder.SetAttribute(texAttribute, (IntPtr)(&uvCoord)); vtxBuilder.NextVertex(); } } return; } if (sections > 1) { if (SmoothingPolicy == SmoothingPolicy.Best) { ExpandVertices_Circular(); } else // if (SmoothingPolicy == SmoothingPolicy.Fast) { ExpandVertices_CatmullRom(); } } vtxBuilder.SetVerticesPerSegment(quadsPerParticle * 6, quadsPerParticle * 4, quadsPerParticle * 2); // Step 1 - Determine the origin of the ribbon var invViewZ = Vector3.Cross(invViewX, invViewY); invViewZ.Normalize(); var axis0 = positions[0] - positions[1]; axis0.Normalize(); var oldPoint = positions[0]; var oldUnitX = GetWidthVector(sizes[0], ref invViewZ, ref axis0, ref axis0); // Step 2 - Draw each particle, connecting it to the previous (front) position var vCoordOld = 0f; for (int i = 0; i < lastParticle; i++) { var centralPos = positions[i]; var particleSize = sizes[i]; // Directions for smoothing var axis1 = (i + 1 < lastParticle) ? positions[i] - positions[i + 1] : positions[lastParticle - 2] - positions[lastParticle - 1]; axis1.Normalize(); var unitX = GetWidthVector(particleSize, ref invViewZ, ref axis0, ref axis1); axis0 = axis1; // Particle rotation - intentionally IGNORED for ribbon var particlePos = oldPoint - oldUnitX; var uvCoord = new Vector2(0, 0); var rotatedCoord = uvCoord; // Top Left - 0f 0f uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 0 : vCoordOld; vtxBuilder.SetAttribute(posAttribute, (IntPtr)(&particlePos)); rotatedCoord = UVRotate.GetCoords(uvCoord); vtxBuilder.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); vtxBuilder.NextVertex(); // Top Right - 1f 0f particlePos += oldUnitX * 2; vtxBuilder.SetAttribute(posAttribute, (IntPtr)(&particlePos)); uvCoord.X = 1; rotatedCoord = UVRotate.GetCoords(uvCoord); vtxBuilder.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); vtxBuilder.NextVertex(); // Move the position to the next particle in the ribbon particlePos += centralPos - oldPoint; particlePos += unitX - oldUnitX; vCoordOld = (TextureCoordinatePolicy == TextureCoordinatePolicy.Stretched) ? ((i + 1) / (float)(lastParticle) * TexCoordsFactor) : ((centralPos - oldPoint).Length() * TexCoordsFactor) + vCoordOld; // Bottom Left - 1f 1f uvCoord.Y = (TextureCoordinatePolicy == TextureCoordinatePolicy.AsIs) ? 1 : vCoordOld; vtxBuilder.SetAttribute(posAttribute, (IntPtr)(&particlePos)); rotatedCoord = UVRotate.GetCoords(uvCoord); vtxBuilder.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); vtxBuilder.NextVertex(); // Bottom Right - 0f 1f particlePos -= unitX * 2; vtxBuilder.SetAttribute(posAttribute, (IntPtr)(&particlePos)); uvCoord.X = 0; rotatedCoord = UVRotate.GetCoords(uvCoord); vtxBuilder.SetAttribute(texAttribute, (IntPtr)(&rotatedCoord)); vtxBuilder.NextVertex(); // Preserve the old attributes for the next cycle oldUnitX = unitX; oldPoint = centralPos; } }