Unity的Mesh 应用---视野可视化Field of view

csdn 这垃圾工具, 一保存就丢内容!!!


https://github.com/SunGuangdong/Field-of-View   

https://www.youtube.com/watch?v=rQG9aUWarwE    (共3个)


测试场景这样的 , 橙色Cube 几个, 设置他们的 Layer 为 Obstacles

蓝色 Capsule 几个, 设置他们的 Layer 为 Targets

白色 Obstacles 作为角色。
先弄第一个脚本 挂在 Character 上: 控制方向和旋转!!!
using  UnityEngine;

///   <summary>
///  控制角色移动
///   </summary>
public   class   Controller  :  MonoBehaviour
{
     ///   <summary>
     ///  移动速度
     ///   </summary>
     public   float  moveSpeed = 6;
     Rigidbody  rb;
     Camera  viewCamera;
     Vector3  velocity;
     void   Start ()
    {
        rb = GetComponent< Rigidbody >();
        viewCamera =  Camera .main;
    }
     void   Update ()
    {
         // 鼠标位置控制 角色朝向
         Vector3  mousePos = viewCamera.ScreenToWorldPoint( new   Vector3 ( Input .mousePosition.x,  Input .mousePosition.y, viewCamera.transform.position.y));
        transform.LookAt(mousePos +  Vector3 .up * transform.position.y);
         // 方向键控制移动
        velocity =  new   Vector3 ( Input .GetAxisRaw( "Horizontal" ), 0,  Input .GetAxisRaw( "Vertical" )).normalized * moveSpeed;
    }
     void   FixedUpdate ()
    {
        rb.MovePosition(rb.position + velocity *  Time .fixedDeltaTime);
    }
}
然后 开始另外一个脚本组件, 先画一个圆

using  System.Collections;
using  System.Collections.Generic;
using  UnityEngine;

public   class   FieldOfView  :  MonoBehaviour
{
     ///   <summary>
     ///  圆半径
     ///   </summary>
     public   float  viewRadius;
     ///   <summary>
     ///  视野 角度
     ///   </summary>
    [ Range (0, 360)]
     public   float  viewAngle; ///   <summary>

///  根据角度得到 方向单位向量(极坐标 x=sin(a), z=cos(a))
///   </summary>
///   <param name=" angleInDegrees "></param>
///   <param name=" angleIsGlobal "></param>
///   <returns></returns>
public   Vector3  DirFromAngle( float  angleInDegrees,  bool  angleIsGlobal)
{
     // 相对自身的?
     if  (!angleIsGlobal)
    {
        angleInDegrees += transform.eulerAngles.y;
    }
     return   new   Vector3 ( Mathf .Sin(angleInDegrees *  Mathf .Deg2Rad), 0,  Mathf .Cos(angleInDegrees *  Mathf .Deg2Rad));
}

}


using  UnityEditor;
using  UnityEngine;

[ CustomEditor ( typeof ( FieldOfView ))]
public   class   FieldOfViewEditor  :  Editor
{
     void   OnSceneGUI ()
    {
         FieldOfView  fow = ( FieldOfView )target;
         Handles .color =  Color .white;
         // 画一个圆
         Handles .DrawWireArc(fow.transform.position,  Vector3 .up,  Vector3 .forward, 360, fow.viewRadius);
         // 视野的两条线
         Vector3  viewAngleA = fow.DirFromAngle(-fow.viewAngle / 2,  false );
         Vector3  viewAngleB = fow.DirFromAngle(fow.viewAngle / 2,  false );
         Handles .DrawLine(fow.transform.position, fow.transform.position + viewAngleA * fow.viewRadius);
         Handles .DrawLine(fow.transform.position, fow.transform.position + viewAngleB * fow.viewRadius);

    }

}
回到场景中, 不要Play,就在编辑器 状态下, 选中 Character 角色。 圆 和直线

然后添加碰撞检测相关的代码(障碍 和 目标):
using  System.Collections;
using  System.Collections.Generic;
using  UnityEngine;

