Search Unity

Give priority to certain triangles

Discussion in 'Shaders' started by Guedez, Jan 27, 2020.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I have a flower, drawn using a single mesh and single material
    Because it is double sided, the 4 vertices that compose the flower part can't just be moved forward/backwards, they need to be at the same Z level as the stem.
    The issue is that the stem Zfights with the flower, and because the flower can be any type of flower (meaning, it's possible that it's texture will not overlap the stem at all), I can't just preemptively end the stem, extra especially since the size of the flower is configurable.

    OBS:
    * I know which vertices are which in the vertex shader since the Z component is being used as a vertex index.
    * It is a instanced shader
    * Image: https://imgur.com/a/RrNCjqQ
     
  2. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Moving those vertexes towards the camera would also solve the issue, but I couldn't get it done yet
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    If you're using ZWrite On, which if you're having z fighting you do, then you must move those vertices / surfaces closer to the camera.
    Double sided doesn't preclude this btw. It just means you can't have it closer in the mesh itself. A vertex shader could manipulate the vertices all you want.

    If you can flag those vertices somehow, ie: vertex coloring, an additional UV channel, or force UVs for "flower petals" to always be within a certain range (like negative!) then you can have the vertex shader see that and push those vertices slightly forward. Should eliminate z fighting too.

    Depending on what kind of shader you're using, the simple version would be something like:
    Code (csharp):
    1. // detect flower UVs
    2. if (v.texcoord.x < 0.0)
    3. {
    4.   // world space view forward vector
    5.   float3 worldSpaceViewFwd = mul(float3(0,0,-1), (float3x3)UNITY_MATRIX_V);
    6.  
    7.   // object space oriented, 1 unit world scaled, view forward vector
    8.   float3 objectSpaceViewFwd = mul((float3x3)unity_WorldToObject, worldSpaceViewFwd);
    9.  
    10.   // offset object space vertex position
    11.   v.vertex.xyz -= objectSpaceViewFwd * _FlowerOffsetScale;
    12. }
     
    Guedez likes this.
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Note with that method you want to be careful with the offset amount as it'll slightly shift the position & size of the flower too.

    Alternatives would be to use the view direction (vertex world pos - camera world pos) to drive it, which will ensure the flower doesn't change in relative size, but may cause texture distortion. If you're doing this with a vertex fragment shader you can manipulate the clip space Z, but since that's a non-linear value that can be tricky to apply appropriately, but has the benefit of not causing any obvious texture distortion ... unless you can move the camera through the flower in which case all bets are off.

    The last option is to do the offset in the fragment shader using SV_Depth. This is by far the most expensive option, but has the benefit of guaranteeing no texture / mesh distortions.
     
  5. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    What I meant was that the naive solution of
    v.vertex.z += 0.001
    would solve the issue for one of the sides only

    Unfortunately, this didn't work, it created this strange effect: https://imgur.com/a/Rb7at5U
    I've used a big offset to be very visible what's happening

    I have no idea how to use vertex world pos - camera world pos to get it done
    I don't understand how to manipulate the clip space Z, google only helped me how to get the value (apparently it's vector.xyz/vector.w), and the camera can go through the flowers
    SV_Depth was my first choice, but I couldn't get any example or directions or anything on how to manipulate this value with a surface shader
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Unfortunately, that's because you can't manipulate it with a surface shader. You're limited to the object space vertex manipulation.

    You should be using a very small number, like 0.0001.

    Here's a bad flower I made to test it out. Here it is without anything special being done (ie: an offset scale of 0.0). I made sure there was obvious z fighting in the image.
    upload_2020-1-28_14-16-24.png

    And here's the exactly same angle with an offset of 0.0001 using the code above as is.
    upload_2020-1-28_14-17-12.png

    And to prove no issues when looking from the side or back.
    upload_2020-1-28_14-19-42.png

    Code (csharp):
    1. Shader "Custom/BadFlower"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    6.         _FlowerOffsetScale ("Flower Offset", Float) = 0.001
    7.         _Cutout ("Cutout", Float) = 0.5
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="AlphaTest" "RenderType"="Opaque" }
    12.         LOD 200
    13.         Cull Off
    14.  
    15.         CGPROGRAM
    16.         // Physically based Standard lighting model, and enable shadows on all light types
    17.         #pragma surface surf Standard fullforwardshadows addshadow alphatest:_Cutout vertex:vert
    18.  
    19.         // Use shader model 3.0 target, to get nicer looking lighting
    20.         #pragma target 3.0
    21.  
    22.         sampler2D _MainTex;
    23.         float _FlowerOffsetScale;
    24.  
    25.         struct Input
    26.         {
    27.             float2 uv_MainTex;
    28.             half facing : VFACE;
    29.         };
    30.  
    31.         void vert (inout appdata_full v)
    32.         {
    33.             // detect flower UVs
    34.             if (v.texcoord.x < 0.0)
    35.             {
    36.                 // world space view forward vector
    37.                 float3 worldSpaceViewFwd = mul(float3(0,0,-1), (float3x3)UNITY_MATRIX_V);
    38.  
    39.                 // object space oriented, 1 unit world scaled, view forward vector
    40.                 float3 objectSpaceViewFwd = mul((float3x3)unity_WorldToObject, worldSpaceViewFwd);
    41.  
    42.                 // offset object space vertex position
    43.                 v.vertex.xyz -= objectSpaceViewFwd * _FlowerOffsetScale;
    44.             }
    45.         }
    46.  
    47.         void surf (Input IN, inout SurfaceOutputStandard o)
    48.         {
    49.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    50.             o.Albedo = c.rgb;
    51.             o.Normal = half3(0,0,IN.facing);
    52.             o.Metallic = 0;
    53.             o.Smoothness = 0;
    54.             o.Alpha = c.a;
    55.         }
    56.         ENDCG
    57.     }
    58.     FallBack "Diffuse"
    59. }
     

    Attached Files:

  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Could it be because I change the
    unity_ObjectToWorld 
    matrix on the
    #pragma instancing_options procedural:setup
    setup()
    method the solution does not line up anymore?
    That would also explain why every single other thing I tried failed with some random gibberish results too
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    If you're not also setting the unity_WorldToObject, yes, absolutely that would be the problem.
     
  9. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I just need to apply the same functions to unity_WorldToObject as I did to unity_ObjectToWorld?
    Edit: No, actually, I recreate the whole unity_ObjectToWorld from zero right at the begining
    Code (CSharp):
    1.             unity_ObjectToWorld._11_12_13_14 = float4(rot.a.x, rot.a.y, rot.a.z, pos.x);
    2.             unity_ObjectToWorld._21_22_23_24 = float4(-rot.a.y, rot.b.x, rot.b.y, pos.y);
    3.             unity_ObjectToWorld._31_32_33_34 = float4(-rot.a.z, -rot.b.y, rot.b.z, pos.z);
    4.             unity_ObjectToWorld._41_42_43_44 = float4(0, 0, 0, 1);
    I guess I will have to learn matrix theory
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    If you're modifying the unity_ObjectToWorld matrix, you'll to calculate the inverse matrix of that for the unity_WorldToObject. If this wasn't a Surface shader this wouldn't be needed and we could do all the calculations in world space. Unfortunately surface shaders require you output an object space position (for various fairly good reasons).

    I believe the UnityStandardParticleInstancing.cginc file has an example for calculating the inverse matrix.
     
    Guedez likes this.
  11. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    plenty of homework for tomorrow, at lest now I have a clear objective
     
  12. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    After copying what vertInstancingMatrices did in UnityStandardParticleInstancing.cginc. I got it working on Iso camera, but it does not work on perspective. I've tried removing the (3x3) and making the float3(0,0,-1) into a float4(0,0,-1,1), but I got gibberish instead of 'adding perspective calculation' as I assumed it would do

    Got it working almost always by:
    Code (CSharp):
    1. float3 worldSpaceViewFwd = mul(float4(0, 0, -1, 1), UNITY_MATRIX_VP);
    2. float3 objectSpaceViewFwd = mul(unity_WorldToObject, worldSpaceViewFwd);
    3. v.vertex.xyz -= objectSpaceViewFwd * 0.001;
    The issue now, is that a minority of the flowers show it wrong when viewed at very specific angles (mostly from very high or very low, considering it's a 3rd person orbit camera game, it will happen)
    https://imgur.com/a/LjvSVpz
     
    Last edited: Jan 29, 2020
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Those really do need to be float3 & float3x3. Those are directions, not positions (which is what a float4 & float4x4 mul does).
     
  14. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    float3 worldSpaceViewFwd = mul(float3(0, 0, -1), (float3x3)UNITY_MATRIX_V);

    Only works on orthogonal camera, does not work on perspective at all
    float3 worldSpaceViewFwd = mul(float3(0, 0, -1), (float3x3)UNITY_MATRIX_VP);

    Does not work at all ever
    float3 worldSpaceViewFwd = mul(float4(0, 0, -1, 1), UNITY_MATRIX_VP);

    work 90~% of the time
    I can't think of any other combination to check
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    I missed you were using the
    UNITY_MATRIX_VP
    in the previous post. Don't use that. The fact you're getting anything that works from that is a bit of mystery to me. Applying the transpose of the view projection matrix ... I'm honestly not even entirely sure what value that will output. Might only be working because the xy values are 0, 0. In that last example with a w of 1 I can imagine it producing a value nearly identical to the original
    mul(float3(0, 0, -1), (float3x3)UNITY_MATRIX_V);
    , but with a different magnitude if you're close to the origin. Anywhere else and I have no idea.

    On the edges of the screen it might not work great, especially if you have a wide FOV.

    You can try something like this instead.
    Code (csharp):
    1.             // detect flower UVs
    2.             if (v.texcoord.x < 0.0)
    3.             {
    4.                 // world space position
    5.                 float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    6.  
    7.                 // world space view direction
    8.                 float3 worldSpaceViewDir = normalize(worldPos - _WorldSpaceCameraPos.xyz);
    9.  
    10.                 // object space oriented, 1 unit world scaled, view direction
    11.                 float3 objectSpaceViewDir = mul((float3x3)unity_WorldToObject, worldSpaceViewDir);
    12.  
    13.                 // offset object space vertex position
    14.                 v.vertex.xyz -= objectSpaceViewDir * _FlowerOffsetScale;
    15.             }
    That's using the view direction (vertex to camera position vector) instead of the forward vector, which should work better with a perspective view. If that doesn't work, then I'd be worried the
    unity_WorldToObject
    matrix isn't being calculated properly. If the above still isn't working for you, and you're constructing your matrix with only a rotation and position and no scale, or the scale is uniform, then you could try replacing the
    objectSpaceViewDir
    line with:
    Code (csharp):
    1. float3 objectSpaceViewDir = mul(worldSpaceViewDir, (float3x3)unity_ObjectToWorld);
    Notice that's the object to world matrix. For uniform rotation matrices the transpose and the inverse are equivalent. And doing a
    mul
    with the matrix as the second parameter applies it as a transposed matrix. It's the same trick I was using with the
    UNITY_MATRIX_V
    , which is guaranteed to be a (mostly) uniformly scaled rotation matrix.
     
    Guedez likes this.
  16. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    That last one worked flawlessly, thanks a lot