I have an issue, and i need help to resolve it.
I am try to colorize some parts of sprite dynamically by custom shader and additional textures as masks. It is not a problem and i solve it easy.
Screen for better understanding:
This is real implementation and color blending for thirs image was realized by shader, sprite and mask.
But I am been forced to use atlases for saving RAM cause have to many sprites to load at the same time (3 states and 5 directions for each of varyous units). And this was caused problems. When i try to do this in edit mode (by using ExecuteInEditMode) all works fine. But when i press "play" button, all breaks down.
How it's look like:
As i understand, problem is that when I press "play" atlas was builded. So when I get sprite from it I get it susseccfully. But when I try to get texture from Sprite for seting it to Shader I have got big texture (full atlas). Shader do not know nothing about actual position on this sheet for cheking is pixel need to colorizing.
So my question are: how can i get "small" texture from sprite to set it to shader?
How I set "mask" to shader:
public void UpdateMask(Texture tex)
{
//Debug.LogFormat("UpdateMask {0}", tex);
m_SRenderer.GetPropertyBlock(m_SpriteMbp);
m_SpriteMbp.SetTexture("_Mask", tex);
m_SRenderer.SetPropertyBlock(m_SpriteMbp);
}
Some shader's pieces:
Properties
{
[PerRendererData] _MainTex("Sprite Texture (RGB)", 2D) = "white" {}
[PerRendererData] _Mask("Alpha (A)", 2D) = "white" {}
_FriendlyColor("FriendlyColor", Color) = (1,1,1,1)
_EnemyColor("EnemyColor", Color) = (1,1,1,1)
_NeutralColor("NeutralColor", Color) = (1,1,1,1)
_Intencity("BlendingIntencity", Range(0, 1)) = 0.5
[PerRendererData] _IsFriendly("IsFriendly", Float) = 0
[PerRendererData] _IsHighlight("Outline", Range(0, 1)) = 0
[PerRendererData] _HighlightColor("Outline Color", Color) = (1,1,1,1)
}
fixed4 frag(v2f IN) : COLOR
{
fixed4 mainTex = tex2D(_MainTex, IN.texcoord) * IN.color;
fixed4 alphaMask = tex2D(_Mask, IN.texcoord) * IN.color;
fixed4 output;
fixed4 blendColor;
if (alphaMask.a > 1.0e-6)
{
if (_IsFriendly == 4)
{
blendColor = fixed4(_NeutralColor.r, _NeutralColor.g, _NeutralColor.b, alphaMask.r);
}
else
{
if (_IsFriendly == 1)
{
blendColor = fixed4(_FriendlyColor.r, _FriendlyColor.g, _FriendlyColor.b, alphaMask.r);
}
else
{
blendColor = fixed4(_EnemyColor.r, _EnemyColor.g, _EnemyColor.b, alphaMask.r);
}
}
output = BlendOverelay(mainTex, blendColor * _Intencity);
}
else
{
output = mainTex;
output.rgb *= output.a;
}
if (_IsHighlight != 0)
{
fixed4 blendedColor = BlendAdditive(output, _HighlightColor);
blendedColor.a = output.a;
blendedColor.rgb *= output.a;
output = blendedColor;
}
return output;
}
You need to tell the sprite renderer where its position in the atlas is and how large the atlas is, so that it can convert the IN.texcoord
UV in atlas space to the corresponding UV in sprite space. Then, you can sample from the alpha map using the sprite space UV
In C#, set the atlas offset & scale information to e.g., _AtlasPosition
:
public void UpdateMask(Texture tex)
{
//Debug.LogFormat("UpdateMask {0}", tex);
m_SRenderer.GetPropertyBlock(m_SpriteMbp);
m_SpriteMbp.SetTexture("_Mask", tex);
Vector4 result = new Vector4(sprite.textureRect.position.x, sprite.textureRect.position.y, sprite.textureRect.size.x, sprite.textureRect.size.y)
m_SpriteMbp.SetVector("_AtlasPosition", result)
m_SRenderer.SetPropertyBlock(m_SpriteMbp);
}
In shader, calculate the current UV in sprite space, and use it to sample from _Mask
:
fixed4 frag(v2f IN) : COLOR
{
fixed4 mainTex = tex2D(_MainTex, IN.texcoord) * IN.color;
// multiply both the position offset and size by the texel size to bring them into UV space
float4 atlasOffsetScale = _AtlasPosition * _MainTex_TexelSize.xyxy;
// apply UV position offset and scale, sample from alpha mask
fixed4 alphaMask = tex2D(_Mask, (IN.texcoord - atlasOffsetScale.xy) / atlasOffsetScale.zw) * IN.color;
fixed4 output;
fixed4 blendColor;
// ...
You'll have to declare _MainTex_TexelSize
in your shader if you haven't already.
This will not work if you are using tight packing. For Sprite Packer, you will need to specify DefaultPackerPolicy
in the sprite packer or specify [RECT] in the packing tag. If you are using SpriteAtlas
, you will need to disable Tight Packing
.
Code sourced from this thread on the unity forums