Hi,
Ok I just succeed to implement shadow volume in my scene... altho it is so damn slow... around 1-2 fps when there is shadow on all my objects. What I don't understand is that I have taken the same algorithm coming from the dx9 sdk and the dx9 sdk sample is running at 20+fps while the shadow it has to render has more vertices/triangles than my the shadow in my scene. Also I'm not even rebuilding the shadow volume everytime the object rotates cuz I just do a rotate world and the lights rotate with the object... and in the dx9 sdk they are rebuilding it everytime the object is rotated and its not even causing any lag... All the lag in my app seems to be caused by the DrawPrimitive line in my render shadow function, if I comment out that line my app runs at around 40-45 fps... and all that line has to render is around 500 to 1k triangles... so why does my framerate drops of 40fps just to render 1k triangles?
Here's the code I'm using:
Class Shadows, this is a class that contains all the shadow objects in my scene and all it does is rendering the array of shadows.
public class Shadows
{
private ArrayList shadows = new ArrayList();
public Shadows()
{
//do nothing
}
public ArrayList GetShadows
{
get
{
return this.shadows;
}
}
public void Render()
{
if( this.shadows.Count > 0 )
{
this.RenderShadow();
this.DrawShadow();
}
}
private void RenderShadow()
{
// Disable z-buffer writes (note: z-testing still occurs), and enable the
// stencil-buffer
Graphics.device.RenderState.ZBufferWriteEnable = false;
Graphics.device.RenderState.StencilEnable = true;
// Dont bother with interpolating color
Graphics.device.RenderState.ShadeMode = ShadeMode.Flat;
// Set up stencil compare fuction, reference value, and masks.
// Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true.
// Note: since we set up the stencil-test to always pass, the STENCILFAIL
// renderstate is really not needed.
Graphics.device.RenderState.StencilFunction = Compare.Always;
Graphics.device.RenderState.StencilZBufferFail = StencilOperation.Keep;
Graphics.device.RenderState.StencilFail = StencilOperation.Keep;
// If ztest passes, inc/decrement stencil buffer value
Graphics.device.RenderState.ReferenceStencil = 0x1;
Graphics.device.RenderState.StencilMask = unchecked((int)0xffffffff);
Graphics.device.RenderState.StencilWriteMask = unchecked((int)0xffffffff);
Graphics.device.RenderState.StencilPass = StencilOperation.Increment;
// Make sure that no pixels get drawn to the frame buffer
Graphics.device.RenderState.AlphaBlendEnable = true;
Graphics.device.RenderState.SourceBlend = Blend.Zero;
Graphics.device.RenderState.DestinationBlend = Blend.One;
// With 2-sided stencil, we can avoid rendering twice:
Graphics.device.RenderState.TwoSidedStencilMode = true;
Graphics.device.RenderState.CounterClockwiseStencilFunction = Compare.Always;
Graphics.device.RenderState.CounterClockwiseStencilZBufferFail = StencilOperation.Keep;
Graphics.device.RenderState.CounterClockwiseStencilFail = StencilOperation.Keep;
Graphics.device.RenderState.CounterClockwiseStencilPass = StencilOperation.Decrement;
Graphics.device.RenderState.CullMode = Cull.None;
// Draw both sides of shadow volume in stencil/z only
//Graphics.device.Transform.World = objectMatrix;
Graphics.device.VertexFormat = VertexFormats.Position;
for( int i = 0; i < this.shadows.Count; i++ )
{
Graphics.device.SetStreamSource( 0, ((Shadow)this.shadows[i]).ShadowVertices, 0 );
//if i comment the next line i get 40-45 fps... else i'm only getting 1-2 fps
Graphics.device.DrawPrimitives(PrimitiveType.TriangleList, 0, ((Shadow)this.shadows[i]).NumShadowVertices / 3 );
}
Graphics.device.RenderState.TwoSidedStencilMode = false;
// Restore render states
Graphics.device.RenderState.ShadeMode = ShadeMode.Gouraud;
Graphics.device.RenderState.CullMode = Cull.CounterClockwise;
Graphics.device.RenderState.ZBufferWriteEnable = true;
Graphics.device.RenderState.StencilEnable = true;
Graphics.device.RenderState.AlphaBlendEnable = false;
}
private void DrawShadow()
{
// Set renderstates (disable z-buffering, enable stencil, disable fog, and
// turn on alphablending)
Graphics.device.RenderState.ZBufferEnable = false;
Graphics.device.RenderState.StencilEnable = true;
Graphics.device.RenderState.FogEnable = false;
Graphics.device.RenderState.AlphaBlendEnable = true;
Graphics.device.RenderState.SourceBlend = Blend.SourceAlpha;
Graphics.device.RenderState.DestinationBlend = Blend.InvSourceAlpha;
Graphics.device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
Graphics.device.TextureState[0].ColorArgument2 = TextureArgument.Diffuse;
Graphics.device.TextureState[0].ColorOperation = TextureOperation.Modulate;
Graphics.device.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;
Graphics.device.TextureState[0].AlphaArgument2 = TextureArgument.Diffuse;
Graphics.device.TextureState[0].AlphaOperation = TextureOperation.Modulate;
// Only write where stencil val >= 1 (count indicates # of shadows that
// overlap that pixel)
Graphics.device.RenderState.ReferenceStencil = 0x1;
Graphics.device.RenderState.StencilFunction = Compare.LessEqual;
Graphics.device.RenderState.StencilPass = StencilOperation.Keep;
// Draw a big, gray square
Graphics.device.VertexFormat = VertexFormats.Transformed | VertexFormats.Diffuse;
Graphics.device.SetTexture(0 , null);
for( int i = 0; i < this.shadows.Count; i++ )
{
Graphics.device.SetStreamSource(0, ((Shadow)this.shadows[i]).ShadowVertexBuffer, 0);
Graphics.device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
}
// Restore render states
Graphics.device.RenderState.ZBufferEnable = true;
Graphics.device.RenderState.StencilEnable = false;
Graphics.device.RenderState.FogEnable = true;
Graphics.device.RenderState.AlphaBlendEnable = false;
}
}
Class Shadow, this is a class that builds the vertex buffers of a shadow volume.
public class Shadow
{
private VertexBuffer shadowVertexBuffer = null;
private ArrayList shadowVerts;
private VertexBuffer shadowVertices = null;
private int numShadowVertices = 0;
public Shadow()
{
}
public int NumShadowVertices
{
get
{
return this.numShadowVertices;
}
}
public VertexBuffer ShadowVertices
{
get
{
return this.shadowVertices;
}
}
public VertexBuffer ShadowVertexBuffer
{
get
{
return this.shadowVertexBuffer;
}
}
public void Create( VertexBuffer vb, int[] ib, PrimitiveType type )
{
// Note: the MeshVertex format depends on the FVF of the mesh
Vector3 light = new Vector3();
Matrix m = Matrix.Invert(Graphics.device.Transform.World);
float x = Graphics.device.Lights[0].Position.X;
float y = Graphics.device.Lights[0].Position.Y;
float z = Graphics.device.Lights[0].Position.Z;
light.X = x*m.M11 + y*m.M21 + z*m.M31 + m.M41;
light.Y = x*m.M12 + y*m.M22 + z*m.M32 + m.M42;
light.Z = x*m.M13 + y*m.M23 + z*m.M33 + m.M43;
CustomVertex.PositionNormalTextured[] tempVertices = null;
int[] index = null;
int[] edges = null;
int numFaces;
if( type == PrimitiveType.TriangleList )
{
numFaces = ib.Length / 3;
}
else
{
numFaces = ib.Length - 2;
}
int numVerts = vb.Description.Size / 32;
int numEdges = 0;
// Allocate a temporary edge list
edges = new int[numFaces * 6];
// Lock the geometry buffers
tempVertices = (CustomVertex.PositionNormalTextured[])vb.Lock( 0, 0 );
// For each face
for(int i=0; i<numFaces; i++)
{
int face0;
int face1;
int face2;
if( type == PrimitiveType.TriangleList )
{
face0 = ib[3*i+0];
face1 = ib[3*i+1];
face2 = ib[3*i+2];
}
else
{
face0 = ib[i+0];
face1 = ib[i+1];
face2 = ib[i+2];
}
Vector3 v0 = new Vector3( tempVertices[face0].X, tempVertices[face0].Y, tempVertices[face0].Z );
Vector3 v1 = new Vector3( tempVertices[face1].X, tempVertices[face1].Y, tempVertices[face1].Z );
Vector3 v2 = new Vector3( tempVertices[face2].X, tempVertices[face2].Y, tempVertices[face2].Z );
// Transform vertices or transform light?
Vector3 vCross1 = v2 - v1;
Vector3 vCross2 = v1 - v0;
Vector3 vNormal = Vector3.Cross(vCross1, vCross2);
if (Vector3.Dot(vNormal, light) >= 0.0f)
{
AddEdge(edges, ref numEdges, face0, face1);
AddEdge(edges, ref numEdges, face1, face2);
AddEdge(edges, ref numEdges, face2, face0);
}
}
shadowVerts = new ArrayList();
for (int i=0; i<numEdges; i++)
{
Vector3 v1 = new Vector3( tempVertices[edges[2*i+0]].X, tempVertices[edges[2*i+0]].Y, tempVertices[edges[2*i+0]].Z );
Vector3 v2 = new Vector3( tempVertices[edges[2*i+1]].X, tempVertices[edges[2*i+1]].Y, tempVertices[edges[2*i+1]].Z );
Vector3 v3 = v1 - light*10;
Vector3 v4 = v2 - light*10;
// Add a quad (two triangles) to the vertex list
shadowVerts.Add( v1 );
shadowVerts.Add( v2 );
shadowVerts.Add( v3 );
shadowVerts.Add( v2 );
shadowVerts.Add( v4 );
shadowVerts.Add( v3 );
numShadowVertices += 6;
}
// Unlock the geometry buffers
vb.Unlock();
if ((shadowVertexBuffer == null) || (shadowVertexBuffer.Disposed))
{
// Create a big square for rendering the mirror, we don't need to recreate this every time, if the VertexBuffer
// is destroyed (by a call to Reset for example), it will automatically be recreated and the 'Created' event fired.
shadowVertexBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), 4, Graphics.device, Usage.WriteOnly, VertexFormats.Transformed | VertexFormats.Diffuse, Pool.Default);
CustomVertex.TransformedColored[] verts = (CustomVertex.TransformedColored[])shadowVertexBuffer.Lock(0, 0);
float xpos = (float)Graphics.device.PresentationParameters.BackBufferWidth;
float ypos = (float)Graphics.device.PresentationParameters.BackBufferHeight;
verts[0] = new CustomVertex.TransformedColored( new Vector4(0, ypos, 0.0f, 1.0f), Color.FromArgb( 70, Color.Black ).ToArgb() );
verts[1] = new CustomVertex.TransformedColored( new Vector4(0, 0, 0.0f, 1.0f), Color.FromArgb( 70, Color.Black ).ToArgb() );
verts[2] = new CustomVertex.TransformedColored( new Vector4(xpos, ypos, 0.0f, 1.0f), Color.FromArgb( 70, Color.Black ).ToArgb() );
verts[3] = new CustomVertex.TransformedColored( new Vector4(xpos, 0, 0.0f, 1.0f), Color.FromArgb( 70, Color.Black ).ToArgb() );
shadowVertexBuffer.Unlock();
}
this.shadowVertices = new VertexBuffer( typeof( Vector3 ), shadowVerts.Count, Graphics.device, 0, VertexFormats.Position, Pool.Default );
this.shadowVertices.SetData( shadowVerts.ToArray( typeof( Vector3 ) ), 0, 0 );
}
private void AddEdge(int[] edges, ref int numEdges, int v0, int v1)
{
// Remove interior edges (which appear in the list twice)
for(int i=0; i < numEdges; i++)
{
if ((edges[2*i+0] == v0 && edges[2*i+1] == v1) ||
(edges[2*i+0] == v1 && edges[2*i+1] == v0))
{
if (numEdges > 1)
{
edges[2*i+0] = edges[2*(numEdges-1)+0];
edges[2*i+1] = edges[2*(numEdges-1)+1];
}
numEdges--;
return;
}
}
edges[2*numEdges+0] = v0;
edges[2*numEdges+1] = v1;
numEdges++;
}
}
Anyone has any clue why it's so slow?
Stéphane Ross
Game Gurus Entertainment
[edited by - sross on September 1, 2003 3:20:01 PM]
[edited by - sross on September 1, 2003 3:20:55 PM]