Hello Guest

Author Topic: Best practises for combining sprites  (Read 8670 times)

Orion

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 12
    • View Profile
Best practises for combining sprites
« on: February 15, 2013, 03:39:01 pm »
Hello everyone,

in my game I animate the characters by stacking multiple sprites on top of each other, that are then animated together. They share the same sprite collection, but unfortunately, each part seems to cause one draw call. So at the current point every single character is 11 draw calls.
Now I actually want to add more parts to the characters, although this time, parts that stick with others (e.g. gun attachments, stuff attached to the head, etc.). At this point already, with 50 characters on screen, I experience heavy performance drops (not surprisingly).

What would be the best practise to do this, while keeping the draw calls low? How would you tackle this?

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Best practises for combining sprites
« Reply #1 on: February 15, 2013, 04:07:06 pm »
If everything is from one sprite collection, then you should only get 1 draw call for all of them. Unless you've applied a transform.scale somewhere, or something else is breaking batching...

Orion

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Best practises for combining sprites
« Reply #2 on: February 15, 2013, 05:46:24 pm »
That is odd. Scales are 1,1,1 everywhere, except of "Target Ortho Size" of the collection, where everything is scaled up to match the camera. Z positions of the parts are not identical though, to render them in the correct order.

I do use a custom surface shader to render them. Although if I use one of the shaders that comes with tk2d, it doesn't seem to make a difference. What else could cause the batching to break?

Also, if it says "Draw Calls: 168   Saved by batching: 103", does that mean, there are actually only 65 drawcalls, or that there would be 271, if batching didn't work?

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Best practises for combining sprites
« Reply #3 on: February 15, 2013, 06:35:21 pm »
It means there are would be 271 without batching.
You only have one sprite collection for all the sprites in the scene? Is it getting mixed up with something else in the scene?

What about the Z of each character, are they the same?

Do you use lights?


Orion

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Best practises for combining sprites
« Reply #4 on: February 16, 2013, 10:15:26 am »
Actually, for testing, I have one collection per character and three characters close to each other. I was planning to use one collection per bodypart, since they would be interchangeable - but from the sound of it, this may not be such a good idea. The z is different per character and body part . I have one directional light and the camera uses an orthographic-like custom camera.

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Best practises for combining sprites
« Reply #5 on: February 16, 2013, 01:16:45 pm »
A directional light for the characters is a bit of a waste - they are all planar and will only get illuminated from one direction. You can reduce that to an ambient color. i.e. Dot(LightDirection, SpriteDirection) * LightColor = constant for all sprites. Unless of course your sprites rotate...

Using a sprite collection per part would be bad - it would break batching horribly.

Do you use atlas spanning? that will add more materials to your sprite collection, and will likely bloat batch count. It wouldn't be too different to the above actually.

You shouldn't have so many draw calls, unless something is breaking batching somewhere. Just need to track it down.

Orion

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Best practises for combining sprites
« Reply #6 on: February 16, 2013, 03:00:31 pm »
Okay, next chance I'll check if putting all three characters in the same collection will help.

The light issue is interesting, though. I animate the color of the light and would like the characters to match the surroundings. If I change global ambient lighting, it will cause the surrounding models to be lit wrong. Should I write an extra shader just for having no light on the characters? I do already use a custom shader for transparent stuff.

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Best practises for combining sprites
« Reply #7 on: February 16, 2013, 04:41:08 pm »
Yeah - create a shader with a global constant, use Shader.SetGlobalColor to set the constant. That's it really. Just don't call it the same name as the normal directional / ambient color and you're sorted.

Orion

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Best practises for combining sprites
« Reply #8 on: February 16, 2013, 04:56:15 pm »
Ah, nice! I didn't know that!

Orion

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Best practises for combining sprites
« Reply #9 on: February 17, 2013, 11:17:43 pm »
Okay, I tried implementing that, but realized that I'm at a complete loss on how to remove the lighting.
I just recently figured out how surface shaders work, after experimenting a lot with strumpy's shader editor and reading all the confusing material on the net.

I realize I could probably write my current surface shader as a fragment shader, but I can't get my head around how, and if it will have any benefit at all. The shader is currently combining the light from the scene, with a special screen overlay light texture (that is larger than the screen, to avoid certain artifacts). It also uses a "Specularity" texture, that affects how much of the light texture it picks up.

Code: [Select]
Shader "MapLitAlpha"
{
Properties
{
_Color("Main Color", Color) = (1,1,1,1)
_MainTex("Base (RGB) Tranparency (A)", 2D) = "gray" {}
_Specularity("Specularity (RGB) Emission (A)", 2D) = "gray" {}
_Light("Lighting (RGB)", 2D) = "gray" {}
_Cutoff("Alpha cutoff", Float) = 0.5
_LightColor("Fake Lighting", Color) = (0.5,0.5,0.5,0.5)
}

SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="TransparentCutout"
}