public   class   FieldOfView  :  MonoBehaviour
{
     ///   <summary>
     ///  圆半径
     ///   </summary>
     public   float  viewRadius;
     ///   <summary>
     ///  视野 角度
     ///   </summary>
    [ Range (0, 360)]
     public   float  viewAngle;
     ///   <summary>
     ///  目标层
     ///   </summary>
     public   LayerMask  targetMask;
     ///   <summary>
     ///  障碍层
     ///   </summary>
     public   LayerMask  obstacleMask;
     ///   <summary>
     ///  在视野内能看到的目标
     ///   </summary>
    [ HideInInspector ]
     public   List < Transform > visibleTargets =  new   List < Transform >();
 
     void   Start ()
    {

        StartCoroutine(FindTargetsWithDelay(.2f));
    }
     IEnumerator  FindTargetsWithDelay( float  delay)
    {
         while  ( true )
        {
             yield   return   new   WaitForSeconds (delay);
            FindVisibleTargets();
        }
    }
     void  FindVisibleTargets()
    {
        visibleTargets.Clear();
         // 球内的所有碰撞体
         Collider [] targetsInViewRadius =  Physics .OverlapSphere(transform.position, viewRadius, targetMask);
         for  ( int  i = 0; i < targetsInViewRadius.Length; i++)
        {
             Transform  target = targetsInViewRadius[i].transform;
             // 目标方向
             Vector3  dirToTarget = (target.position - transform.position).normalized;
             // 在视野角度内
             if  ( Vector3 .Angle(transform.forward, dirToTarget) < viewAngle / 2)
            {
                 // 在视野距离内, 还不是障碍物
                 float  dstToTarget =  Vector3 .Distance(transform.position, target.position);
                 if  (! Physics .Raycast(transform.position, dirToTarget, dstToTarget, obstacleMask))
                {
                    visibleTargets.Add(target);
                }
            }
        }
    }

     ///   <summary>
     ///  根据角度得到 方向单位向量(极坐标 x=sin(a), z=cos(a))
     ///   </summary>
     ///   <param name=" angleInDegrees "></param>
     ///   <param name=" angleIsGlobal "></param>
     ///   <returns></returns>
     public   Vector3  DirFromAngle( float  angleInDegrees,  bool  angleIsGlobal)
    {
         // 相对自身的?
         if  (!angleIsGlobal)
        {
            angleInDegrees += transform.eulerAngles.y;
        }
         return   new   Vector3 ( Mathf .Sin(angleInDegrees *  Mathf .Deg2Rad), 0,  Mathf .Cos(angleInDegrees *  Mathf .Deg2Rad));
    }

}
// 如果视野内有 目标, 做一个红色连线
using  UnityEditor;
using  UnityEngine;

[ CustomEditor ( typeof ( FieldOfView ))]
public   class   FieldOfViewEditor  :  Editor
{
     void   OnSceneGUI ()
    {
         FieldOfView  fow = ( FieldOfView )target;
         Handles .color =  Color .white;
         // 画一个圆
         Handles .DrawWireArc(fow.transform.position,  Vector3 .up,  Vector3 .forward, 360, fow.viewRadius);
         // 视野的两条线
         Vector3  viewAngleA = fow.DirFromAngle(-fow.viewAngle / 2,  false );
         Vector3  viewAngleB = fow.DirFromAngle(fow.viewAngle / 2,  false );
         Handles .DrawLine(fow.transform.position, fow.transform.position + viewAngleA * fow.viewRadius);
         Handles .DrawLine(fow.transform.position, fow.transform.position + viewAngleB * fow.viewRadius);
// 和视野内的目标做一个红色连线
         Handles .color =  Color .red;
         foreach  ( Transform  visibleTarget  in  fow.visibleTargets)
        {
             Handles .DrawLine(fow.transform.position, visibleTarget.position);
        }
    }

}

注意,别忘了指定Layer

这个效果必须要 Play状态下看了!



2、 我们创建了一个系统来检测哪些目标在我们单位的视野中。 这对于stealth类型游戏是有用的。
使用Mesh进行视野绘制!


先写一些测试代码,看看
///   <summary>
///  视野被分的密度
///   </summary>
public   float  meshResolution;

