Search Unity

Getting normals relative to camera view

Discussion in 'Shaders' started by hsallander, Jan 23, 2017.

  1. hsallander

    hsallander

    Joined:
    Dec 19, 2013
    Posts:
    46
    Hi, I'm quite new at shader programming and I'm trying to put together a matcap shader for a project. I've looked at the some current implementations as free assets from the Asset Store, but none of them function as I would expect. They all seem to use the normals relative to the camera direction, but not the direction between the camera and the object which is what I would expect is the correct approach. For instance this shader from @Jean-Moreno does the following vert() calculations, I've been in contact with him and he was also unable to solve it, and I've tried modifying the calculations myself to make the normals relative to the camera position but have so far been unsuccessful)

    Code (CSharp):
    1.              
    2. v2f vert (vertexdata v)
    3. {
    4.     v2f o;
    5.     o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    6.     o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    7.     o.color = v.color;
    8.                  
    9.     float3 worldNorm = normalize(
    10.         unity_WorldToObject[0].xyz * v.normal.x
    11.         + unity_WorldToObject[1].xyz * v.normal.y
    12.         + unity_WorldToObject[2].xyz * v.normal.z);
    13.  
    14.     worldNorm = mul((float3x3)UNITY_MATRIX_V, worldNorm);
    15.  
    16.     o.cap.xy = worldNorm.xy * 0.5 + 0.5;
    17.                  
    18.     return o;
    19. }
    20.  
    The way this behaves is as follows,
    I’m using a simple matcap image with red edges just to illustrate:




    Here’s a case where the object is more or less centered, and then it’s behaving correctly, first image has no red edge, then I rotate the camera and when the edge is approaching 90deg from the camera it turns red:
    ->


    And here’s a scenario when it doesn’t work as expected. First image has no black edge, then I move the camera sideways (note, not rotating, so the camera direction is the same), and the edge continues to be black even though in the second image it’s approaching 90 deg from the view.
    ->



    Anyone out there that knows how to solve this? (like @Daniel-Brauer who seems to be a shader guru here at the forums)

    Thanks in advance!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Matcap shaders are generally used because they're significantly cheaper to calculate than actual lighting. To get the "correct" normals would actually significantly complicate the shader over the average Matcap shader, hence why you can't find one that works like you expect. I'm actually surprised the snippet you posted works the way it does, most of the time I see Matcap shaders just use normalize(mul(UNITY_MATRIX_MV, v.normal)) and be done with it, though the above will handle non-uniform scaled meshes correctly.

    Really the calculations would still be less than those needed for something like Unity's own Standard shader.
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Code (CSharp):
    1. Shader "Unlit/Matcap"
    2. {
    3.     Properties
    4.     {
    5.         _Matcap ("Matcap", 2D) = "white"
    6.         [Toggle] _PerspectiveCorrection ("Use Perspective Correction", Float) = 1.0
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Tags { "RenderType" = "Opaque" }
    12.  
    13.         Pass
    14.         {
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.  
    19.             #include "UnityCG.cginc"
    20.  
    21.             struct v2f
    22.             {
    23.                 float4 pos : SV_POSITION;
    24.                 float2 cap : TEXCOORD0;
    25.             };
    26.  
    27.             sampler2D _Matcap;
    28.             bool _PerspectiveCorrection;
    29.  
    30.             v2f vert (appdata_full v)
    31.             {
    32.                 v2f o;
    33.                 o.pos = UnityObjectToClipPos(v.vertex);
    34.                            
    35.                 float3 worldNorm = UnityObjectToWorldNormal(v.normal);
    36.                 float3 viewNorm = mul((float3x3)UNITY_MATRIX_V, worldNorm);
    37.  
    38.                 if (_PerspectiveCorrection)
    39.                 {
    40.                     // get view space position of vertex
    41.                     float3 viewPos = UnityObjectToViewPos(v.vertex);
    42.                     float3 viewDir = normalize(viewPos);
    43.  
    44.                     // get vector perpendicular to both view direction and view normal
    45.                     float3 viewCross = cross(viewDir, viewNorm);
    46.                    
    47.                     // swizzle perpendicular vector components to create a new perspective corrected view normal
    48.                     viewNorm = float3(-viewCross.y, viewCross.x, 0.0);
    49.                 }
    50.            
    51.                 o.cap = viewNorm.xy * 0.5 + 0.5;
    52.                 return o;
    53.             }
    54.  
    55.             fixed4 frag (v2f i) : SV_Target
    56.             {
    57.                 fixed4 col = tex2D(_Matcap, i.cap);
    58.                 return col;
    59.             }
    60.  
    61.             ENDCG
    62.         }
    63.     }
    64. }
     
  4. hsallander

    hsallander

    Joined:
    Dec 19, 2013
    Posts:
    46
    Thanks a lot @bgolus! This definitely behaves more according to what I'm looking for. It does not fill the whole edge of the cube with the outer matcap color though at certain angles, which is a bit strange? But maybe that's a necessary effect of compensating for the perspective or can that be fixed as well somehow?



    Eg on the cube in the screendump the whole right edge should have the same normal relative to the camera even though it's in perspective, no?
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    No. It was the same normal using the old method because the normal and "view direction" (camera forward vector) are both constant across the screen. With the perspective correction the "view direction" varies across the screen as it's now the angle from the camera position to that vertex position. The normal being constant was the reason it looked funny before!

    The issue you're seeing now is the calculations are being done at the vertex level and not per-pixel (again, because it's cheaper). However for something like a box you see this break down in a way the old method didn't. You'd have to modify the shader I posted to pass both the viewNorm and viewPos (not viewDir!) to the fragment shader and do lines 42 - 51 in the fragment shader.
     
    m_hakozaki and hsallander like this.
  6. projectonemotion

    projectonemotion

    Joined:
    Jan 20, 2017
    Posts:
    16
    Hi bgolus!
    In some android devices (e.g. devices with Adreno 506 GPU) "viewDir" not working properly, in specific camera angles it works fine but in most angles texture deformed very much and completely messed up. is there any alternative for "viewDir"? is there any way to fix it?
    Thanks in advance!

    Edit:
    In rotation (0, 180, 0) it looks fine but when camera rotates texture messed up.
     
    Last edited: Aug 9, 2018
  7. projectonemotion

    projectonemotion

    Joined:
    Jan 20, 2017
    Posts:
    16
    It seems there is a bug in calculation of UNITY_MATRIX_V in some Android devices. bug reported to unity.
    Quick fix: calculate view matrix in C# and pass it to shader.