在地形的表面绘制一个圆

在很多的RTS类型的游戏中,通过围绕被选中的单位绘制一个圆圈来表示距离(攻击范围,可移动距离,视野等等)。如果地面是平坦的,那么可以用通过对一张绘制有圆圈的矩形纹理进行缩放就能简单的做到。但是如果地面并不平坦,那么这个矩形的纹理就很有可能被高出的山丘或者其他几何物体遮住。接下来的知识点将展示如何编写一个着色器,让你可以在任何复杂的物体表面绘制一个圆圈[且不会被遮住]。如果你想对这个圆圈移动或者执行动画,那么我们就需要有着色器和C#代码才行。下图展示了用着色器在一个丘陵地形中绘制一个圆圈的例子:


  • 始前准备

    这里的着色器主要是用于地形的,对于其他的游戏对象不适用。所以我们首先要做的是在Unity中创建好一个地形。

    1. 我们先分别建立一个着色器和材质,名字分别是RadiusShaderRadiusMaterial

    2. 当你的角色物体准备好后;我们会绕着它绘制一个圆圈。

    3. 在Unity的菜单[这里的菜单操作我就不翻译了,翻译了感觉反而不好,怕翻译错了,大家找不到],选择 GameObject | 3D Object | Terrain 来创建一个新的地形。

    4. 为地形创建一些几何面。你可以导入一个已存在的地形或者自己用地形工具画一个新的。(Raise/Lower Terrain, Paint Height, Smooth Height )[括号里面这些都是Unity地形编辑器中的工具]

    5. 地形是Unity中特殊的游戏对象,它表面的纹理映射方式跟传统的3D模型不一样。你不能通过在着色器定义一个**_MainTex来提供纹理,因为在地形中需要直接由地形自己提供。这一步骤可以通过在地形编辑器中选择Paint Texture** 然后点击Add Texture…:

    1. 完成上面步骤后,纹理就设置好了。你必须修改地形的材质这样就可以在地形中使用我们提供的自定义的着色器。在 Terrain Settings中, 把Material一栏的属性改为Custom,接着把我们的Radius material材质拖拽到Custom Material栏上。

    接下来就要准备你自己的着色器了。


  • 操作步骤

    让我们开始编辑着色器RadiusShader 的代码:

    1. 在新的着色器中, 添加下面四个属性:
       _Center("Center", Vector) = (0,0,0,0)
       _Radius("Radius", Float) = 0.5
       _RadiusColor("Radius Color", Color) = (1,0,0,1)
       _RadiusWidth("Radius Width", Float) = 2
    
    1. 然后在CGPROGRAM块中添加它们各自的变量与之对应:
        float3 _Center;
        float _Radius;
        fixed4 _RadiusColor;
        float _RadiusWidth;
    
    1. 所以现在输入表面函数的数据不仅仅是纹理的UV数据了,还包括地形的中每一个点的位置(这个位置是基于世界坐标的)。 为了拿到这个参数我们需要修改输入结构体 Input struct,如下所示:
        struct Input
        {
          float2 uv_MainTex; // The UV of the terrain texture
          float3 worldPos; // The in-world position
        };
    
    1. 最后我们在表面函数中使用这个参数:
        void surf(Input IN, inout SurfaceOutputStandard o)
        {
          float d = distance(_Center, IN.worldPos);
          if (d > _Radius && d < _Radius + _RadiusWidth)
            o.Albedo = _RadiusColor;
          else
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }  
    

    通过上面的步骤,你就可以在地形中绘制一个圆。你可以通过材质的检查器面板Inspector tab去改变这个圆的位置,半径和颜色。

  • 在表面移动这个圆

    如果你想让圆跟着你的角色移动,那就还需要一些额外的工作:

    1. 创建一个叫Radius的C#脚本。
    2. 在脚本中添加下面这些属性:
    public Material radiusMaterial;
    public float radius = 1;
    public Color color = Color.white;
    
    1. 在**Update()**方法中,添加下面这些代码:
    radiusMaterial.SetVector("_Center", transform.position);
    radiusMaterial.SetFloat("_Radius", radius);
    radiusMaterial.SetColor("_RadiusColor", color);
    
    1. 然后把这个脚本挂在到你的角色身上。

    2. 最后把Radius material这个材质拖拽到C#脚本中的Radius Material中去[是在检查器面板Inspector tab中]。

    现在你可以移动你的角色,并且这个圆能很好的跟随玩家。并且修改脚本中的半径也能修改圆圈的半径大小。


  • 原理介绍

    绘制这个圆的相关参数是中心点,半径和颜色。它们分别可以在着色器中的**_Center**, _Radius, 和 _RadiusColor中获得。 通过在输入结构体Input structure 添加一个worldPos的变量,这等于是让Unity提供给我们像素点的位置,这些位置是用世界坐标来表示的。 这是编辑器中一个游戏对象的确切位置。 surf()函数是这个圆真正绘制的地方。 它会计算绘制点到中心点的距离,然后判断这个绘制点是否处于_Radius 和**_Radius + _RadiusWidth**之间;如果满足就使用对应的颜色。如果不是就不修改,跟我们以前遇到的情况一样对纹理贴图取样即可。