void   LateUpdate ()
{
     //DrawFieldOfView();
     // 视野角度分成多少份
     int  stepCount =  Mathf .RoundToInt(viewAngle * meshResolution);
     // 每份大小
     float  stepAngleSize = viewAngle / stepCount;
     for  ( int  i = 0; i <= stepCount; i++)
    {
         float  angle = transform.eulerAngles.y - viewAngle / 2 + stepAngleSize * i;
         Debug .DrawLine(transform.position, transform.position + DirFromAngle(angle,  true ) * viewRadius,  Color .red);
    }
}

meshResolution = 0.1 的效果

meshResolution = 1 的效果


Mesh 最主要的数据 顶点 和三角形, 但是三角形也是顶点组合。
顶点通过射线确定位置。
这种扇形的Mesh还是挺有规律。 下面右图中, 顶点5个, 三角形3个。 那么所有三角形需要的数组就是 (v-2) *3 , 每个三角形是3个点。




///   <summary>
///  记录射线 射中的一些信息
///   </summary>
public   struct   ViewCastInfo
{
     // 射线是否射中目标
     public   bool  hit;
     // 射中的位置,(没有射中就是圆上的点呗)
     public   Vector3  point;
     // 射中的距离,(没有射中就是半径呗)
     public   float  dst;
     // 就是 这个射线的角度
     public   float  angle;
     public  ViewCastInfo( bool  _hit,  Vector3  _point,  float  _dst,  float  _angle)
    {
        hit = _hit;
        point = _point;
        dst = _dst;
        angle = _angle;
    }
}


///   <summary>
///  发射线,并记录命中信息
///   </summary>
///   <param name=" globalAngle "></param>
///   <returns></returns>
ViewCastInfo  ViewCast( float  globalAngle)
{
     Vector3  dir = DirFromAngle(globalAngle,  true );
     RaycastHit  hit;
     if  ( Physics .Raycast(transform.position, dir,  out  hit, viewRadius, obstacleMask))
    {
         return   new   ViewCastInfo ( true , hit.point, hit.distance, globalAngle);
    }
     else
    {
         return   new   ViewCastInfo ( false , transform.position + dir * viewRadius, viewRadius, globalAngle);
    }
}

void   LateUpdate ()
{
    DrawFieldOfView();
}


// 组件
public   MeshFilter  viewMeshFilter;
// Mesh 对象
Mesh  viewMesh;
void   Start ()
{
    viewMesh =  new   Mesh ();
    viewMesh.name =  "View Mesh" ;
    viewMeshFilter.mesh = viewMesh;
    StartCoroutine(FindTargetsWithDelay(.2f));
}


///   <summary>
///  绘制视野
///   </summary>
void  DrawFieldOfView()
{
     // 视野角度分成多少份
     int  stepCount =  Mathf .RoundToInt(viewAngle * meshResolution);
     // 每份大小
     float  stepAngleSize = viewAngle / stepCount;
     List < Vector3 > viewPoints =  new   List < Vector3 >();
     for  ( int  i = 0; i <= stepCount; i++)
    {
         float  angle = transform.eulerAngles.y - viewAngle / 2 + stepAngleSize * i;
         ViewCastInfo  newViewCast = ViewCast(angle);
        viewPoints.Add(newViewCast.point);
    }
     // 填充 Mesh的 顶点和 三角形数据
     int  vertexCount = viewPoints.Count + 1;   // 射线的数量在加上  圆点
     Vector3 [] vertices =  new   Vector3 [vertexCount];
     int [] triangles =  new   int [(vertexCount - 2) * 3];
    vertices[0] =  Vector3 .zero;  // 圆点
     for  ( int  i = 0; i < vertexCount - 1; i++)
    {
        vertices[i + 1] = transform.InverseTransformPoint(viewPoints[i]);
         if  (i < vertexCount - 2)
        {
            triangles[i * 3] = 0;
            triangles[i * 3 + 1] = i + 1;
            triangles[i * 3 + 2] = i + 2;
        }
    }
    viewMesh.Clear();
    viewMesh.vertices = vertices;
    viewMesh.triangles = triangles;
    viewMesh.RecalculateNormals();
}


为 Character 对象创建一个 子对象 用Cube就行(主要用它上面的 MeshFilter, MeshRenderer 组件)。

运行就可以看到Mesh已经出来了!!!, 可以为Cube换一个材质





