Search Unity

Clipping shader: only render scene objects intersections with volume

Discussion in 'Shaders' started by Ofreyre, Nov 21, 2017.

  1. Ofreyre

    Ofreyre

    Joined:
    Jul 17, 2013
    Posts:
    28
    Hi,

    I have implemented a 3D field of view visualization, producing a list of vectors casting rays from a source position. Those vectors represent triangles that compose a flat surface over which fragments are visible. Then send those vectors to a global shader array, with it's length.

    The shader used by objects in scene, evaluates if the fragment is inside any of those triangles, if not, discard the fragment.

    Although the framerate is good running in editor, I have two problems with this approach:
    1. Is very GPU intensive and the framerate in mobile devices is just too slow.
    2. I can't publish for PC because the number of contants exeeds the limit.

    Before trying to occlude objects out of field of view range, which I don´t think will solve the performance issue, I want to try something different.

    I could try creating a volume with the base triangles and use stencil buffer, but my problem is that the camera is not at the source position and pointing at the same direction of the field of view, so the stencil will clip from camera perspective and not from the source point, that is what I need.

    This is the code that produces the vectors list.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class FOVV : MonoBehaviour {
    6.  
    7.     public class RaycastData
    8.     {
    9.         public bool hit = false;
    10.         public Vector3 v;
    11.         public float distance;
    12.         public float radians;
    13.         public Vector3 normal;
    14.  
    15.  
    16.         public RaycastData(bool hit, Vector3 v, float distance, float radians, Vector3 normal) {
    17.             this.hit = hit;
    18.             this.v = v;
    19.             this.distance = distance;
    20.             this.radians = radians;
    21.             this.normal = normal;
    22.  
    23.         }
    24.     }
    25.  
    26.     public struct MinMax
    27.     {
    28.         public RaycastData min;
    29.         public RaycastData max;
    30.  
    31.         public MinMax(RaycastData min, RaycastData max) {
    32.             this.min = min;
    33.             this.max = max;
    34.         }
    35.     }
    36.  
    37.     public float m_radius = 5;
    38.     [Range(0, 360)]
    39.     public float m_angle = 60f;
    40.     public int m_sections = 20;
    41.     public float m_castHeight = 0.1f;
    42.     public LayerMask m_layerMask;
    43.     public float m_mask_updatetime = 0.2f;
    44.     GameObject m_mask_gameObject;
    45.     public Material m_mask_material;
    46.     public float m_mask_top = 1f;
    47.     public float m_mask_bottom = 0f;
    48.     public int m_mask_edge_iterations = 6;
    49.     public float m_mask_edge_tolerance = 0.3f;
    50.     public float m_mask_offset = 0;
    51.     public float m_mask_normals_tolerance = 0.9f;
    52.     public bool m_mask_render = false;
    53.     float[] m_mask = new float[1000];
    54.  
    55.  
    56.     float mask_offset {
    57.         get { return m_mask_offset; }
    58.         set {
    59.             m_mask_offset = value;
    60.         }
    61.     }
    62.  
    63.     public Vector3 Angle2Vector(float radians) {
    64.         radians += transform.eulerAngles.y * Mathf.Deg2Rad;
    65.         return transform.position + new Vector3(m_radius * Mathf.Sin(radians), 0, m_radius * Mathf.Cos(radians));
    66.     }
    67.  
    68.     public Vector3 Angle2Direction(float radians) {
    69.         radians += transform.eulerAngles.y * Mathf.Deg2Rad;
    70.         return new Vector3(m_radius * Mathf.Sin(radians), 0, m_radius * Mathf.Cos(radians));
    71.     }
    72.  
    73.     public List<Vector3> RecalculateMask() {
    74.         float radians = m_angle * Mathf.Deg2Rad;
    75.         float angleD = radians / m_sections;
    76.         List<Vector3> mask = new List<Vector3>();
    77.         float angle0 = -radians * 0.5f;
    78.         RaycastData raycastData, prevRaycastData = null;
    79.         Vector3 origin = new Vector3(transform.position.x, Mathf.Max(m_castHeight, transform.position.y), transform.position.z);
    80.         for (int i = 0, n = m_sections + 1; i < n; i++) {
    81.             raycastData = Raycast(angle0 + i * angleD, origin);
    82.             if (i > 0)
    83.             {
    84.                 /*while (raycastData.hit != prevRaycastData.hit ||
    85.                     ( raycastData.hit == prevRaycastData.hit && (Mathf.Abs(raycastData.distance - prevRaycastData.distance) > m_mask_edge_tolerance) ||
    86.                     Mathf.Abs(Vector3.Dot(raycastData.normal, prevRaycastData.normal)) < m_mask_normals_tolerance))*/
    87.  
    88.                 while (raycastData.hit != prevRaycastData.hit ||
    89.                     (raycastData.hit == prevRaycastData.hit && (Mathf.Abs(raycastData.distance - prevRaycastData.distance) > m_mask_edge_tolerance) ||
    90.                     raycastData.normal != prevRaycastData.normal))
    91.                 {
    92.                     MinMax minmax = GetEdge(prevRaycastData, raycastData, origin);
    93.                     if (minmax.min.v != Vector3.zero)
    94.                     {
    95.                         mask.Add(origin + minmax.min.v * m_mask_offset);
    96.                     }
    97.                     if (minmax.max.v != Vector3.zero)
    98.                     {
    99.                         mask.Add(origin + minmax.max.v * m_mask_offset);
    100.                     }
    101.                     prevRaycastData = minmax.max;
    102.                 }
    103.             }
    104.             mask.Add(origin + raycastData.v * m_mask_offset);
    105.             prevRaycastData = raycastData;
    106.         }
    107.         return mask;
    108.     }
    109.  
    110.     public RaycastData Raycast(float radians, Vector3 origin) {
    111.         Vector3 direction = Angle2Direction(radians);
    112.         RaycastHit hit;
    113.         if (Physics.Raycast(origin, direction, out hit, m_radius, m_layerMask)) {
    114.             return new RaycastData(true, hit.point - origin, hit.distance, radians, hit.normal);
    115.         }
    116.         return new RaycastData(false, direction, m_radius, radians, hit.normal);
    117.     }
    118.  
    119.     public void SetShaderMask()
    120.     {
    121.         float radians = m_angle * Mathf.Deg2Rad;
    122.         float angleD = radians / m_sections;
    123.         float angle0 = -radians * 0.5f;
    124.         m_mask[0] = transform.position.x;
    125.         m_mask[1] = transform.position.z;
    126.  
    127.         RaycastData raycastData, prevRaycastData = null;
    128.         Vector3 v;
    129.         int j = 2;
    130.         Vector3 origin = new Vector3(transform.position.x, Mathf.Max(m_castHeight, transform.position.y), transform.position.z);
    131.  
    132.         for (int i = 0, n = m_sections + 1; i < n; i++)
    133.         {
    134.             raycastData = Raycast(angle0 + i * angleD, origin);
    135.             if (i > 0)
    136.             {
    137.                 /*while (raycastData.hit != prevRaycastData.hit ||
    138.                     (raycastData.hit == prevRaycastData.hit && (Mathf.Abs(raycastData.distance - prevRaycastData.distance) > m_mask_edge_tolerance) ||
    139.                     Mathf.Abs(Vector3.Dot(raycastData.normal, prevRaycastData.normal)) < m_mask_normals_tolerance))*/
    140.                 while (raycastData.hit != prevRaycastData.hit ||
    141.                 (raycastData.hit == prevRaycastData.hit && (Mathf.Abs(raycastData.distance - prevRaycastData.distance) > m_mask_edge_tolerance) ||
    142.                 raycastData.normal != prevRaycastData.normal))
    143.                 {
    144.                     MinMax minmax = GetEdge(prevRaycastData, raycastData, origin);
    145.                     if (minmax.min.v != Vector3.zero)
    146.                     {
    147.                         v = origin + minmax.min.v * m_mask_offset;
    148.                         m_mask[j] = v.x;
    149.                         j++;
    150.                         m_mask[j] = v.z;
    151.                         j++;
    152.                     }
    153.                     if (minmax.max.v != Vector3.zero)
    154.                     {
    155.                         v = origin + minmax.max.v * m_mask_offset;
    156.                         m_mask[j] = v.x;
    157.                         j++;
    158.                         m_mask[j] = v.z;
    159.                         j++;
    160.                     }
    161.                     prevRaycastData = minmax.max;
    162.                 }
    163.             }
    164.             v = origin + raycastData.v * m_mask_offset;
    165.             m_mask[j] = v.x;
    166.             j++;
    167.             m_mask[j] = v.z;
    168.             j++;
    169.             prevRaycastData = raycastData;
    170.         }
    171.         Shader.SetGlobalFloatArray("_MASK", m_mask);
    172.         Shader.SetGlobalFloat("_MASKLENGTH", j / 2);
    173.     }
    174.  
    175.  
    176.     MinMax GetEdge(RaycastData min, RaycastData max, Vector3 origin) {
    177.         RaycastData raycastData;
    178.         RaycastData minRaycast = min;
    179.         RaycastData maxRaycast = max;
    180.         int i = 0;
    181.         for (; i < m_mask_edge_iterations; i++) {
    182.             raycastData = Raycast((minRaycast.radians + maxRaycast.radians) * 0.5f, origin);
    183.             /*if (raycastData.hit == min.hit &&
    184.                 Mathf.Abs(raycastData.distance - minRaycast.distance) <= m_mask_edge_tolerance
    185.                 && Mathf.Abs(Vector3.Dot(raycastData.normal, minRaycast.normal)) > m_mask_normals_tolerance)*/
    186.             if (raycastData.hit == min.hit &&
    187.                 Mathf.Abs(raycastData.distance - minRaycast.distance) <= m_mask_edge_tolerance
    188.                 && raycastData.normal == minRaycast.normal)
    189.             {
    190.                 minRaycast = raycastData;
    191.             } else {
    192.                 maxRaycast = raycastData;
    193.             }
    194.         }
    195.         return new MinMax(minRaycast, maxRaycast);
    196.     }
    197.  
    198.     void Awake() {
    199.         Shader.SetGlobalFloatArray("_MASK", m_mask);
    200.         Shader.SetGlobalFloat("_MASKLENGTH", 0);
    201.     }
    202.    
    203.     // Update is called once per frame
    204.     void Update () {
    205.        
    206.     }
    207.  
    208.     void LateUpdate(){
    209.         SetShaderMask();
    210.     }
    211. }
    212.  

    This the shader:

    Code (CSharp):
    1. Shader "FOVV/Clip" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    6.         _Metallic ("Metallic", Range(0,1)) = 0.0
    7.         [MaterialToggle] _ShowClipping("Show Clipping", float) = 0
    8.     }
    9.     SubShader {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 200
    12.        
    13.         CGPROGRAM
    14.         // Physically based Standard lighting model, and enable shadows on all light types
    15.         #pragma surface surf Standard fullforwardshadows
    16.  
    17.         // Use shader model 3.0 target, to get nicer looking lighting
    18.         #pragma target 3.0
    19.         #pragma profileoption NumTemps=1024
    20.  
    21.         sampler2D _MainTex;
    22.  
    23.         struct Input {
    24.             float2 uv_MainTex;
    25.             float3 worldPos;
    26.  
    27.         };
    28.  
    29.         half _Glossiness;
    30.         half _Metallic;
    31.         fixed4 _Color;
    32.  
    33.         // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    34.         // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    35.         // #pragma instancing_options assumeuniformscaling
    36.         UNITY_INSTANCING_CBUFFER_START(Props)
    37.             // put more per-instance properties here
    38.         UNITY_INSTANCING_CBUFFER_END
    39.  
    40.         float _MASK[1000];
    41.         float _MASKLENGTH;
    42.         float _ShowClipping;
    43.  
    44.         float sign(float2 p1, float2 p2, float2 p3) {
    45.             return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
    46.         }
    47.  
    48.         float InTriangle(float2 p) {
    49.             if (_ShowClipping != 0) {
    50.                 //if (((int)((p.x + p.y) * 10)) % 2 == 0) {
    51.                 if (fmod(floor((p.x + p.y) * 10), 2) == 0) {
    52.                     return 1;
    53.                 }
    54.             }
    55.             if (_MASKLENGTH == 0) return 1;
    56.             float2 p1, p2, p3;
    57.             p1.x = _MASK[0];
    58.             p1.y = _MASK[1];
    59.             int k;
    60.             bool b1, b2, b3;
    61.             for (int i = 2; i < _MASKLENGTH; i++) {
    62.                 k = i * 2;
    63.                 p2.x = _MASK[k - 2];
    64.                 p2.y = _MASK[k - 1];
    65.                 p3.x = _MASK[k];
    66.                 p3.y = _MASK[k + 1];
    67.                 b1 = sign(p, p1, p2) < 0.0f;
    68.                 b2 = sign(p, p2, p3) < 0.0f;
    69.                 b3 = sign(p, p3, p1) < 0.0f;
    70.                 if (b1 == b2 && b2 == b3) {
    71.                     return 1;
    72.                 }
    73.             }
    74.             return -1;
    75.         }
    76.  
    77.         float AfterX(float x){
    78.             return x<0?1:-1;
    79.         }
    80.  
    81.         void surf (Input IN, inout SurfaceOutputStandard o) {
    82.             clip(InTriangle(IN.worldPos.xz));
    83.             //clip (AfterX(IN.worldPos.x));
    84.  
    85.             // Albedo comes from a texture tinted by color
    86.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    87.             o.Albedo = c.rgb;
    88.             // Metallic and smoothness come from slider variables
    89.             o.Metallic = _Metallic;
    90.             o.Smoothness = _Glossiness;
    91.             o.Alpha = c.a;
    92.         }
    93.         ENDCG
    94.     }
    95.     FallBack "Diffuse"
    96. }
    97.  
    Thanks