I'm using MonoGame in C# and rendering billboarded Spine characters into a 3D world. Things are generally working, but I'm trying to do better handling of alpha and opaque objects throughout my code to alleviate some rendering bugs. To accomplish this, I'm trying to render my Spine characters using an effect with two passes, the first to render the opaque pixels and write to the Z buffer, and the second to draw transparent pixels and not update the Z buffer, but to do alpha blending.
If interested, I'm generally doing the same thing described here, albeit with newer versions of the code:
http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series4/Billboarding_renderstates.php
The issue I'm running into is that my Spine skeletons don't seem to be rendering my second pass How I'm currently trying to handle this is as follows:
C# code Snippet:
Matrix billboardWorld = Matrix.Invert( camera.viewMatrix );
billboardWorld.Translation = _WorldPosition;
skelRender.Effect.Parameters["World"].SetValue( worldScale * billboardWorld );
skelRender.Effect.Parameters["Projection"].SetValue( camera.Projection );
skelRender.Effect.Parameters["View"].SetValue( camera.viewMatrix );
skelRender.Begin();
skelRender.Draw( skel );
skelRender.End();
HLSL Snippets:
float4 BSPS(VSOutput input) : SV_TARGET
{
return float4(1, 1, 1, 1);
}
technique CharacterLit
{
pass P0
{
VertexShader = compile vs_4_0 VSQuad();
PixelShader = compile ps_4_0 PSQuad();
}
pass P1
{
VertexShader = compile vs_4_0 VSBS();
PixelShader = compile ps_4_0 BSPS();
}
}
The problem lies in the fact that when calling the SkeletonRenderer Begin / Draw / End calls, when it finally goes to render things via the End call, it does this:
public void End () {
foreach (EffectPass pass in effect.CurrentTechnique.Passes) {
pass.Apply();
batcher.Draw(device);
}
}
public void Draw (GraphicsDevice device) {
if (items.Count == 0) return;
int itemCount = items.Count;
int vertexCount = 0, triangleCount = 0;
for (int i = 0; i < itemCount; i++) {
MeshItem item = items[i];
vertexCount += item.vertexCount;
triangleCount += item.triangleCount;
}
EnsureCapacity(vertexCount, triangleCount);
vertexCount = 0;
triangleCount = 0;
Texture2D lastTexture = null;
for (int i = 0; i < itemCount; i++) {
MeshItem item = items[i];
int itemVertexCount = item.vertexCount;
if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) {
FlushVertexArray(device, vertexCount, triangleCount);
vertexCount = 0;
triangleCount = 0;
lastTexture = item.texture;
device.Textures[0] = lastTexture;
}
int[] itemTriangles = item.triangles;
int itemTriangleCount = item.triangleCount;
for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++)
triangles[t] = (short)(itemTriangles[ii] + vertexCount);
triangleCount += itemTriangleCount;
Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount);
vertexCount += itemVertexCount;
item.texture = null;
freeItems.Enqueue(item);
}
FlushVertexArray(device, vertexCount, triangleCount);
items.Clear();
}
The issue is that after rendering the first pass, it calls items.Clear() at the end of the MeshBatcher draw call. When it comes in a second time for the second pass, it hits the early out because items.Count is 0.
I'm curious if this is a bug, or if I'm just not doing it in the intended way? I believe I could fix this by calling the SkeletonRenderer Begin / Draw / End calls inside a loop that goes through each pass, but then it makes the code in SkeletonRenderer.End that loops through the passes seem pointless.