但是有一个问题, 出于性能考虑 肯定不能将 meshResolution 的值设置的特别大。
如果值小了, 就会出现下面精度的问题!

边缘信息, 如果两个相邻节点发生了是否命中的改变。 说明两个点之间有一个碰撞边缘被漏掉了。
那么用二分的方式 在这两个相邻节点之间做射线。 找到碰撞的边缘信息!


using  System.Collections;
using  System.Collections.Generic;
using  UnityEngine;

public   class   FieldOfView  :  MonoBehaviour
{
     ///   <summary>
     ///  圆半径
     ///   </summary>
     public   float  viewRadius;
     ///   <summary>
     ///  视野 角度
     ///   </summary>
    [ Range (0, 360)]
     public   float  viewAngle;
     ///   <summary>
     ///  目标层
     ///   </summary>
     public   LayerMask  targetMask;
     ///   <summary>
     ///  障碍层
     ///   </summary>
     public   LayerMask  obstacleMask;
     ///   <summary>
     ///  在视野内能看到的目标
     ///   </summary>
    [ HideInInspector ]
     public   List < Transform > visibleTargets =  new   List < Transform >();
     ///   <summary>
     ///  视野被分的密度
     ///   </summary>
     public   float  meshResolution;
     ///   <summary>
     ///  查找边界 需要迭代的次数
     ///   </summary>
     public   int  edgeResolveIterations;
     ///   <summary>
     ///  当两个射线命中点很近(就是这个近的程度)的时候,认为他俩需要替换用的
     ///   </summary>
     public   float  edgeDstThreshold;
     ///   <summary>
     ///  顶点做偏移
     ///   </summary>
     public   float  maskCutawayDst = .1f;
     // 组件
     public   MeshFilter  viewMeshFilter;
     // Mesh 对象
     Mesh  viewMesh;
     void   Start ()
    {
        viewMesh =  new   Mesh ();
        viewMesh.name =  "View Mesh" ;
        viewMeshFilter.mesh = viewMesh;
        StartCoroutine(FindTargetsWithDelay(.2f));
    }
     IEnumerator  FindTargetsWithDelay( float  delay)
    {
         while  ( true )
        {
             yield   return   new   WaitForSeconds (delay);
            FindVisibleTargets();
        }
    }
     void   LateUpdate ()
    {
        DrawFieldOfView();
    }
     void  FindVisibleTargets()
    {
        visibleTargets.Clear();
         // 球内的所有碰撞体
         Collider [] targetsInViewRadius =  Physics .OverlapSphere(transform.position, viewRadius, targetMask);
         for  ( int  i = 0; i < targetsInViewRadius.Length; i++)
        {
             Transform  target = targetsInViewRadius[i].transform;
             // 目标方向
             Vector3  dirToTarget = (target.position - transform.position).normalized;
             // 在视野角度内
             if  ( Vector3 .Angle(transform.forward, dirToTarget) < viewAngle / 2)
            {
                 // 在视野距离内, 还不是障碍物
                 float  dstToTarget =  Vector3 .Distance(transform.position, target.position);
                 if  (! Physics .Raycast(transform.position, dirToTarget, dstToTarget, obstacleMask))
                {
                    visibleTargets.Add(target);
                }
            }
        }
    }
     ///   <summary>
     ///  绘制视野
     ///   </summary>
     void  DrawFieldOfView()
    {
         // 视野角度分成多少份
         int  stepCount =  Mathf .RoundToInt(viewAngle * meshResolution);
         // 每份大小
         float  stepAngleSize = viewAngle / stepCount;
         List < Vector3 > viewPoints =  new   List < Vector3 >();
         // old, new 是为了 边缘检测做准备
         ViewCastInfo  oldViewCast =  new   ViewCastInfo ();
         for  ( int  i = 0; i <= stepCount; i++)
        {
             float  angle = transform.eulerAngles.y - viewAngle / 2 + stepAngleSize * i;
             ViewCastInfo  newViewCast = ViewCast(angle);
             if  (i > 0)
            {
                 // 认为 两个点没在一个圆周上
                 bool  edgeDstThresholdExceeded =  Mathf .Abs(oldViewCast.dst - newViewCast.dst) > edgeDstThreshold;
                 // 两个射线的命中结果不一样 或者 命中了一样的但是碰撞体的这一面太斜了(不插入会露馅)
                 if  (oldViewCast.hit != newViewCast.hit || (oldViewCast.hit && newViewCast.hit && edgeDstThresholdExceeded))
                {
                     // 新旧两个点构成  一条边
                     EdgeInfo  edge = FindEdge(oldViewCast, newViewCast);
                     if  (edge.pointA !=  Vector3 .zero)
                    {
                        viewPoints.Add(edge.pointA);
                    }
                     if  (edge.pointB !=  Vector3 .zero)
                    {
                        viewPoints.Add(edge.pointB);
                    }
                }
            }
             Debug .DrawLine(transform.position, transform.position + DirFromAngle(angle,  true ) * viewRadius,  Color .red);
            viewPoints.Add(newViewCast.point);
            oldViewCast = newViewCast;
        }
         // 填充 Mesh的 顶点和 三角形数据
         int  vertexCount = viewPoints.Count + 1;   // 射线的数量在加上  圆点
         Vector3 [] vertices =  new   Vector3 [vertexCount];
         int [] triangles =  new   int [(vertexCount - 2) * 3];
        vertices[0] =  Vector3 .zero;  // 圆点
         for  ( int  i = 0; i < vertexCount - 1; i++)
        {
            vertices[i + 1] = transform.InverseTransformPoint(viewPoints[i]) +  Vector3 .forward * maskCutawayDst;
             if  (i < vertexCount - 2)
            {
                triangles[i * 3] = 0;
                triangles[i * 3 + 1] = i + 1;
                triangles[i * 3 + 2] = i + 2;
            }
        }
        viewMesh.Clear();
        viewMesh.vertices = vertices;
        viewMesh.triangles = triangles;
        viewMesh.RecalculateNormals();
    }
     ///   <summary>
     ///  找到边界 (是从Max 到 Min 做二分找)
     ///   </summary>
     ///   <param name=" minViewCast "></param>
     ///   <param name=" maxViewCast "></param>
     ///   <returns></returns>
     EdgeInfo  FindEdge( ViewCastInfo  minViewCast,  ViewCastInfo  maxViewCast)
    {
         float  minAngle = minViewCast.angle;
         float  maxAngle = maxViewCast.angle;
         Vector3  minPoint =  Vector3 .zero;
         Vector3  maxPoint =  Vector3 .zero;
         float  angle = minViewCast.angle;
         // 迭代 查找 边界(次数越多越精确)
         for  ( int  i = 0; i < edgeResolveIterations; i++)
        {
            angle = (minAngle + maxAngle) / 2;
             ViewCastInfo  newViewCast = ViewCast(angle);
             // 近似处理 两个碰撞点的距离 是否超出了范围
             bool  edgeDstThresholdExceeded =  Mathf .Abs(minViewCast.dst - newViewCast.dst) > edgeDstThreshold;
             // 经过迭代两个射线都 射中同一个碰撞体, 或者都未中。 就是可以认为新的点替代min然后继续做查找 (是从Max 到 Min 做二分找)
             if  (newViewCast.hit == minViewCast.hit && !edgeDstThresholdExceeded)
            {
                minAngle = angle;
                minPoint = newViewCast.point;
            }
             // 新的点替代max然后继续做查找 
             else
            {
                maxAngle = angle;
                maxPoint = newViewCast.point;
            }
        }
         Debug .DrawLine(transform.position, transform.position + DirFromAngle(angle,  true ) * viewRadius,  Color .red);
         return   new   EdgeInfo (minPoint, maxPoint);
    }
     ///   <summary>
     ///  发射线,并记录命中信息
     ///   </summary>
     ///   <param name=" globalAngle "></param>
     ///   <returns></returns>
     ViewCastInfo  ViewCast( float  globalAngle)
    {
         Vector3  dir = DirFromAngle(globalAngle,  true );
         RaycastHit  hit;
         if  ( Physics .Raycast(transform.position, dir,  out  hit, viewRadius, obstacleMask))
        {
             return   new   ViewCastInfo ( true , hit.point, hit.distance, globalAngle);
        }
         else
        {
             return   new   ViewCastInfo ( false , transform.position + dir * viewRadius, viewRadius, globalAngle);
        }
    }
     ///   <summary>
     ///  根据角度得到 方向单位向量(极坐标 x=sin(a), z=cos(a))
     ///   </summary>
     ///   <param name=" angleInDegrees "></param>
     ///   <param name=" angleIsGlobal "></param>
     ///   <returns></returns>
     public   Vector3  DirFromAngle( float  angleInDegrees,  bool  angleIsGlobal)
    {
         // 相对自身的?
         if  (!angleIsGlobal)
        {
            angleInDegrees += transform.eulerAngles.y;
        }
         return   new   Vector3 ( Mathf .Sin(angleInDegrees *  Mathf .Deg2Rad), 0,  Mathf .Cos(angleInDegrees *  Mathf .Deg2Rad));
    }
     ///   <summary>
     ///  记录射线 射中的一些信息
     ///   </summary>
     public   struct   ViewCastInfo
    {
         // 射线是否射中目标
         public   bool  hit;
         // 射中的位置,(没有射中就是圆上的点呗)
         public   Vector3  point;
         // 射中的距离,(没有射中就是半径呗)
         public   float  dst;
         // 就是 这个射线的角度
         public   float  angle;
         public  ViewCastInfo( bool  _hit,  Vector3  _point,  float  _dst,  float  _angle)
        {
            hit = _hit;
            point = _point;
            dst = _dst;
            angle = _angle;
        }
    }
     ///   <summary>
     ///  边缘信息
     ///   </summary>
     public   struct   EdgeInfo
    {
         // 有一个代表要插入的边缘点
         public   Vector3  pointA;
         public   Vector3  pointB;
         public  EdgeInfo( Vector3  _pointA,  Vector3  _pointB)
        {
            pointA = _pointA;
            pointB = _pointB;
        }
    }

}

