Search Unity

Mixing shaders -> problems with visability

Discussion in 'Shaders' started by SimRuJ, Oct 19, 2018.

  1. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    I've got three "layers" of objects in my project - think of it as terrain with a planar river and bedrock:

    1. Transparent blue plane:
    - "Hidden/Internal-Colored" shader
    - GameObject created at runtime with vertices read from file
    - Needs color, transparency and has to be visible from both sides (no texture)

    2. Transparent ground:
    - "Transparent/VertexLit" shader
    - Finished GameObjects that are imported at runtime
    - Needs texture and transparency (no color, doesn't have to be visible from below)

    3. "Bedrock":
    - "Standard" shader
    - Textured plane (4 corner points) created at runtime
    - Needs texture (no transparency or color, not visible from below)

    The problem is that, depending on the angle/position of the camera, a part of the ground objects (2.) aren't visible through 1. but instead 3. shines through. The objects are still there but they are occluded by the blue plane, even though I'm not using any type of mask. This effect is visible both in the "Game" and the "Scene" tab!

    What it looks like:


    Moving a bit closer to see what it's supposed to look like:


    I've tried different shaders for 1. but most of them don't support a GameObject being visible from both sides AND color/transparency. There's also only ambient light in the scene (the default directional one is disabled).

    Does anyone know of any shaders that support what I need but still work well together?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    By default the Hidden/Internal-Colored shader writes to the depth buffer, something you rarely want for a transparent objects. When you setup that material, make sure you set the _ZWrite property to 0.0.
     
  3. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    @bgolus Thanks! At runtime you can do it like this:
    Code (CSharp):
    1. if(mat.HasProperty("_ZWrite")) {
    2.     mat.SetFloat("_ZWrite",0);
    3. }
    Unfortunately the transparency is now messed up in a different way: If there's the transparent ground (2.) below it, setting the alpha to "1" (or a value close to 1) now doesn't make it fully opaque anymore - it looks more like the alpha is set to 0.7 - but in places where the ground isn't loaded yet, the plane is fully opaque.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
  5. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    @bgolus Thanks for the link! Sorry, my knowledge about shaders is very, very (very) limited. Is this the same problem as in the video, even though I have 3 separate models with different shaders?

    The effect I described in my second post only seems to happen with my normal, big, blue plane and looking at it more closely the rendering order is indeed off with ZWrite disabled (the plane is always "on top").

    The small plane (I posted screenshots of above) does get completely opaque (in addition to the rendering order problem) but I'm dealing with a similar visual problem like above (with "alpha" set to 1):
    (I)

    With "ZWrite" on 1 and the plane's alpha on 0 the transparent ground (2.) underneath is masked and the bedrock (3.) shines through:
    (II)
    - this only happens with the actual plane, not the small test one. Is this possibly caused by the triangle order being wrong? It's the way I'm getting the coordinates and I can't really do anything about. This is one of the reasons why I need the shader to show both sides (so culling off, otherwise it's just a zigzag).

    Other than that the transparency works properly. With both "ZWrite" and the plane's alpha on 0 it looks fine but it won't get fully opaque, plus the rendering order is off.
    Is there no shader that can create a good mix inbetween? Or at least to fix the problem in screenshot (II)? :/
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Not the same issue as that post in terms of the desired result, but the same underlying problem. Really just the first few paragraphs of my post are the important part.

    Short version is the order in which transparent objects are rendered maters. If you aren't controlling that yourself manually, Unity is going to guess and do it for you, and that guess is almost purely based on the distance to a bounding sphere. For your case, the mesh for the bottom left section is closer to you than the blue rectangle, thus it gets rendered later.

    The solution is to modify the render queues on your materials to force things to render in the order you need.
     
  7. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    @bgolus
    I see, thanks!

    I don't have to change anything about my bedrock (3.), since it's opaque, right?

    I now changed "mat.renderQueue" for the other two layers:
    1. blue plane: 3200
    2. transparent ground: 3100
    Does it matter how much I add (as long as it's between 3000 and 4000), so if it's 3001/3002, 3010/3020 or 3100/3200?

    Does setting the rendering order manually also mean that "ZWrite" gets ignored?
    With the above values all alpha values work for the blue plane now, no matter if the ground objects below it have been loaded. :)
    But (unfortunately there's a but): In cases where the blue plane is below the ground, the rendering order is now off of course, for example in a case like this (just an old screenshot, not the way it actually looks):



    In my app the plane is now always above the terrain, even though it should only be above, where the terrain is actually lower than the plane (so e.g. with a river bed and a river plane that's intersecting the shore line).

    Is there a way to fix that too?
    The "Legacy/Transparent/VertexLit" shader (the one I'm using for the ground) automatically renders everything in the right order and the transparency works properly too with the plane but it isn't visible from both sides and the shader also only seems to have 3 transparency "settings" (fully transparent, 0.5 and fully opaque).
    Using "Hidden/Internal-Colored" (currently used for the plane) for both the plane and the ground objects displays the transparency properly and it's visible from both sides but then again it doesn't support textures (and even if it did, there might be a conflict with the color and its alpha). :/
     
    Last edited: Oct 25, 2018
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    At the risk of sounding like a broken record, I'm going to repeat this line again.
    Opaque objects with ZWrite On allows each pixel to get sorted by the depth buffer. The point of it is to ensure the final rendered image is the same regardless of the order objects are rendered in.

    Semi-transparent objects with ZWrite On can have the potential for several visual anomalies depending on the order the objects render in, and the order their triangles order in. You'll get weird "holes" where things don't appear at all (like you saw above) which is often worse than just rendering in the wrong order with ZWrite Off.

    The best solution is don't use transparency unless you really, really need it. And if you do, you have to be prepared to make some concessions, like knowing intersecting or complex models will not render correctly. If you have an object that you want to be both opaque and transparent, split it up so the opaque parts and transparent parts are separate materials. If you can, use alpha tested materials rather than transparent ones as these get the benefit of per pixel sorting.
     
  9. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    @bgolus

    Yes, that's exactly the behavior I need when the blue plane is completely opaque.

    I saw. :/ So in the end I have to decide between a weird mirror plane that's still visible, even though it should be transparent ((II) in post #5) and a plane that's too transparent and never gets fully opaque. :/

    I do need the transparency for both the blue plane and the ground objects, with the former I even let the user set it (that's why it can also be fully transparent or opaque) and the latter is always set to a fixed alpha value (0.8f at the moment).
    Everything I display in my app is built at runtime by reading coordinates from some type of text file (e.g. .obj for the ground objects), so I don't know in advance where and if objects are going to intersect other objects or how many objects there are and because of this also can't just split the objects in advance.
    I tried setting the rendering order to "AlphaTest" (2450) for the blue plane and the ground objects (using the same shaders mentioned in post #1) and the result wasn't any better than before but more like a mix: The plane was always on top and there were weird artifacts, e.g. one of the ground objects caused a weird cut out in the plane like in image (II) in post #5.
    What would you do if it was your app?

    I tried to combine the "Legacy/Transparent/VertexLit" and "Hidden/Internal-Colored" shaders by using the latter as a base (since the transparency looks pretty good with it) and adding texture support to it, which works (the objects are also still visible from both sides) but I managed to somehow also mess up transparency (setting it doesn't change anything anymore) and, like I said, my knowledge about shaders is very, very basic, so no idea what's actually wrong. Would you mind checking it out?:

    https://pastebin.com/s6gfUqTE
     
    Last edited: Oct 29, 2018
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Because changing the order to "AlphaTest" doesn't make it an alpha test shader, that's just the queue, ie: the order to render objects in. They're named such as it's generally the optimal order to render objects in. Also anything rendered in queues <2500 are sorted front to back which is bad for alpha blending, but good for early depth rejection when you can rely on the zwrite and the depth buffer to sort stuff rather than the render order.

    Alpha test shaders are opaque shaders that use the clip() function or discard keyword to reject certain pixels.
    https://docs.unity3d.com/Manual/SL-Blend.html

    I'd find another way to do the effect. Likely, mainly, not making anything but the blue plane transparent. Alternatively I'd split the geometry at the blue plane in code and make sure the stuff under water renders first... but more likely I'd just avoid the problem entirely.

    You combined them, yes... both shaders are now in the same shader. That's essentially no different than having a single object rendered with two different shaders. That's not really combining them at least in terms of merging the functionality. It just gets you the worst of both.

    One is written in old (and deprecated) fixed function shader code, and the other in more general hlsl. You can't really mix them in any real way, you have to convert the fixed function shader into hlsl, which Unity will do for you if you select the shader and click on "Show Generated Code". But for actually combining the two, there's no point. Both shaders do almost exactly the same thing with minor variations. I suggest you actually learn to write shaders.

    Start here:
    https://www.alanzucconi.com/2015/06/10/a-gentle-introduction-to-shaders-in-unity3d/
     
  11. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    Sorry for the late reply.

    Oh.
    Edit: Is it possible to make it sort/render depending on the height, not the camera? From what I've understood, wouldn't it get the right order then?

    The problem is that I need the objects to be transparent and if it's only slightly because there's something else that has to be visible through them (it's not causing any problems with the shaders or visibility, so I didn't mention it) and if they are opaque, there's also no point to add the "bedrock", which is a feature I'd prefer not getting rid of. :/
    Split the geometry? Can you tell it to do that automatically, like push everything below a certain y value into a different object, as long as it's below the blue plane?

    I basically did 3 things:
    Added the first pass from the Legacy shader (and "_MainTex" to go with it), set the culling to "off" and added tags to the second pass (which seem to be pretty important).
    I have no idea why the "VertexLit" shader only has 3 transparency "settings", that's why I took the "Hidden/Internal-Colored" one as a base (just seemed easier). What I was trying to achieve:
    A shader the transparency of which works as well as the latter one's but that also has culling set to "off" and supports textures. Like I said, I got it to take a texture and setting the color of that texture works but just not the alpha and that's what I now want to fix.

    I see, there's now a lot more code for the "VertexLit" shader (and also a lot more confusion :/).

    There is for me because the transparency works great with both shaders as long as I use the same one for the river AND the ground objects but both types of objects have different prerequisites (the blue plane uses color and culling off, the other objects a texture and culling on) and neither of the shaders supports all of it. I need a shader that uses the same transparency settings but also has culling set to "off" and supports color AND textures. I've tested a lot of Unity' shaders but either culling wasn't the way I needed it to be or the transparency/rendering order was simply off.

    Thanks for the link. I tried before with the Unity tutorial here and I do understand the layout (kind of, like the passes, structs,...) but I just can't really get my head around the math behind it. :/


    Edit: I tried all the Legacy shaders again and it looks like "Transparent/Cutout/SoftEdgeUnlit" displays everything properly (even when the alpha's set to 0), it supports textures and culling is off by default. The only thing I don't like is the way the transparency jumps from one value to another (the same as with the "VertexLit" one) but I'm going to look at that tomorrow.
     
    Last edited: Nov 5, 2018
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Yes and no. There's no "sort by height" option, but you could manually control the render order either via the material queue or explicitly via script using the sorting order.
    https://docs.unity3d.com/ScriptReference/Renderer-sortingOrder.html
    The value is exposed to the inspector and serialized for sprites and UI elements, but not for any other renderers, but I believe it still works if set via script at runtime.

    I understand the blue plane being transparent, but why the ground and bedrock? If the issue is you want something to be visible below the "ground" then I'd look at making the ground opaque and using a special shader on that object that's using ZTest Greater or ZTest Always that will render over the ground rather than making the ground itself transparent.

    If by automatically you mean you write a custom script or find some code / asset online that can split geometry along a plane, sure. Unity doesn't have any support for doing so by default.
     
  13. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    Yes, you can still set the rendering order via script with "myMaterial.renderQueue = 3100". The problem with this is that the blue plane is then always on top OR the ground objects are always on top, even though the blue plane is below the ground at the shore line. So unless I cut the ground objects right where the plane intersects them and then give different values to the resulting smaller objects, simply setting the rendering order won't do it unfortunately.

    Nonono, the bedrock isn't transparent, only the blue plane and the ground objects are. The blue plane is a single object and always transparent with a slider for the user to set it (that's why I want it to look good with alpha 0 and alpha 1). The ground objects are transparent (alpha is 0.8), so you can see the other objects mentioned in my last post through them and also a hint of the bedrock texture.

    Oh, that's what I was afraid to hear... There's indeed an asset for something like that with a test version for it (click) but I haven't tested it yet.
     
    Last edited: Nov 6, 2018
  14. SimRuJ

    SimRuJ

    Joined:
    Apr 7, 2016
    Posts:
    247
    Thanks again for all of your answers and the links!

    I did some more testing with "Legacy Shaders/Transparent/Cutout/Soft Edge Unlit" (on Github: click) and like the results much better than what I've seen with the other shaders so far.

    I changed two things:
    - Set the default "_Cutoff" to 0.9 to only get full opaqueness at 0.9, not at the default 0.5
    - Deleted "ZWrite off" in the second pass, otherwise the plane is always on top and the right visibility only kicks in once the alpha is set to a value bigger than 0.9 (the "_Cutoff") - this also gets rid of the issue described here (even with "Cull back").

    I'm now using it for both the blue plane (default alpha = 0.5) and the ground objects (alpha = 0.8) but I might make a second version with "culling back" for the ground objects at one point:

    With alpha = 0 the blue plane is completely invisible - no weird "mirror" object like in post #5 (screenshot (II)) close to the camera. It does seem to mask ground objects that are further away though but there's an easy fix: Disable the plane if the transparency is set to a value below 0.05 and set it active again accordingly. If you/the camera is really, really far away, the plane also "bugs" out at other alpha values (most visible at alpha < 0.6), simply ignores the ground objects and lets the bedrock shine through (it changes the tint of the plane) but with how well the shader's performing otherwise I can live with that.

    With alpha = 0.5 the ground objects are visible through the plane and the bedrock is too (just barely - the way it's supposed to be). At the shoreline the blue plane is cut off but in areas where the ground objects are actually below the blue plane, it's visible through it (so similar to the screenshot in post #7).

    With alpha = 0.89 the plane is completely opaque but not visible through the ground objects at the shore.

    With alpha >= 0.9 the blue plane is visible through the shore line and the transparency is correct, even in situations like in the screenshot in post #7. I might even change the "_Cutoff" to 1.0 because while the plane being visible through the ground objects is the correct visibility, I have to admit that seeing this "underground part" only if the transparency slider is at its max is less distracting.

    I still have to do a lot more testing but I think I'm going to keep this shader for now.