Skip to content

Commit

Permalink
add option to show particle effects in WorldViewer (#42)
Browse files Browse the repository at this point in the history
* add option to show particle effects in WorldViewer

* significant performance increases to Particle.Update()

* .

* 1000x performance increase to Particle.Draw via batching / instancing

* add missing call to init emitters

* fix issues with some emitters blinking out for 1 frame

* update version info
  • Loading branch information
gmriggs authored Jan 23, 2022
1 parent b280a8b commit 5a02cca
Show file tree
Hide file tree
Showing 27 changed files with 962 additions and 99 deletions.
7 changes: 6 additions & 1 deletion ACViewer/ACViewer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
<Compile Include="Enum\DatType.cs" />
<Compile Include="Enum\Facing.cs" />
<Compile Include="Enum\ModelType.cs" />
<Compile Include="Enum\ProfilerSection.cs" />
<Compile Include="Enum\ViewMode.cs" />
<Compile Include="Extensions\PositionExtensions.cs" />
<Compile Include="Extensions\TextureExtensions.cs" />
Expand Down Expand Up @@ -240,6 +241,7 @@
<Compile Include="Model\Face.cs" />
<Compile Include="Model\LandVertex.cs" />
<Compile Include="Model\Mesh.cs" />
<Compile Include="Model\ParticleDeclaration.cs" />
<Compile Include="Model\VertexInstanceEnv.cs" />
<Compile Include="Model\VertexPositionNormalTextures.cs" />
<Compile Include="ParticleViewer.cs" />
Expand All @@ -262,7 +264,10 @@
<Compile Include="Physics\Common\TextureMergeInfo.cs" />
<Compile Include="Physics\Common\TMTerrainDesc.cs" />
<Compile Include="Render\Buffer.cs" />
<Compile Include="Render\DrawCount.cs" />
<Compile Include="Render\ParticleBatch.cs" />
<Compile Include="Render\ParticleBatchDraw.cs" />
<Compile Include="Render\ParticleTextureFormat.cs" />
<Compile Include="Render\PerfTimer.cs" />
<Compile Include="Render\EffectParameters.cs" />
<Compile Include="Render\TextureSet.cs" />
<Compile Include="Render\InstanceBatch.cs" />
Expand Down
106 changes: 105 additions & 1 deletion ACViewer/Content/texture.fx
Original file line number Diff line number Diff line change
Expand Up @@ -726,4 +726,108 @@ technique LandscapeSinglePass
VertexShader = compile vs_4_0 LandscapeSinglePassVS();
PixelShader = compile ps_4_0 LandscapeSinglePassPS();
}
}
}

//------- Technique: ParticleInstance --------

struct VertexParticleBase
{
float4 Position : SV_POSITION;
float2 TextureCoord : TEXCOORD0;
};

struct VertexParticleInstance
{
float4 Position : POSITION1;
float3 BillboardTexture : TEXCOORD1;
float3 ScaleOpacityActive : POSITION2;
};

struct ParticleVertexShaderOutput
{
float4 Position : SV_POSITION;
float3 TextureCoordIdx : TEXCOORD0;
float2 OpacityActive : POSITION1;
};

ParticleVertexShaderOutput ParticleInstanceVS(VertexParticleBase base, VertexParticleInstance instance)
{
ParticleVertexShaderOutput output = (ParticleVertexShaderOutput)0;

// xWorld?
float3 iPos = base.Position + instance.Position;

float3 center = iPos;
float3 eyeVector = center - xCamPos;

float3 sideVector = cross(eyeVector, xCamUp);
sideVector = normalize(sideVector);
float3 upVector = cross(sideVector, eyeVector);
upVector = normalize(upVector);

float3 finalPosition = center;
finalPosition += (base.TextureCoord.x - 0.5f) * sideVector * 0.5f * instance.BillboardTexture.x * instance.ScaleOpacityActive.x;
finalPosition += (0.5f - base.TextureCoord.y) * upVector * 0.5f * instance.BillboardTexture.y * instance.ScaleOpacityActive.x;

float4 finalPosition4 = float4(finalPosition, 1);

float4x4 preViewProjection = mul(xView, xProjection);

output.Position = mul(finalPosition4, preViewProjection);

// pixel shader needs:
// transformed position
// base texture u/v
// texture idx
// opacity
// active
output.TextureCoordIdx = float3(base.TextureCoord.xy, instance.BillboardTexture.z);

output.OpacityActive = float2(instance.ScaleOpacityActive.yz);

return output;
}

float4 ParticleInstancePS(ParticleVertexShaderOutput input) : COLOR
{
float4 color = xTextures.Sample(TextureSampler, input.TextureCoordIdx);

color.a *= input.OpacityActive.x;

// only output completely opaque pixels
// also discard inactive particles
clip(color.a < 1.0f || input.OpacityActive.y == 0 ? -1 : 1);

return color;
}