可以看到这里面插入了一条边


3、 我们创建了一个stencil着色器,以实现一个很酷的有限FOV效果。

这个shader 最重要的就是 Stencil 关键字。这个技术可以实现很多功能, 特效,模型的Mask !!!

Shader   "Custom/Stencil Mask"  {
Properties  {
_Color  ( "Color" , Color) = (1,1,1,1)
_MainTex  ( "Albedo (RGB)" , 2D) =  "white"  {}
_Glossiness  ( "Smoothness" Range (0,1)) = 0.5
_Metallic  ( "Metallic" Range (0,1)) = 0.0
}
SubShader  {
Tags  {  "RenderType" = "Opaque"   "Queue" = "Geometry-100"  }
ColorMask 0
ZWrite off
LOD 200

Stencil {
Ref 1
Pass  replace
}

CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma  surface surf Standard fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma  target 3.0

sampler2D  _MainTex;

struct  Input {
float2  uv_MainTex;
};

half  _Glossiness;
half  _Metallic;
fixed4  _Color;

void   surf  (Input IN,  inout  SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4  c =  tex2D  (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack  "Diffuse"
}


Shader   "Custom/Stencil Object"  {
Properties  {
_Color  ( "Color" , Color) = (1,1,1,1)
_MainTex  ( "Albedo (RGB)" , 2D) =  "white"  {}
_Glossiness  ( "Smoothness" Range (0,1)) = 0.5
_Metallic  ( "Metallic" Range (0,1)) = 0.0
}
SubShader  {
Tags  {  "RenderType" = "Opaque"  }
LOD 200

Stencil {
Ref 1
Comp equal
}
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma  surface surf Standard fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma  target 3.0

sampler2D  _MainTex;

struct  Input {
float2  uv_MainTex;
};

half  _Glossiness;
half  _Metallic;
fixed4  _Color;

void   surf  (Input IN,  inout  SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4  c =  tex2D  (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack  "Diffuse"
}

之前 Character 创建的Cube用于 Mesh的显示, 它的Shader要 替换为 Stencil Mask 。
地面,所有障碍物和目标 Ground,Obstacles ,Targets 替换为 Stencil Object 着色器。



猜你喜欢

转载自blog.csdn.net/u010019717/article/details/80961587