private void UpdateVertices(ParticleBag particleBag, Matrix4x4 modelViewMatrix) { var particles = particleBag.LiveParticles; // Create billboarding rotation (always facing camera) Matrix4x4.Decompose(modelViewMatrix, out _, out Quaternion modelViewRotation, out _); modelViewRotation = Quaternion.Inverse(modelViewRotation); var billboardMatrix = Matrix4x4.CreateFromQuaternion(modelViewRotation); // Update vertex buffer EnsureSpaceForVertices(particleBag.Count * 4); for (int i = 0; i < particleBag.Count; ++i) { // Positions var modelMatrix = orientationType == 0 ? particles[i].GetRotationMatrix() * billboardMatrix * particles[i].GetTransformationMatrix() : particles[i].GetRotationMatrix() * particles[i].GetTransformationMatrix(); var tl = Vector4.Transform(new Vector4(-1, -1, 0, 1), modelMatrix); var bl = Vector4.Transform(new Vector4(-1, 1, 0, 1), modelMatrix); var br = Vector4.Transform(new Vector4(1, 1, 0, 1), modelMatrix); var tr = Vector4.Transform(new Vector4(1, -1, 0, 1), modelMatrix); int quadStart = i * VertexSize * 4; rawVertices[quadStart + 0] = tl.X; rawVertices[quadStart + 1] = tl.Y; rawVertices[quadStart + 2] = tl.Z; rawVertices[quadStart + (VertexSize * 1) + 0] = bl.X; rawVertices[quadStart + (VertexSize * 1) + 1] = bl.Y; rawVertices[quadStart + (VertexSize * 1) + 2] = bl.Z; rawVertices[quadStart + (VertexSize * 2) + 0] = br.X; rawVertices[quadStart + (VertexSize * 2) + 1] = br.Y; rawVertices[quadStart + (VertexSize * 2) + 2] = br.Z; rawVertices[quadStart + (VertexSize * 3) + 0] = tr.X; rawVertices[quadStart + (VertexSize * 3) + 1] = tr.Y; rawVertices[quadStart + (VertexSize * 3) + 2] = tr.Z; // Colors for (int j = 0; j < 4; ++j) { rawVertices[quadStart + (VertexSize * j) + 3] = particles[i].Color.X; rawVertices[quadStart + (VertexSize * j) + 4] = particles[i].Color.Y; rawVertices[quadStart + (VertexSize * j) + 5] = particles[i].Color.Z; rawVertices[quadStart + (VertexSize * j) + 6] = particles[i].Alpha; } // UVs if (spriteSheetData != null && spriteSheetData.Sequences.Length > 0 && spriteSheetData.Sequences[0].Frames.Length > 0) { var sequence = spriteSheetData.Sequences[particles[i].Sequence % spriteSheetData.Sequences.Length]; var particleTime = particles[i].ConstantLifetime - particles[i].Lifetime; var frame = particleTime * sequence.FramesPerSecond * animationRate; var currentFrame = sequence.Frames[(int)Math.Floor(frame) % sequence.Frames.Length]; // Lerp frame coords and size var subFrameTime = frame % 1.0f; var offset = (currentFrame.StartMins * (1 - subFrameTime)) + (currentFrame.EndMins * subFrameTime); var scale = ((currentFrame.StartMaxs - currentFrame.StartMins) * (1 - subFrameTime)) + ((currentFrame.EndMaxs - currentFrame.EndMins) * subFrameTime); rawVertices[quadStart + (VertexSize * 0) + 7] = offset.X + (scale.X * 0); rawVertices[quadStart + (VertexSize * 0) + 8] = offset.Y + (scale.Y * 1); rawVertices[quadStart + (VertexSize * 1) + 7] = offset.X + (scale.X * 0); rawVertices[quadStart + (VertexSize * 1) + 8] = offset.Y + (scale.Y * 0); rawVertices[quadStart + (VertexSize * 2) + 7] = offset.X + (scale.X * 1); rawVertices[quadStart + (VertexSize * 2) + 8] = offset.Y + (scale.Y * 0); rawVertices[quadStart + (VertexSize * 3) + 7] = offset.X + (scale.X * 1); rawVertices[quadStart + (VertexSize * 3) + 8] = offset.Y + (scale.Y * 1); } else { rawVertices[quadStart + (VertexSize * 0) + 7] = 0; rawVertices[quadStart + (VertexSize * 0) + 8] = 1; rawVertices[quadStart + (VertexSize * 1) + 7] = 0; rawVertices[quadStart + (VertexSize * 1) + 8] = 0; rawVertices[quadStart + (VertexSize * 2) + 7] = 1; rawVertices[quadStart + (VertexSize * 2) + 8] = 0; rawVertices[quadStart + (VertexSize * 3) + 7] = 1; rawVertices[quadStart + (VertexSize * 3) + 8] = 1; } } GL.BindBuffer(BufferTarget.ArrayBuffer, vertexBufferHandle); GL.BufferData(BufferTarget.ArrayBuffer, particleBag.Count * VertexSize * 4 * sizeof(float), rawVertices, BufferUsageHint.DynamicDraw); }
public void Render(ParticleBag particleBag, Matrix4x4 viewProjectionMatrix, Matrix4x4 modelViewMatrix) { if (particleBag.Count == 0) { return; } // Update vertex buffer UpdateVertices(particleBag, modelViewMatrix); // Draw it GL.Enable(EnableCap.Blend); GL.UseProgram(shader.Program); if (additive) { GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); } else { GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); } GL.BindVertexArray(quadVao); GL.EnableVertexAttribArray(0); GL.ActiveTexture(TextureUnit.Texture0); GL.BindTexture(TextureTarget.Texture2D, glTexture); GL.BindBuffer(BufferTarget.ArrayBuffer, vertexBufferHandle); GL.Uniform1(shader.GetUniformLocation("uTexture"), 0); // set texture unit 0 as uTexture uniform var otkProjection = viewProjectionMatrix.ToOpenTK(); GL.UniformMatrix4(shader.GetUniformLocation("uProjectionViewMatrix"), false, ref otkProjection); // TODO: This formula is a guess but still seems too bright compared to valve particles GL.Uniform1(shader.GetUniformLocation("uOverbrightFactor"), overbrightFactor); GL.Disable(EnableCap.CullFace); GL.Enable(EnableCap.DepthTest); GL.DepthMask(false); GL.BindBuffer(BufferTarget.ElementArrayBuffer, quadIndices.GLHandle); GL.DrawElements(BeginMode.Triangles, particleBag.Count * 6, DrawElementsType.UnsignedShort, 0); GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); GL.BindBuffer(BufferTarget.ArrayBuffer, 0); GL.Enable(EnableCap.CullFace); GL.Disable(EnableCap.DepthTest); GL.DepthMask(true); GL.BindVertexArray(0); GL.UseProgram(0); if (additive) { GL.BlendEquation(BlendEquationMode.FuncAdd); } GL.Disable(EnableCap.Blend); }
public void Render(ParticleBag particleBag, Matrix4x4 viewProjectionMatrix, Matrix4x4 modelViewMatrix) { var particles = particleBag.LiveParticles; GL.Enable(EnableCap.Blend); GL.UseProgram(shader.Program); if (additive) { GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); } else { GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); } GL.BindVertexArray(quadVao); GL.EnableVertexAttribArray(0); GL.ActiveTexture(TextureUnit.Texture0); GL.BindTexture(TextureTarget.Texture2D, glTexture); GL.Uniform1(shader.GetUniformLocation("uTexture"), 0); // set texture unit 0 as uTexture uniform var otkProjection = viewProjectionMatrix.ToOpenTK(); GL.UniformMatrix4(shader.GetUniformLocation("uProjectionViewMatrix"), false, ref otkProjection); // TODO: This formula is a guess but still seems too bright compared to valve particles GL.Uniform1(shader.GetUniformLocation("uOverbrightFactor"), overbrightFactor); var modelMatrixLocation = shader.GetUniformLocation("uModelMatrix"); var colorLocation = shader.GetUniformLocation("uColor"); var alphaLocation = shader.GetUniformLocation("uAlpha"); var uvOffsetLocation = shader.GetUniformLocation("uUvOffset"); var uvScaleLocation = shader.GetUniformLocation("uUvScale"); // Create billboarding rotation (always facing camera) Matrix4x4.Decompose(modelViewMatrix, out _, out Quaternion modelViewRotation, out _); modelViewRotation = Quaternion.Inverse(modelViewRotation); var billboardMatrix = Matrix4x4.CreateFromQuaternion(modelViewRotation); for (int i = 0; i < particles.Length; ++i) { var position = new Vector3(particles[i].Position.X, particles[i].Position.Y, particles[i].Position.Z); var previousPosition = new Vector3(particles[i].PositionPrevious.X, particles[i].PositionPrevious.Y, particles[i].PositionPrevious.Z); var difference = previousPosition - position; var direction = Vector3.Normalize(difference); var midPoint = position + (0.5f * difference); // Trail width = radius // Trail length = distance between current and previous times trail length divided by 2 (because the base particle is 2 wide) var length = Math.Min(maxLength, particles[i].TrailLength * difference.Length() / 2f); var t = 1 - (particles[i].Lifetime / particles[i].ConstantLifetime); var animatedLength = t >= lengthFadeInTime ? length : t * length / lengthFadeInTime; var scaleMatrix = Matrix4x4.CreateScale(particles[i].Radius, animatedLength, 1); // Center the particle at the midpoint between the two points var translationMatrix = Matrix4x4.CreateTranslation(Vector3.UnitY * animatedLength); // Calculate rotation matrix var axis = Vector3.Normalize(Vector3.Cross(Vector3.UnitY, direction)); var angle = (float)Math.Acos(direction.Y); var rotationMatrix = Matrix4x4.CreateFromAxisAngle(axis, angle); var modelMatrix = orientationType == 0 ? Matrix4x4.Multiply(scaleMatrix, Matrix4x4.Multiply(translationMatrix, rotationMatrix)) : particles[i].GetTransformationMatrix(); // Position/Radius uniform var otkModelMatrix = modelMatrix.ToOpenTK(); GL.UniformMatrix4(modelMatrixLocation, false, ref otkModelMatrix); if (spriteSheetData != null && spriteSheetData.Sequences.Length > 0 && spriteSheetData.Sequences[0].Frames.Length > 0) { var sequence = spriteSheetData.Sequences[0]; var particleTime = particles[i].ConstantLifetime - particles[i].Lifetime; var frame = particleTime * sequence.FramesPerSecond * animationRate; var currentFrame = sequence.Frames[(int)Math.Floor(frame) % sequence.Frames.Length]; // Lerp frame coords and size var subFrameTime = frame % 1.0f; var offset = (currentFrame.StartMins * (1 - subFrameTime)) + (currentFrame.EndMins * subFrameTime); var scale = ((currentFrame.StartMaxs - currentFrame.StartMins) * (1 - subFrameTime)) + ((currentFrame.EndMaxs - currentFrame.EndMins) * subFrameTime); GL.Uniform2(uvOffsetLocation, offset.X, offset.Y); GL.Uniform2(uvScaleLocation, scale.X * finalTextureScaleU, scale.Y * finalTextureScaleV); } else { GL.Uniform2(uvOffsetLocation, 1f, 1f); GL.Uniform2(uvScaleLocation, finalTextureScaleU, finalTextureScaleV); } // Color uniform GL.Uniform3(colorLocation, particles[i].Color.X, particles[i].Color.Y, particles[i].Color.Z); GL.Uniform1(alphaLocation, particles[i].Alpha * particles[i].AlphaAlternate); GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); } GL.BindVertexArray(0); GL.UseProgram(0); if (additive) { GL.BlendEquation(BlendEquationMode.FuncAdd); } GL.Disable(EnableCap.Blend); }