float4 ParticleInstanceTransPS(ParticleVertexShaderOutput input) : COLOR
{
float4 color = xTextures.Sample(TextureSampler, input.TextureCoordIdx);

color.a *= input.OpacityActive.x;

// only output semi-transparent pixels
// also discard inactive particles
clip(color.a < 1.0f && color.a >= 0.03125f && input.OpacityActive.y > 0 ? 1 : -1);

return color;
}

technique ParticleInstance
{
pass Pass0
{
ZWriteEnable = true;

VertexShader = compile vs_4_0 ParticleInstanceVS();
PixelShader = compile ps_4_0 ParticleInstancePS();
}
pass Pass1
{
ZWriteEnable = false;

VertexShader = compile vs_4_0 ParticleInstanceVS();
PixelShader = compile ps_4_0 ParticleInstanceTransPS();
}
}
Binary file modified ACViewer/Content/texture.mgfxo
Binary file not shown.
9 changes: 9 additions & 0 deletions ACViewer/Enum/ProfilerSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace ACViewer.Enum
{
public enum ProfilerSection
{
Draw,
ParticleUpdate,
ParticleDraw
}
}
34 changes: 34 additions & 0 deletions ACViewer/Model/ParticleDeclaration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Numerics;

using Microsoft.Xna.Framework.Graphics;

namespace ACViewer
{
public struct ParticleDeclaration : IVertexType
{
// per-particle data
// align to 16?

public Vector3 Position;

public Vector3 BillboardTexture; // pointSpriteSizeX = u, pointSpriteSizeY = v, textureIdx = w
public Vector3 ScaleOpacityActive; // scale = x, opacity = y, active = z

public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
(
// VertexElementUsage.PointSize for base?
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 1),
new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.TextureCoordinate, 1),
new VertexElement(sizeof(float) * 6, VertexElementFormat.Vector3, VertexElementUsage.Position, 2)
);

public ParticleDeclaration(int textureIdx, Vector2 dims)
{
Position = Vector3.Zero;
BillboardTexture = new Vector3(dims.X, dims.Y, textureIdx);
ScaleOpacityActive = Vector3.Zero;
}

VertexDeclaration IVertexType.VertexDeclaration => VertexDeclaration;
}
}
2 changes: 1 addition & 1 deletion ACViewer/Model/SetupInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public void Draw(int polyIdx = -1)

var indexCnt = polygon.Indices.Count;
GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indexCnt / 3);
DrawCount.NumSetup++;
//PerfTimer.NumSetup++;
tris += indexCnt / 3;
}

Expand Down
2 changes: 1 addition & 1 deletion ACViewer/Physics/Common/Position.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void Init()