Cull Back
ZWrite On
ZTest LEqual
ColorMask RGBA
Fog{Mode Off}


CGPROGRAM
#pragma surface surf SimpleLambert  nolightmap noforwardadd approxview halfasview alpha decal:blend vertex:vert
//#pragma target 2.0

// Warning: Modified: don't use Strumpy anymore!

float4 _Color;
sampler2D _MainTex;
sampler2D _Specularity;
sampler2D _Light;
float _Cutoff;

struct EditorSurfaceOutput {
half3 Albedo;
half3 Normal;
half3 Emission;
half Specular;
half Alpha;
//half3 Gloss;
//half4 Custom;
};

half4 LightingSimpleLambert (EditorSurfaceOutput s, half3 lightDir, half atten) {
half NdotL = dot (s.Normal, lightDir);
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten * 2);
c.a = s.Alpha;
return c;
}

struct Input {
float2 uv_MainTex;
float2 uv_Specularity;
float4 screenPos;
float depth;
};

void vert (inout appdata_full v, out Input o)
{
v.tangent = float4(1,0,0,0);
// Front is darker than back
float depthFactor = 0.4 - ( _WorldSpaceCameraPos.z - mul(_Object2World, v.vertex).z )*0.2;
o.depth = depthFactor;
// Init to fix dx11 errors
o.screenPos = float4(0,0,0,0);
o.uv_Specularity = float2(0,0);
o.uv_MainTex = float2(0,0);
}

void surf (Input IN, inout EditorSurfaceOutput o)
{
float4 Tex2D0 = tex2D(_MainTex,(IN.uv_MainTex.xyxy).xy);
float4 Albedo = Tex2D0 * _Color;
float4 Tex2D2 = tex2D(_Specularity,(IN.uv_Specularity.xyxy).xy);
// Calculate sizes and margin
float2 Size = float2(2048, 2048);
float2 Margin = (Size - _ScreenParams.xy)*0.5;
Margin /= Size;
// Map back to texture
float2 SamplePos = lerp(Margin, 1-Margin, IN.screenPos.xy);
float4 Tex2D1 = tex2D(_Light,SamplePos);
float4 Emission = Tex2D1 * 8;
Emission *= Tex2D2.a;
Emission *= Albedo;
o.Albedo = Albedo;
o.Emission = IN.depth*Emission;
o.Normal = float3(0.0,0.0,1.0);
o.Alpha = Tex2D0.a;
}
ENDCG
}
Fallback "Transparent/Cutout/Diffuse"
}

On another note -
I've put all characters into the same collection now, but it doesn't seem to make any difference for the draw calls. It still adds 11 draw calls per character, and also 11 "saved by batching". I don't use atlas spanning.

Maybe I'm doing something wrong with the shader, but if I switch between my shader and the tk2d CutoutVertexColor (and have my whole screen filled with characters = ~5000 draw calls), I only get a difference of 2 draw calls for the entire scene, and the difference in performance in the profiler is immeasurably small, so I don't think it really matters, or is the bottle neck here.
If I switch to BlendVertexColor, the DrawCalls go down to almost nothing (lets say 1 or 2 draw calls for everything). But I have the impression that the order of the sprites is then not guaranteed. Do you have more suggestions, how I could lessen the decrease of performance?

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Best practises for combining sprites
« Reply #10 on: February 18, 2013, 10:14:39 am »
The order of sprites with blendvertexcolor is back to front. You're almost guaranteed that. If your sprites are at the same Z value, then the draw order is undefined.

I think you'll need to rewrite this as a fragment shader to get out of doing lighting, I don't think there is a way to turn it off using a surface shader.

Orion

  • 2D Toolkit
  • Newbie
  • *
  • Posts: 12
    • View Profile
Re: Best practises for combining sprites
« Reply #11 on: February 18, 2013, 05:46:36 pm »
It took me all day, but I finally managed to convert the surface shader to a fragment shader that does exactly the same as before. The drawcalls do indeed batch now, so that's really great. Thanks a lot for the help!

unikronsoftware

  • Administrator
  • Hero Member
  • *****
  • Posts: 9709
    • View Profile
Re: Best practises for combining sprites
« Reply #12 on: February 18, 2013, 11:15:39 pm »
Glad its sorted. I suspect its something to do with the surface shader + lighting breaking batching in the previous iteration, but its hard to tell. As a bonus, your shader is probably a lot more efficient too :)