- Home /
Semi-Transparency, Sprites, and Sorting Groups [Unity 3D]
Hi. I'm working on a 3D game with 2D sprites. I'm having difficulty with finding an appropriate solution to the z-buffer/transparency problem. Currently, I'm using render order to try and fix it, though it is not working how I would hope.
I have 3 elements in play;
The 2 Players [Sprites]
Water [Transparent Model]
Ice [Transparent Model]
I need ice and the players to always be on the same sorting group layer, as the way they're sorted is already correct. However, I need Ice to always render above Water, and any player that's underwater needs to be rendered under the water. My current solution is with Sorting Groups, but it seems to not be working correctly.
//Update render order
public void waterRenderUpdate(bool underWater)
{
if (underWater)
{
transform.GetComponent<SortingGroup>().sortingOrder = -2;
}
else
{
transform.GetComponent<SortingGroup>().sortingOrder = 0;
}
}
When the code above is used, the players do go under the water, but it causes ice to always be rendered ontop of submerged characters. If I change the ice as well by shifting groups, the other player will appear wrong through the ice. If I set the ice to the same layer as the underwater player, this causes the ice to always appear under the water, which should never happen.
I am not using the Lightweight Render Pipeline, due to a combination of issues it causes. I'm also looking for a non-resource intensive solution. I am not concerned with characters gradually entering a transparent material, popping in/out from them is fine (it's why I tried sorting groups). That being said, a solution that happens to cause that as well is equally valid. I don't need to use sorting layers. Are there any solutions to the z-buffer issue that I can apply here?
Your sprite seems to have defined edges - are you willing to sacrifice semi-transparency? It will make the edges aliased, but using an alpha-cutoff shader will write to the depth buffer. Alternatively, you could use dithered transparency, but that has it's own issues.
I can think of a few causes where I would want semi-transparency, though they all have work-arounds the dithered transparency option viable. $$anonymous$$y biggest concern is that shaders area really painful to write. The reason I tried to implement the LRP really early on is to avoid them, though ultimately it just caused more problems. I get the feeling that writing shaders might be unavoidable for the situation however, so maybe something similar that doesn't discard pixels? There's a few solutions here, I'm sure.
As for alpha-cutoff shaders, I'm afraid I'm unfamiliar with the term, and looking it up online doesn't clarify much seeing as alpha cutout shaders keep popping up. Cases where I could find shaders titled 'alpha-cutoff' are this , which seems to refer to using them like the filled tool on the UI, this , which uses angle offsets, 2 sprites ins$$anonymous$$d of a sprite and a 3D transparent surface, and this , which cuts holes in 3D meshes similar to how masks work, though done shader-side.
If you're referring to the shader for Alpha Cutout, those sprites aren't quads. They're Sprite Renderers, and they already can cutout sprites. Would modifying the default sprite shader somehow fix this? If you meant Alpha Cutout on the surface of the water/ice, I tried doing the pixel tiled trick, though it proved very ineffective as the center of the volume would either snap between transparent and filled depending on the cutoff value, or the pixels would be so big that you could clearly see it's a cutoff (even if you stop compression).
Could you please clarify what you meant by alpha-cutoff? Thank you for responding, by the way, it's very much appreciated.
I use cutoff interchangeably with cutout; both mean the same thing - rather than blending across transparency changes, only render pixels that are above a specified opacity. In this case, it looks like your sprite doesn't have semi-transparent sections, hence I was suggesting to use a cutout shader in its place. If the sprite shader already supports cutout, then in theory it should write to the depth buffer and solve this problem. If it doesn't, then yes you would need to modify the sprite shader (which isn't too difficult, although I'm not sure on how sprite materials are implemented). As for dithered transparency, it would be similar to a cutout shader, but using a noise grid (similar to what you mentioned). To fix the problem of visible missing pixels, you would need to sample random values every frame and use something like TAA to blend them together.
Answer by Smg065 · Sep 11, 2019 at 02:53 PM
So I took a look at @Namey5 's suggestions, and found that Dithered Textures for the water was the solution I wanted. Moreover, I like how it looks so much that I'm alright with the missing pixels, because unlike the attempt I made before, it seems to have a slider for pixel size, and keep it as a multiple of 2 (0.5, 0.25, 0.75, 0.125, etc), it's unnoticeable enough to work for my game. This is the dithered shader I used, credit to Alexander Ocias.
Shader "Ocias/Diffuse (Stipple Transparency)" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Transparency ("Transparency", Range(0,1)) = 1.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 150
CGPROGRAM
#pragma surface surf Lambert noforwardadd
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float4 screenPos;
};
half _Transparency;
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
// Screen-door transparency: Discard pixel if below threshold.
float4x4 thresholdMatrix =
{ 1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0,
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
};
float4x4 _RowAccess = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
float2 pos = IN.screenPos.xy / IN.screenPos.w;
pos *= _ScreenParams.xy; // pixel position
clip(_Transparency - thresholdMatrix[fmod(pos.x, 4)] * _RowAccess[fmod(pos.y, 4)]);
}
ENDCG
}
Fallback "Mobile/VertexLit"
}
I know that this doesn't act as a satisfying answer for people who might be interested in other solutions, so here's some more that I know of, but didn't work on my project.
You can use a non-sprite cutout shader in Unity's defaults if you aren't worried about shadows being cast on the sprite (I am).
You can attach a dithered texture to the player rather than the water, and keep it at 1. This, as stated above, writes the sprite to the depth buffer, at the cost of not being able to have transparency in your sprites. I personally put it in the water, so I could have the freedom of semi-transparent sprites.
If you need to write to depth, you're using transparent/unlit sprites, and you can't use dithering on the material, consider looking at this thread. It seems the post works really well for old versions of unity, and a post in the comments has one that works for Unity 2017.
Thank you for your help, and I hope one of the solutions above helped out others with similar problems.