public void Clear()
{
ObjCellID = 0;
//ObjCellID = 0;
Frame.Origin = Vector3.Zero;
Frame.Orientation = Quaternion.Identity;
}
Expand Down
5 changes: 0 additions & 5 deletions ACViewer/Physics/Extensions/Vector3Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ namespace ACE.Server.Physics.Extensions
{
public static class Vector3Extensions
{
public static Vector3 Copy(this Vector3 v)
{
return new Vector3(v.X, v.Y, v.Z);
}

public static float Dot2D(this Vector3 a, Vector3 b)
{
return a.X * b.X + a.Y * b.Y;
Expand Down
26 changes: 13 additions & 13 deletions ACViewer/Physics/Particles/Particle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ public bool Init(ParticleEmitterInfo info, PhysicsObj parent, int partIdx, AFram
break;
case ParticleType.ParabolicLVGA:
A = StartFrame.LocalToGlobalVec(a);
B = b.Copy();
B = b;
break;
case ParticleType.ParabolicLVGAGR:
case ParticleType.Swarm:
A = StartFrame.LocalToGlobalVec(a);
B = b.Copy();
C = c.Copy();
B = b;
C = c;
break;
case ParticleType.Explode:
A = a.Copy();
B = b.Copy();
A = a;
B = b;

var ra = Common.Random.RollDice(-(float)Math.PI, (float)Math.PI);
var po = Common.Random.RollDice(-(float)Math.PI, (float)Math.PI);
Expand All @@ -77,8 +77,8 @@ public bool Init(ParticleEmitterInfo info, PhysicsObj parent, int partIdx, AFram

break;
case ParticleType.Implode:
A = a.Copy();
B = b.Copy();
A = a;
B = b;
Offset *= c;
C = Offset;
break;
Expand All @@ -90,18 +90,18 @@ public bool Init(ParticleEmitterInfo info, PhysicsObj parent, int partIdx, AFram
C = StartFrame.LocalToGlobalVec(c);
break;
case ParticleType.ParabolicGVGA:
B = b.Copy();
B = b;
break;
case ParticleType.ParabolicGVGAGR:
C = c.Copy();
C = c;
break;
case ParticleType.GlobalVelocity:
A = a.Copy();
A = a;
break;
default:
A = a.Copy();
B = b.Copy();
C = c.Copy();
A = a;
B = b;
C = c;
break;
}

Expand Down
25 changes: 22 additions & 3 deletions ACViewer/Physics/Particles/ParticleEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
using ACE.Entity.Enum;
using ACE.Server.Physics.Animation;
using ACE.Server.Physics.Common;
using ACE.Server.Physics.Extensions;

using ACViewer.Render;

namespace ACE.Server.Physics
{
Expand All @@ -29,6 +30,10 @@ public class ParticleEmitter
public bool Stopped;
public double LastUpdateTime;

// custom for ACViewer renderer
public ParticleBatchDraw RenderBatch;
public int BatchIdx;

public ParticleEmitter(PhysicsObj parent)
{
Parent = parent;
Expand Down Expand Up @@ -62,6 +67,11 @@ public bool KillParticle(int i)

Parts[i] = null;
NumParticles--;

// acviewer custom
if (RenderBatch != null)
RenderBatch.DestroyParticle(this, i);

return true;
}

Expand All @@ -84,7 +94,7 @@ public void RecordParticleEmission()
NumParticles++;
TotalEmitted++;

LastEmitOffset = PhysicsObj.Position.Frame.Origin.Copy();
LastEmitOffset = PhysicsObj.Position.Frame.Origin;
LastEmitTime = PhysicsTimer.CurrentTime;
}

Expand Down Expand Up @@ -116,7 +126,7 @@ public bool SetInfo(ParticleEmitterInfo info)
return false;
}
PhysicsObj = PhysicsObj.makeParticleObject(Info.MaxParticles, Info.SortingSphere);
LastEmitOffset = PhysicsObj.Position.Frame.Origin.Copy();
LastEmitOffset = PhysicsObj.Position.Frame.Origin;
Parts = PhysicsObj.PartArray.Parts;
PartStorage = new PhysicsPart[Info.MaxParticles];
for (var i = 0; i < Info.MaxParticles; i++)
Expand Down Expand Up @@ -153,6 +163,10 @@ public void EmitParticle()

Particles[nextIdx].Init(Info, Parent, PartIndex, ParentOffset, Parts[nextIdx], randomOffset, firstParticle, randomA, randomB, randomC);

// acviewer custom
if (RenderBatch != null)
RenderBatch.SpawnParticle(this, nextIdx);

PhysicsObj.AddPartToShadowCells(Parts[nextIdx]);

RecordParticleEmission();
Expand Down Expand Up @@ -240,6 +254,11 @@ public bool UpdateParticles()

var firstParticle = Info.TotalParticles == 0 && Info.TotalSeconds == 0.0f;
Particles[i].Update(Info.ParticleType, firstParticle, part, frame);

// acviewer custom
if (RenderBatch != null)
RenderBatch.UpdateParticle(this, i);

KillParticle(i);
}
}
Expand Down
8 changes: 4 additions & 4 deletions ACViewer/Physics/Particles/ParticleEmitterInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ public ParticleEmitterInfo(DatLoader.FileTypes.ParticleEmitterInfo info)
LifeSpanRand = info.LifespanRand;
LifeSpan = info.Lifespan;
//SortingSphere?
OffsetDir = info.OffsetDir.Copy();
OffsetDir = info.OffsetDir;
MinOffset = info.MinOffset;
MaxOffset = info.MaxOffset;
A = info.A.Copy();
B = info.B.Copy();
C = info.C.Copy();
A = info.A;
B = info.B;
C = info.C;
MinA = info.MinA;
MaxA = info.MaxA;
MinB = info.MinB;
Expand Down
4 changes: 2 additions & 2 deletions ACViewer/Physics/PhysicsObj.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3914,7 +3914,7 @@ public void remove_unsticky_listener(Action listener)

public bool IsGrounded { get => TransientState.HasFlag(TransientStateFlags.OnWalkable) && CachedVelocity.Equals(Vector3.Zero); }

public bool Equals(PhysicsObj obj)
/*public bool Equals(PhysicsObj obj)
{
if (obj == null) return false;
return ID == obj.ID;
Expand All @@ -3923,6 +3923,6 @@ public bool Equals(PhysicsObj obj)
public override int GetHashCode()
{
return ID.GetHashCode();
}
}*/
}
}
Loading

0 comments on commit 5a02cca

Please sign in to comment.