Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question How to calculate how much of object is visible to camera (how much camera is zoomed in to object)

Discussion in 'Shaders' started by KUNGERMOoN, Oct 7, 2023.

  1. KUNGERMOoN

    KUNGERMOoN

    Joined:
    Apr 29, 2020
    Posts:
    18
    Hi, does anyone here know how can I detect how much camera is zoomed in to the object/how big part of a surface (say, a plane mesh or cube's surface) is in a camera's field of view? I need to calculate that for my grid shader to choose the correct scale of the grid (when you zoom out, the grid is every 10 cells instead of every cell). For now, I've been calculating this using
    float zoom = (1 / length(UNITY_MATRIX_MVP._m01_m11_m21)) * 2;
    (found it after playing with some built-in variables), but the values seem to be incorrect for Perspective cameras.

    The value I want to calculate is the red percentage on the below image: (but between 0 and 1 rather than 0% and 100%)
    zoom problem — copy.png
     
  2. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,081
    var camFov = 2 * Mathf.Atan( halfSize / aspect / distance ) * Mathf.Rad2Deg

    halfSize - is radius of enclosing sphere
    distance - is distance form camera to object
    aspect - is aspect of camera (it needs because FOV is actually VerticalFov )

    var gridScale = renderingCamera.FieldOfView / camFov ;
     
  3. KUNGERMOoN

    KUNGERMOoN

    Joined:
    Apr 29, 2020
    Posts:
    18
    Thanks for replying. I'm actually looking for a solution in HLSL which I could use in my shader, not in C#. (seems like I forgot to mention that in the post)
    I believe it should be possible to calculate what I need using built-in Matrix variables, but I'm too dumb to understand them.
     
  4. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,081
    if you try to calculate in FragmentShader then your input position has W attribute it is where linear z depth stored - distance from camera to fragment

    if you need it in vertex shader then you can multiply float4(0, 0, 0, 1) by UNITY_MATRIX_MVP to project object pivot point to ClipSpace and again linear depth will be in W attribute of result float4

    something like this:

    float zoom = mul( UNITY_MATRIX_MVP, float4(0, 0, 0, 1) ).w * someCoeff;
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    There are a number of ways to go about this. And what you're doing it's an entirely terrible option. However for perspective or orthographic cameras you need to do slightly different math. And you need to know things like how big the box is as the above kind of setup is getting the "scale" to the center of the mesh, which can be quite different for the surface of a box vs the pivot.

    What you really want to do is find the distance between the surface position and the camera, and then divide that by the projection's scale. The projection scale is in the second element of the second row, or
    UNITY_MATRIX_P._m11
    .

    For handling orthographic cameras you don't want the distance at all, but rather only 1 divided by the projection scale.

    The next thing that you'll probably want to do is be able to handle the scale in steps of 10, as your grid is in divisions of 10. It should be noted that mip maps work in steps of 2. Why does this matter? Because we can generalize how mip map levels are calculated to be any number of steps. The mip map level for a texture is calculated using a
    log2(x)
    , which is really
    log(x)/log(2)
    . So if we change that 2 to be another number, we can use any division number we want to get logₙ(x) (where n is the number of division).

    Then doing
    pow(gridDivs, log(distance)/log(gridSize))
    gets us the inverse scale value for a particular distance. And we can take the floor or ceiling of that logₙ(x) to get the prev and next scales.

    Combine that knowledge together (along with some procedural grid rendering) and you get something like this:
    Code (csharp):
    1. Shader "Unlit/InfiniteGrid"
    2. {
    3.     Properties
    4.     {
    5.         [NoScaleOffset] _MainTex ("Grid Texture", 2D) = "white" {}
    6.  
    7.         [Toggle] _WorldUV ("Use World Space UV", Float) = 1.0
    8.         _GridScale ("Grid Scale", Float) = 1.0
    9.         _GridBias ("Grid Bias", Float) = 0.5
    10.         _GridDiv ("Grid Divisions", Float) = 10.0
    11.  
    12.         [Header(Procedural Grid)]
    13.         [Toggle(PROC_GRID)] _ProcGrid ("Use Procedural Grid", Float) = 0.0
    14.         _BaseColor ("Base Color", Color) = (0,0,0,1)
    15.         _LineColor ("Line Color", Color) = (1,1,1,1)
    16.         _LineWidth ("Line Width (in pixels)", Range(0,10)) = 0.33
    17.         _MajorLineWidth ("Major Line Width (in pixels)", Range(0,10)) = 2.0
    18.     }
    19.     SubShader
    20.     {
    21.         Tags { "RenderType"="Opaque" }
    22.         LOD 100
    23.  
    24.         Pass
    25.         {
    26.             CGPROGRAM
    27.             #pragma shader_feature_local _ PROC_GRID
    28.  
    29.             #pragma vertex vert
    30.             #pragma fragment frag
    31.  
    32.             #include "UnityCG.cginc"
    33.  
    34.             struct appdata
    35.             {
    36.                 float4 vertex : POSITION;
    37.                 float2 uv : TEXCOORD0;
    38.             };
    39.  
    40.             struct v2f
    41.             {
    42.                 float4 pos : SV_POSITION;
    43.                 float2 uv : TEXCOORD0;
    44.                 float3 cameraPos : TEXCOORD1;
    45.             };
    46.  
    47.             bool _WorldUV;
    48.             float _GridScale, _GridDistScale;
    49.  
    50.             v2f vert (appdata v)
    51.             {
    52.                 v2f o;
    53.                 o.pos = UnityObjectToClipPos(v.vertex);
    54.                 float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
    55.                 o.uv = (_WorldUV ? worldPos.xz : v.uv) * _GridScale;
    56.              
    57.                 if (UNITY_MATRIX_P[3][3] >= 1.0) // if ortographic
    58.                     o.cameraPos = float3(0.0, 0.0, 1.0);
    59.                 else // perspective
    60.                     o.cameraPos = mul(UNITY_MATRIX_V, float4(worldPos, 1.0)).xyz;
    61.                 // adjust for persp fov & ortho scale
    62.                 o.cameraPos /= UNITY_MATRIX_P[1][1];
    63.              
    64.                 return o;
    65.             }
    66.  
    67.             sampler2D _MainTex;
    68.             half4 _LineColor, _BaseColor;
    69.             float _GridDiv, _GridBias, _LineWidth, _MajorLineWidth;
    70.             bool _ProcGrid;
    71.  
    72.             fixed4 frag (v2f i) : SV_Target
    73.             {
    74.                 // number of divisions in the grid
    75.                 float gridDiv = max(round(_GridDiv), 2.0);
    76.  
    77.                 // distance to surface (or orth scale)
    78.                 // float viewLength = length(i.cameraPos);
    79.                 // log length of view length
    80.                 // float logLength = log(viewLength)/log(gridDiv) - _GridBias;
    81.  
    82.                 // optimization of the above two lines taking advantage of this fact:
    83.                 // log(sqrt(value)) == 0.5 * log(value)
    84.                 float logLength = (0.5 * log(dot(i.cameraPos, i.cameraPos)))/log(gridDiv) - _GridBias;
    85.  
    86.                 // get stepped log values
    87.                 float logA = floor(logLength);
    88.                 float logB = logA + 1.0;
    89.                 float blendFactor = frac(logLength);
    90.  
    91.                 // inverse scales for each UV set derived from log values
    92.                 float uvScaleA = pow(gridDiv, logA);
    93.                 float uvScaleB = pow(gridDiv, logB);
    94.  
    95.                 // scale each UV
    96.                 float2 UVA = i.uv / uvScaleA;
    97.                 float2 UVB = i.uv / uvScaleB;
    98.  
    99.             #if !defined(PROC_GRID)
    100.                 // you can use these UVs plus the blend factor to fade between two scales of texture
    101.                 return lerp(tex2D(_MainTex, UVA), tex2D(_MainTex, UVB), blendFactor);
    102.  
    103.             #else
    104.                 // proceedural grid
    105.                 // we use a third UV set for the grid to show major grid lines thicker
    106.                 float logC = logA + 2.0;
    107.                 float uvScaleC = pow(gridDiv, logC);
    108.                 float2 UVC = i.uv / uvScaleC;
    109.  
    110.                 // proceedural grid UVs sawtooth to triangle wave
    111.                 float2 gridUVA = 1.0 - abs(frac(UVA) * 2.0 - 1.0);
    112.                 float2 gridUVB = 1.0 - abs(frac(UVB) * 2.0 - 1.0);
    113.                 float2 gridUVC = 1.0 - abs(frac(UVC) * 2.0 - 1.0);
    114.  
    115.                 // adjust line width based on blend factor
    116.                 float lineWidthA = _LineWidth * (1-blendFactor);
    117.                 float lineWidthB = lerp(_MajorLineWidth, _LineWidth, blendFactor);
    118.                 float lineWidthC = _MajorLineWidth * blendFactor;
    119.  
    120.                 // fade lines out when below 1 pixel wide
    121.                 float lineFadeA = saturate(lineWidthA);
    122.                 float lineFadeB = saturate(lineWidthB);
    123.                 float lineFadeC = saturate(lineWidthC);
    124.  
    125.                 // get screen space derivatives of base UV
    126.                 float2 uvLength = float2(length(float2(ddx(i.uv.x), ddy(i.uv.x))), length(float2(ddx(i.uv.y), ddy(i.uv.y))));
    127.  
    128.                 // calculate UV space width for anti-aliasing
    129.                 // This is done by scaling base UV rather than using derivatives of the pre-scaled UVs
    130.                 // to avoid artifacts at scale discontinuity edges. Note, this intentionally does not
    131.                 // into account the * 2.0 that was applied to the grid UVs so these are half derivs!
    132.                 float2 lineAAA = (uvLength) / uvScaleA;
    133.                 float2 lineAAB = (uvLength) / uvScaleB;
    134.                 float2 lineAAC = (uvLength) / uvScaleC;
    135.  
    136.                 // use smoothstep to get nice anti-aliasing on lines
    137.                 // line width * half deriv == 1.0 equals 1 pixel wide rather than 2 pixels wide
    138.                 // +/- 1.5 * half deriv == +/- 0.75 pixel AA
    139.                 float2 grid2A = smoothstep((lineWidthA + 1.5) * lineAAA, (lineWidthA - 1.5) * lineAAA, gridUVA);
    140.                 float2 grid2B = smoothstep((lineWidthB + 1.5) * lineAAB, (lineWidthB - 1.5) * lineAAB, gridUVB);
    141.                 float2 grid2C = smoothstep((lineWidthC + 1.5) * lineAAC, (lineWidthC - 1.5) * lineAAC, gridUVC);
    142.  
    143.                 // combine x and y grid lines together and apply < 1 pixel width fade
    144.                 float gridA = saturate(grid2A.x + grid2A.y) * lineFadeA;
    145.                 float gridB = saturate(grid2B.x + grid2B.y) * lineFadeB;
    146.                 float gridC = saturate(grid2C.x + grid2C.y) * lineFadeC;
    147.  
    148.                 // combine all 3 grids together
    149.                 float grid = saturate(gridA + max(gridB, gridC));
    150.  
    151.                 // lerp between base and line color
    152.                 return lerp(_BaseColor, _LineColor, grid * _LineColor.a);
    153.             #endif
    154.             }
    155.             ENDCG
    156.         }
    157.     }
    158. }
     
    Last edited: Oct 9, 2023
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Here's what the above looks like using a grid texture that comes with Unity's starter assets. (And some adjustments to the shader's default settings.)
    upload_2023-10-8_18-6-57.png

    And using the procedural grid with the base color adjusted.
    upload_2023-10-8_18-8-33.png
     
  7. KUNGERMOoN

    KUNGERMOoN

    Joined:
    Apr 29, 2020
    Posts:
    18
    Thank, you bgolus!
    You not only answered my question, but also wrote the code which does everything I needed, and much more.